File indexing completed on 2024-05-05 16:46:04

0001 /*
0002     SPDX-FileCopyrightText: 2010, 2015 Alex Richardson <alex.richardson@gmx.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 #include "outlinenode.h"
0008 
0009 #include <language/duchain/duchainutils.h>
0010 #include <language/duchain/ducontext.h>
0011 #include <language/duchain/declaration.h>
0012 #include <language/duchain/duchain.h>
0013 #include <language/duchain/duchainlock.h>
0014 #include <language/duchain/classfunctiondeclaration.h>
0015 #include <language/duchain/functiondefinition.h>
0016 #include <language/duchain/types/alltypes.h>
0017 #include <language/duchain/namespacealiasdeclaration.h>
0018 #include <language/duchain/classdeclaration.h>
0019 #include <language/duchain/forwarddeclaration.h>
0020 
0021 #include <KTextEditor/CodeCompletionModel>
0022 
0023 #include <debug.h>
0024 
0025 using namespace KDevelop;
0026 
0027 OutlineNode::OutlineNode(const QString& text, OutlineNode* parent)
0028     : m_cachedText(text)
0029     , m_parent(parent)
0030 {
0031 }
0032 
0033 OutlineNode::OutlineNode(DUContext* ctx, const QString& name, OutlineNode* parent)
0034     : m_cachedText(name)
0035     , m_declOrContext(ctx)
0036     , m_parent(parent)
0037 {
0038     KTextEditor::CodeCompletionModel::CompletionProperties prop;
0039     switch (ctx->type()) {
0040         case KDevelop::DUContext::Class:
0041             prop |= KTextEditor::CodeCompletionModel::Class;
0042             break;
0043         case KDevelop::DUContext::Enum:
0044             prop |= KTextEditor::CodeCompletionModel::Enum;
0045             break;
0046         case KDevelop::DUContext::Function:
0047             prop |= KTextEditor::CodeCompletionModel::Function;
0048             break;
0049         case KDevelop::DUContext::Namespace:
0050             prop |= KTextEditor::CodeCompletionModel::Namespace;
0051             break;
0052         case KDevelop::DUContext::Template:
0053             prop |= KTextEditor::CodeCompletionModel::Template;
0054             break;
0055         default:
0056             break;
0057     }
0058     m_cachedIcon = DUChainUtils::iconForProperties(prop);
0059     appendContext(ctx, ctx->topContext());
0060 }
0061 
0062 
0063 OutlineNode::OutlineNode(Declaration* decl, OutlineNode* parent)
0064     : m_declOrContext(decl)
0065     , m_parent(parent)
0066 {
0067     // qCDebug(PLUGIN_OUTLINE) << "Adding:" << decl->qualifiedIdentifier().toString() << ": " <<typeid(*decl).name();
0068 
0069     // TODO: properly qualified identifier for out of line function definitions
0070     m_cachedText = decl->identifier().toString();
0071     m_cachedIcon = DUChainUtils::iconForDeclaration(decl);
0072     if (auto* alias = dynamic_cast<NamespaceAliasDeclaration*>(decl)) {
0073         //e.g. C++ using namespace statement
0074         m_cachedText = alias->importIdentifier().toString();
0075     }
0076     else if (auto* member = dynamic_cast<ClassMemberDeclaration*>(decl)) {
0077         if (member->isFriend()) {
0078             m_cachedText = QLatin1String("friend ") + m_cachedText;
0079         }
0080     }
0081     if (AbstractType::Ptr type = decl->abstractType()) {
0082         //add the (function return) type at the end (after a colon - like UML)
0083         //so that the first thing seen is the name of the function/variable
0084         //and not the (function return) type
0085         AbstractType::WhichType typeEnum = type->whichType();
0086         switch (typeEnum) {
0087         case AbstractType::TypeFunction: {
0088             auto func = type.staticCast<FunctionType>();
0089             // func->partToString() does not add the argument names -> do it manually
0090             if (DUContext* fCtx = DUChainUtils::functionContext(decl)) {
0091                 m_cachedText += QLatin1Char('(');
0092                 bool first = true;
0093                 const auto childDecls = fCtx->localDeclarations(decl->topContext());
0094                 for (Declaration* childDecl : childDecls) {
0095                     if (first) {
0096                         first = false;
0097                     } else {
0098                         m_cachedText += QLatin1String(", ");
0099                     }
0100 
0101                     if (childDecl->abstractType()) {
0102                         m_cachedText += childDecl->abstractType()->toString();
0103                     }
0104                     auto ident = childDecl->identifier();
0105                     if (!ident.isEmpty()) {
0106                         m_cachedText += QLatin1Char(' ') +  ident.toString();
0107                     }
0108 
0109                 }
0110                 m_cachedText += QLatin1Char(')');
0111             } else {
0112                 qCWarning(PLUGIN_OUTLINE) << "Missing function context:" << decl->qualifiedIdentifier().toString();
0113                 m_cachedText += func->partToString(FunctionType::SignatureArguments);
0114             }
0115             //constructors/destructors have no return type, a trailing semicolon would look stupid
0116             if (func->returnType()) {
0117                 m_cachedText += QLatin1String(" : ") + func->partToString(FunctionType::SignatureReturn);
0118             }
0119             return; // don't append any children here!
0120         }
0121         case AbstractType::TypeEnumeration:
0122             //no need to append the fully qualified type
0123             break;
0124         case AbstractType::TypeEnumerator:
0125             //no need to append the fully qualified type
0126             Q_ASSERT(decl->type<EnumeratorType>());
0127             m_cachedText += QLatin1String(" = ") + decl->type<EnumeratorType>()->valueAsString();
0128             break;
0129         case AbstractType::TypeStructure: {
0130             //this seems to be the way it has to be done (after grepping through source code)
0131             //TODO shouldn't there be some kind of isFriend() functionality?
0132             static IndexedIdentifier friendIdentifier(Identifier(QStringLiteral("friend")));
0133             const bool isFriend = decl->indexedIdentifier() == friendIdentifier;
0134             if (isFriend) {
0135                 //FIXME There seems to be no way of finding out whether the friend is class/struct/etc
0136                 m_cachedText += QLatin1Char(' ') + type->toString();
0137             }
0138             break;
0139         }
0140         case AbstractType::TypeAlias: {
0141             //append the type it aliases
0142             auto alias = type.staticCast<TypeAliasType>();
0143             if (AbstractType::Ptr targetType = alias->type()) {
0144                 m_cachedText += QLatin1String(" : ") + targetType->toString();
0145             }
0146         }
0147         break;
0148         default:
0149             QString typeStr = type->toString();
0150             if (!typeStr.isEmpty()) {
0151                 m_cachedText += QLatin1String(" : ") + typeStr;
0152             }
0153         }
0154     }
0155 
0156 
0157     //these two don't seem to be hit
0158     if (decl->isAutoDeclaration()) {
0159         m_cachedText = QLatin1String("Implicit: ") + m_cachedText;
0160     }
0161     if (decl->isAnonymous()) {
0162         m_cachedText = QLatin1String("<anonymous>") + m_cachedText;
0163     }
0164 
0165     if (DUContext* ctx = decl->internalContext()) {
0166         appendContext(ctx, decl->topContext());
0167     }
0168     if (m_cachedText.isEmpty()) {
0169         m_cachedText = i18nc("An anonymous declaration (class, function, etc.)", "<anonymous>");
0170     }
0171 }
0172 
0173 std::unique_ptr<OutlineNode> OutlineNode::dummyNode()
0174 {
0175     return std::unique_ptr<OutlineNode>(new OutlineNode(QStringLiteral("<dummy node>"), nullptr));
0176 }
0177 
0178 std::unique_ptr<OutlineNode> OutlineNode::fromTopContext(TopDUContext* ctx)
0179 {
0180     auto result = dummyNode();
0181     result->appendContext(ctx, ctx);
0182     return result;
0183 }
0184 
0185 void OutlineNode::appendContext(DUContext* ctx, TopDUContext* top)
0186 {
0187     // qDebug() << ctx->scopeIdentifier().toString() << "context type=" << ctx->type();
0188     const auto childDecls = ctx->localDeclarations(top);
0189     for (Declaration* childDecl : childDecls) {
0190         if (childDecl) {
0191             m_children.emplace_back(childDecl, this);
0192         }
0193     }
0194     bool certainlyRequiresSorting = false;
0195     const auto childContexts = ctx->childContexts();
0196     for (DUContext* childContext : childContexts) {
0197         if (childContext->owner()) {
0198             // if there is a onwner, this will already have been handled by the loop above
0199             // TODO: is this always true? With my testing so far it seems to be
0200             // qDebug() << childContext->scopeIdentifier(true).toString()
0201             //        << " has an owner declaration: " << childContext->owner()->toString() << "-> skip";
0202             continue;
0203         }
0204         QVector<Declaration*> decls = childContext->localDeclarations(top);
0205         if (decls.isEmpty()) {
0206             continue;
0207         }
0208         // we now know that we will have o sort since we appended a node in the wrong order
0209         certainlyRequiresSorting = true;
0210         QString ctxName = childContext->scopeIdentifier(true).toString();
0211         // if child context is a template context or if name is empty append to current list,
0212         // otherwise create a new context node
0213         if (childContext->type() == DUContext::Template || ctxName.isEmpty()) {
0214             //append all subcontexts to this node
0215             appendContext(childContext, top);
0216         } else {
0217             // context without matching declaration, for example the definition of
0218             // "class Foo::Bar if it was forward declared in a namespace before:
0219             // namespace Foo { class Bar; }
0220             // class Foo::Bar { ... };
0221             // TODO: icon and location for the namespace
0222             if (childContext->type() == DUContext::ContextType::Helper) {
0223                 // This context could be for a definition of an existing class method.
0224                 // If we don't merge all those context end up with a tree like this:
0225                 //  +-+- FooClass
0226                 //  | \-- method1()
0227                 //  +-+- FooClass
0228                 //  | \-- method2()
0229                 //  \ OtherStuff
0230                 auto it = std::find_if(m_children.begin(), m_children.end(), [childContext](const OutlineNode& node) {
0231                     if (auto* ctx = dynamic_cast<DUContext*>(node.duChainObject())) {
0232                         return ctx->equalScopeIdentifier(childContext);
0233                     }
0234                     return false;
0235                 });
0236                 if (it != m_children.end()) {
0237                     it->appendContext(childContext, top);
0238                 }
0239                 else {
0240                     // TODO: get the correct icon for the context
0241                     m_children.emplace_back(childContext, ctxName, this);
0242                 }
0243             } else {
0244                 // just add the context
0245                 m_children.emplace_back(childContext, ctxName, this);
0246             }
0247         }
0248     }
0249     // we now need to sort since sometimes the elements from ctx->localDeclarations(top)
0250     // are not in the order they appear in the source. Additionally, if we had any child
0251     // contexts that were added, they will be at the end of the list
0252     // and need to be moved to the correct location. In that case certainlyRequiresSorting
0253     // will be true and we can pass it to sortByLocation() to skip the std::is_sorted() call
0254     sortByLocation(certainlyRequiresSorting);
0255 }
0256 
0257 void OutlineNode::sortByLocation(bool requiresSorting)
0258 {
0259 
0260     if (m_children.size() <= 1) {
0261         return;
0262     }
0263     // TODO: does it make sense to cache m_declOrContext->range().start?
0264     // adds 8 bytes to each node, but save a lot of pointer lookups when sorting
0265     // qDebug("sorting children of %s (%p) by location", qPrintable(m_cachedText), this);
0266     auto compare = [](const OutlineNode& n1, const OutlineNode& n2) -> bool {
0267         // nodes without decl always go at the end
0268         if (!n1.m_declOrContext) {
0269             return false;
0270         } else if (!n2.m_declOrContext) {
0271             return true;
0272         }
0273         return n1.m_declOrContext->range().start < n2.m_declOrContext->range().start;
0274     };
0275     // since most nodes will be correctly sorted we check that before calling std::sort().
0276     // This saves a lot of move ctor/assignment calls in the common case.
0277     // If we appended a context without a Declaration* we know that it will be unsorted
0278     // so we can pass requiresSorting = true to skip the useless std::is_sorted() call.
0279     // uncomment the following qDebug() lines to see whether this optimization really makes sense
0280     if (requiresSorting || !std::is_sorted(m_children.begin(), m_children.end(), compare)) {
0281         // qDebug("Need to sort node %s(%p)", qPrintable(m_cachedText), this);
0282         std::sort(m_children.begin(), m_children.end(), compare);
0283     } else {
0284         // qDebug("Node %s(%p) was sorted!", qPrintable(m_cachedText), this);
0285     }
0286 }
0287 
0288 OutlineNode::~OutlineNode()
0289 {
0290 }