diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp index 761b07eceec83..ef65d22d16c3d 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -136,10 +136,9 @@ CodeAction toCodeAction(const Fix &F, const URIForFile &File, Edit.textDocument = VersionedTextDocumentIdentifier{{File}, Version}; for (const auto &E : F.Edits) Edit.edits.push_back( - {E.range, E.newText, - SupportChangeAnnotation ? E.annotationId : ""}); + {E.range, E.newText, SupportChangeAnnotation ? E.annotationId : ""}); if (SupportChangeAnnotation) { - for (const auto &[AID, Annotation]: F.Annotations) + for (const auto &[AID, Annotation] : F.Annotations) Action.edit->changeAnnotations[AID] = Annotation; } } @@ -909,24 +908,24 @@ void ClangdLSPServer::onRename(const RenameParams &Params, if (!Server->getDraft(File)) return Reply(llvm::make_error( "onRename called for non-added file", ErrorCode::InvalidParams)); - Server->rename(File, Params.position, Params.newName, Opts.Rename, - [File, Params, Reply = std::move(Reply), - this](llvm::Expected R) mutable { - if (!R) - return Reply(R.takeError()); - if (auto Err = validateEdits(*Server, R->GlobalChanges)) - return Reply(std::move(Err)); - WorkspaceEdit Result; - // FIXME: use documentChanges if SupportDocumentChanges is - // true. - Result.changes.emplace(); - for (const auto &Rep : R->GlobalChanges) { - (*Result - .changes)[URI::createFile(Rep.first()).toString()] = - Rep.second.asTextEdits(); - } - Reply(Result); - }); + Server->rename( + File, Params.position, Params.newName, Opts.Rename, + [File, Params, Reply = std::move(Reply), + this](llvm::Expected R) mutable { + if (!R) + return Reply(R.takeError()); + if (auto Err = validateEdits(*Server, R->GlobalChanges)) + return Reply(std::move(Err)); + WorkspaceEdit Result; + // FIXME: use documentChanges if SupportDocumentChanges is + // true. + Result.changes.emplace(); + for (const auto &Rep : R->GlobalChanges) { + (*Result.changes)[URI::createFile(Rep.first()).toString()] = + Rep.second.asTextEdits(); + } + Reply(Result); + }); } void ClangdLSPServer::onDocumentDidClose( @@ -1070,7 +1069,7 @@ void ClangdLSPServer::onCodeAction(const CodeActionParams &Params, std::map ToLSPDiags; ClangdServer::CodeActionInputs Inputs; - for (const auto& LSPDiag : Params.context.diagnostics) { + for (const auto &LSPDiag : Params.context.diagnostics) { if (auto DiagRef = getDiagRef(File.file(), LSPDiag)) { ToLSPDiags[*DiagRef] = LSPDiag; Inputs.Diagnostics.push_back(*DiagRef); @@ -1079,13 +1078,9 @@ void ClangdLSPServer::onCodeAction(const CodeActionParams &Params, Inputs.File = File.file(); Inputs.Selection = Params.range; Inputs.RequestedActionKinds = Params.context.only; - Inputs.TweakFilter = [this](const Tweak &T) { - return Opts.TweakFilter(T); - }; - auto CB = [this, - Reply = std::move(Reply), - ToLSPDiags = std::move(ToLSPDiags), File, - Selection = Params.range]( + Inputs.TweakFilter = [this](const Tweak &T) { return Opts.TweakFilter(T); }; + auto CB = [this, Reply = std::move(Reply), ToLSPDiags = std::move(ToLSPDiags), + File, Selection = Params.range]( llvm::Expected Fixits) mutable { if (!Fixits) return Reply(Fixits.takeError()); @@ -1094,8 +1089,7 @@ void ClangdLSPServer::onCodeAction(const CodeActionParams &Params, for (const auto &QF : Fixits->QuickFixes) { CAs.push_back(toCodeAction(QF.F, File, Version, SupportsDocumentChanges, SupportsChangeAnnotation)); - if (auto It = ToLSPDiags.find(QF.Diag); - It != ToLSPDiags.end()) { + if (auto It = ToLSPDiags.find(QF.Diag); It != ToLSPDiags.end()) { CAs.back().diagnostics = {It->second}; } } @@ -1356,7 +1350,8 @@ void ClangdLSPServer::onResolveTypeHierarchy( } Reply(serializeTHIForExtension(std::move(**Resp))); }; - Server->resolveTypeHierarchy(Params.item, Params.resolve, Params.direction, + Server->resolveTypeHierarchy(Params.item.uri.file(), Params.item, + Params.resolve, Params.direction, std::move(Serialize)); } @@ -1370,13 +1365,13 @@ void ClangdLSPServer::onPrepareTypeHierarchy( void ClangdLSPServer::onSuperTypes( const ResolveTypeHierarchyItemParams &Params, Callback>> Reply) { - Server->superTypes(Params.item, std::move(Reply)); + Server->superTypes(Params.item.uri.file(), Params.item, std::move(Reply)); } void ClangdLSPServer::onSubTypes( const ResolveTypeHierarchyItemParams &Params, Callback> Reply) { - Server->subTypes(Params.item, std::move(Reply)); + Server->subTypes(Params.item.uri.file(), Params.item, std::move(Reply)); } void ClangdLSPServer::onPrepareCallHierarchy( @@ -1389,7 +1384,7 @@ void ClangdLSPServer::onPrepareCallHierarchy( void ClangdLSPServer::onCallHierarchyIncomingCalls( const CallHierarchyIncomingCallsParams &Params, Callback> Reply) { - Server->incomingCalls(Params.item, std::move(Reply)); + Server->incomingCalls(Params.item.uri.file(), Params.item, std::move(Reply)); } void ClangdLSPServer::onClangdInlayHints(const InlayHintsParams &Params, @@ -1434,7 +1429,7 @@ void ClangdLSPServer::onInlayHint(const InlayHintsParams &Params, void ClangdLSPServer::onCallHierarchyOutgoingCalls( const CallHierarchyOutgoingCallsParams &Params, Callback> Reply) { - Server->outgoingCalls(Params.item, std::move(Reply)); + Server->outgoingCalls(Params.item.uri.file(), Params.item, std::move(Reply)); } void ClangdLSPServer::applyConfiguration( diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h index 6ada3fd9e6e47..64dcbfbc55325 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/clangd/ClangdLSPServer.h @@ -133,7 +133,8 @@ class ClangdLSPServer : private ClangdServer::Callbacks, Callback>); void onGoToImplementation(const TextDocumentPositionParams &, Callback>); - void onReference(const ReferenceParams &, Callback>); + void onReference(const ReferenceParams &, + Callback>); void onSwitchSourceHeader(const TextDocumentIdentifier &, Callback>); void onDocumentHighlight(const TextDocumentPositionParams &, @@ -243,7 +244,7 @@ class ClangdLSPServer : private ClangdServer::Callbacks, /// Used to indicate the ClangdLSPServer is being destroyed. std::atomic IsBeingDestroyed = {false}; - // FIXME: The caching is a temporary solution to get corresponding clangd + // FIXME: The caching is a temporary solution to get corresponding clangd // diagnostic from a LSP diagnostic. // Ideally, ClangdServer can generate an identifier for each diagnostic, // emit them via the LSP's data field (which was newly added in LSP 3.16). @@ -259,11 +260,9 @@ class ClangdLSPServer : private ClangdServer::Callbacks, return {LSPDiag.range, LSPDiag.message}; } /// A map from LSP diagnostic to clangd-naive diagnostic. - typedef std::map - DiagnosticToDiagRefMap; + typedef std::map DiagnosticToDiagRefMap; /// Caches the mapping LSP and clangd-naive diagnostics per file. - llvm::StringMap - DiagRefMap; + llvm::StringMap DiagRefMap; // Last semantic-tokens response, for incremental requests. std::mutex SemanticTokensMutex; diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp index f1a87dd12d905..e304db2914483 100644 --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -62,8 +62,8 @@ namespace clangd { namespace { // Tracks number of times a tweak has been offered. -static constexpr trace::Metric TweakAvailable( - "tweak_available", trace::Metric::Counter, "tweak_id"); +static constexpr trace::Metric + TweakAvailable("tweak_available", trace::Metric::Counter, "tweak_id"); // Update the FileIndex with new ASTs and plumb the diagnostics responses. struct UpdateIndexCallbacks : public ParsingCallbacks { @@ -872,29 +872,42 @@ void ClangdServer::typeHierarchy(PathRef File, Position Pos, int Resolve, } void ClangdServer::superTypes( - const TypeHierarchyItem &Item, + PathRef File, const TypeHierarchyItem &Item, Callback>> CB) { - WorkScheduler->run("typeHierarchy/superTypes", /*Path=*/"", - [=, CB = std::move(CB)]() mutable { - CB(clangd::superTypes(Item, Index)); - }); + auto Action = [File = File.str(), Item, CB = std::move(CB), + this](llvm::Expected InpAST) mutable { + if (!InpAST) + return CB(InpAST.takeError()); + CB(clangd::superTypes(Item, Index, InpAST->AST)); + }; + WorkScheduler->runWithAST("superTypes Calls", File, std::move(Action)); } -void ClangdServer::subTypes(const TypeHierarchyItem &Item, +void ClangdServer::subTypes(PathRef File, const TypeHierarchyItem &Item, Callback> CB) { - WorkScheduler->run( - "typeHierarchy/subTypes", /*Path=*/"", - [=, CB = std::move(CB)]() mutable { CB(clangd::subTypes(Item, Index)); }); + + auto Action = [File = File.str(), Item, CB = std::move(CB), + this](llvm::Expected InpAST) mutable { + if (!InpAST) + return CB(InpAST.takeError()); + CB(clangd::subTypes(Item, Index, InpAST->AST)); + }; + WorkScheduler->runWithAST("subTypes Calls", File, std::move(Action)); } void ClangdServer::resolveTypeHierarchy( - TypeHierarchyItem Item, int Resolve, TypeHierarchyDirection Direction, + PathRef File, TypeHierarchyItem Item, int Resolve, + TypeHierarchyDirection Direction, Callback> CB) { - WorkScheduler->run( - "Resolve Type Hierarchy", "", [=, CB = std::move(CB)]() mutable { - clangd::resolveTypeHierarchy(Item, Resolve, Direction, Index); - CB(Item); - }); + auto Action = [Item = std::move(Item), Resolve, Direction, CB = std::move(CB), + this](llvm::Expected InpAST) mutable { + if (!InpAST) + return CB(InpAST.takeError()); + clangd::resolveTypeHierarchy(Item, Resolve, Direction, Index, InpAST->AST); + CB(Item); + }; + WorkScheduler->runWithAST("resolveTypeHierarchy Calls", File, + std::move(Action)); } void ClangdServer::prepareCallHierarchy( @@ -909,12 +922,15 @@ void ClangdServer::prepareCallHierarchy( } void ClangdServer::incomingCalls( - const CallHierarchyItem &Item, + PathRef File, const CallHierarchyItem &Item, Callback> CB) { - WorkScheduler->run("Incoming Calls", "", - [CB = std::move(CB), Item, this]() mutable { - CB(clangd::incomingCalls(Item, Index)); - }); + auto Action = [Item, CB = std::move(CB), + this](llvm::Expected InpAST) mutable { + if (!InpAST) + return CB(InpAST.takeError()); + CB(clangd::incomingCalls(Item, Index, InpAST->AST)); + }; + WorkScheduler->runWithAST("Incoming Calls", File, std::move(Action)); } void ClangdServer::inlayHints(PathRef File, std::optional RestrictRange, @@ -929,12 +945,15 @@ void ClangdServer::inlayHints(PathRef File, std::optional RestrictRange, } void ClangdServer::outgoingCalls( - const CallHierarchyItem &Item, + PathRef File, const CallHierarchyItem &Item, Callback> CB) { - WorkScheduler->run("Outgoing Calls", "", - [CB = std::move(CB), Item, this]() mutable { - CB(clangd::outgoingCalls(Item, Index)); - }); + auto Action = [Item, CB = std::move(CB), + this](llvm::Expected InpAST) mutable { + if (!InpAST) + return CB(InpAST.takeError()); + CB(clangd::outgoingCalls(Item, Index, InpAST->AST)); + }; + WorkScheduler->runWithAST("Outgoing Calls", File, std::move(Action)); } void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) { diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h index 3ffaf67553dce..c582a96691deb 100644 --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -283,14 +283,14 @@ class ClangdServer { TypeHierarchyDirection Direction, Callback> CB); /// Get direct parents of a type hierarchy item. - void superTypes(const TypeHierarchyItem &Item, + void superTypes(PathRef File, const TypeHierarchyItem &Item, Callback>> CB); /// Get direct children of a type hierarchy item. - void subTypes(const TypeHierarchyItem &Item, + void subTypes(PathRef File, const TypeHierarchyItem &Item, Callback> CB); /// Resolve type hierarchy item in the given direction. - void resolveTypeHierarchy(TypeHierarchyItem Item, int Resolve, + void resolveTypeHierarchy(PathRef File, TypeHierarchyItem Item, int Resolve, TypeHierarchyDirection Direction, Callback> CB); @@ -299,11 +299,11 @@ class ClangdServer { Callback> CB); /// Resolve incoming calls for a given call hierarchy item. - void incomingCalls(const CallHierarchyItem &Item, - Callback>); + void incomingCalls(PathRef File, const CallHierarchyItem &Item, + Callback> CB); /// Resolve outgoing calls for a given call hierarchy item. - void outgoingCalls(const CallHierarchyItem &Item, + void outgoingCalls(PathRef File, const CallHierarchyItem &Item, Callback>); /// Resolve inlay hints for a given document. diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp index 243746056aed0..f82003e383edf 100644 --- a/clang-tools-extra/clangd/FindSymbols.cpp +++ b/clang-tools-extra/clangd/FindSymbols.cpp @@ -13,7 +13,10 @@ #include "Quality.h" #include "SourceCode.h" #include "index/Index.h" +#include "index/Symbol.h" +#include "index/SymbolLocation.h" #include "support/Logger.h" +#include "clang/AST/Decl.h" #include "clang/AST/DeclFriend.h" #include "clang/AST/DeclTemplate.h" #include "clang/Index/IndexSymbol.h" @@ -118,6 +121,16 @@ bool isAbstract(const Decl *D) { // Indicates whether declaration D is virtual in cases where D is a method. bool isVirtual(const Decl *D) { + // We want to treat a method as virtual if it is declared virtual, even if it + // is not implemented in this class, or if it overrides/implements a + // base-class method. This is because the "virtual" modifier is still relevant + // to the method's behavior and how it should be highlighted, even if it is + // not itself a virtual method in the strictest sense. For example, a method + // that overrides a virtual method from a base class is still considered + // virtual, even if it is not declared as such in the derived class. + // Similarly, a method that implements a pure virtual method from a base class + // is also considered virtual, even if it is not declared as such in the + // derived class. if (const auto *CMD = llvm::dyn_cast(D)) return CMD->isVirtual(); return false; @@ -159,6 +172,45 @@ SymbolTags toSymbolTagBitmask(const SymbolTag ST) { return (1 << static_cast(ST)); } +bool isOverrides(const NamedDecl *ND) { + if (const auto *MD = llvm::dyn_cast(ND)) { + // A method "overrides" if: + // 1. It overrides at least one method + // 2. At least one of the overridden methods is virtual (but NOT pure + // virtual) + + if (MD->size_overridden_methods() == 0) + return false; + + for (const auto Overridden : MD->overridden_methods()) { + // Check if the overridden method is virtual but not pure virtual + if (Overridden->isVirtual() && !Overridden->isPureVirtual()) + return true; + } + return false; + } + return false; +} + +bool isImplements(const NamedDecl *ND) { + if (const auto *MD = llvm::dyn_cast(ND)) { + // A method "implements" pure virtual methods from base classes if: + // 1. It overrides at least one method + // 2. It is NOT itself pure virtual (i.e., it has a concrete implementation) + // 3. ALL overridden methods are pure virtual + + if (MD->size_overridden_methods() == 0 || MD->isPureVirtual()) + return false; + + for (const auto Overridden : MD->overridden_methods()) { + if (!Overridden->isPureVirtual()) + return false; + } + return true; + } + return false; +} + SymbolTags computeSymbolTags(const NamedDecl &ND) { SymbolTags Result = 0; const auto IsDef = isUniqueDefinition(&ND); @@ -181,6 +233,12 @@ SymbolTags computeSymbolTags(const NamedDecl &ND) { if (isFinal(&ND)) Result |= toSymbolTagBitmask(SymbolTag::Final); + if (isOverrides(&ND)) + Result |= toSymbolTagBitmask(SymbolTag::Overrides); + + if (isImplements(&ND)) + Result |= toSymbolTagBitmask(SymbolTag::Implements); + if (not isa(ND)) { // Do not treat an UnresolvedUsingValueDecl as a declaration. // It's more common to think of it as a reference to the @@ -208,6 +266,62 @@ SymbolTags computeSymbolTags(const NamedDecl &ND) { return Result; } +// Filter symbol tags based on the presence of other tags and the kind of +// symbol. This is needed to avoid redundant tags. +SymbolTags filterSymbolTags(const NamedDecl &ND, const SymbolTags ST) { + SymbolTags Result = ST; + + if (not isa(ND)) + return Result; + + if (ST & toSymbolTagBitmask(SymbolTag::Overrides)) { + // Overrides means that ND overrides an existing implementation of a virtual + // method in a base class. If a symbol is marked as Overrides, the tags + // Virtual, Declaration and Definition should be removed, as the Overrides + // tag implies that the symbol has/is virtual/declaration/definition. + Result &= ~toSymbolTagBitmask(SymbolTag::Virtual); + Result &= ~toSymbolTagBitmask(SymbolTag::Declaration); + Result &= ~toSymbolTagBitmask(SymbolTag::Definition); + } + if (ST & toSymbolTagBitmask(SymbolTag::Implements)) { + // Implements means that ND implements an existing pure virtual method in a + // base class. If a symbol is marked as Implements, the tags Virtual, + // Declaration, Definition and Overrides should be removed, as the + // Implements tag implies that the symbol is virtual, is a declaration, is a + // definition, and overrides a method. + Result &= ~toSymbolTagBitmask(SymbolTag::Virtual); + Result &= ~toSymbolTagBitmask(SymbolTag::Declaration); + Result &= ~toSymbolTagBitmask(SymbolTag::Definition); + Result &= ~toSymbolTagBitmask(SymbolTag::Overrides); + } + if (ST & toSymbolTagBitmask(SymbolTag::Virtual)) { + // Virtual means that ND is a virtual method that does not override any + // method in a base class. If a symbol is marked as Virtual, the tags + // Declaration and Definition should be removed, as the Virtual tag implies + // that the symbol is a declaration/definition. + Result &= ~toSymbolTagBitmask(SymbolTag::Declaration); + Result &= ~toSymbolTagBitmask(SymbolTag::Definition); + } + if (ST & toSymbolTagBitmask(SymbolTag::Abstract)) { + // Abstract means that ND is a pure virtual method. If a symbol is marked as + // Abstract, the tags Virtual, Declaration and Definition should be removed, + // as the Abstract tag implies that the symbol is virtual and a + // declaration/definition. + Result &= ~toSymbolTagBitmask(SymbolTag::Virtual); + Result &= ~toSymbolTagBitmask(SymbolTag::Declaration); + Result &= ~toSymbolTagBitmask(SymbolTag::Definition); + } + if (ST & toSymbolTagBitmask(SymbolTag::Final)) { + // Final means that ND is a method that cannot be overridden by any method + // in a derived class. If a symbol is marked as Final, the tags Virtual and + // Overrides should be removed, as the Final tag implies that the symbol is + // virtual. + Result &= ~toSymbolTagBitmask(SymbolTag::Virtual); + Result &= ~toSymbolTagBitmask(SymbolTag::Overrides); + } + return Result; +} + std::vector getSymbolTags(const NamedDecl &ND) { const auto symbolTags = computeSymbolTags(ND); std::vector Tags; @@ -215,6 +329,9 @@ std::vector getSymbolTags(const NamedDecl &ND) { if (symbolTags == 0) return Tags; + // Apply specific filter to the symbol tags. + const auto filteredTags = filterSymbolTags(ND, symbolTags); + // Iterate through SymbolTag enum values and collect any that are present in // the bitmask. SymbolTag values are in the numeric range // [FirstTag .. LastTag]. @@ -222,12 +339,26 @@ std::vector getSymbolTags(const NamedDecl &ND) { constexpr unsigned MaxTag = static_cast(SymbolTag::LastTag); for (unsigned I = MinTag; I <= MaxTag; ++I) { auto ST = static_cast(I); - if (symbolTags & toSymbolTagBitmask(ST)) + if (filteredTags & toSymbolTagBitmask(ST)) Tags.push_back(ST); } return Tags; } +std::vector getSymbolTags(const Symbol &S) { + std::vector Tags; + + if (S.Flags & Symbol::Deprecated) + Tags.push_back(SymbolTag::Deprecated); + + if (S.Definition) + Tags.push_back(SymbolTag::Definition); + else + Tags.push_back(SymbolTag::Declaration); + + return Tags; +} + namespace { using ScoredSymbolInfo = std::pair; struct ScoredSymbolGreater { @@ -359,6 +490,7 @@ getWorkspaceSymbols(llvm::StringRef Query, int Limit, Info.score = Relevance.NameMatch > std::numeric_limits::epsilon() ? Score / Relevance.NameMatch : QualScore; + Info.tags = getSymbolTags(Sym); Top.push({Score, std::move(Info)}); }); for (auto &R : std::move(Top).items()) diff --git a/clang-tools-extra/clangd/FindSymbols.h b/clang-tools-extra/clangd/FindSymbols.h index 97b99af4f35e6..d85a03ebe449b 100644 --- a/clang-tools-extra/clangd/FindSymbols.h +++ b/clang-tools-extra/clangd/FindSymbols.h @@ -18,9 +18,13 @@ #include "llvm/ADT/StringRef.h" namespace clang { +class NamedDecl; + namespace clangd { class ParsedAST; class SymbolIndex; +struct Symbol; +struct SymbolLocation; /// A bitmask type representing symbol tags supported by LSP. /// \see @@ -69,6 +73,9 @@ SymbolTags computeSymbolTags(const NamedDecl &ND); /// \p ND The declaration to get tags for. std::vector getSymbolTags(const NamedDecl &ND); +/// Returns the symbol tags for an index `Symbol`. +std::vector getSymbolTags(const Symbol &S); + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp index 9926f2dd63de5..e1e3180dc4af8 100644 --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -209,7 +209,7 @@ bool fromJSON(const llvm::json::Value &Params, ChangeAnnotation &R, O.map("needsConfirmation", R.needsConfirmation) && O.mapOptional("description", R.description); } -llvm::json::Value toJSON(const ChangeAnnotation & CA) { +llvm::json::Value toJSON(const ChangeAnnotation &CA) { llvm::json::Object Result{{"label", CA.label}}; if (CA.needsConfirmation) Result["needsConfirmation"] = *CA.needsConfirmation; @@ -859,6 +859,8 @@ llvm::json::Value toJSON(const SymbolInformation &P) { }; if (P.score) O["score"] = *P.score; + if (!P.tags.empty()) + O["tags"] = P.tags; return std::move(O); } @@ -1437,6 +1439,8 @@ llvm::json::Value toJSON(const TypeHierarchyItem &I) { if (I.detail) Result["detail"] = I.detail; + if (!I.tags.empty()) + Result["tags"] = I.tags; return std::move(Result); } diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h index a88c9a391f97a..2450c476f1c55 100644 --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -1115,22 +1115,33 @@ enum class SymbolTag { Internal = 6, File = 7, Static = 8, - Abstract = 9, - Final = 10, + Abstract = 9, // In context of a class and method - this symbol indicates a + // pure virtual class or method. + Final = 10, // In context of a method - this symbol indicates that the method + // cannot be overridden in subclasses. + // In context of a class - this symbol indicates that the class is + // final and thus cannot be extended. Sealed = 11, Transient = 12, Volatile = 13, Synchronized = 14, - Virtual = 15, + Virtual = + 15, // In context of a method - this symbol indicates a virtual + // method declared and implemented in same class, and thereby it is + // not implementing or overriding a method from any base class. Nullable = 16, NonNull = 17, Declaration = 18, Definition = 19, ReadOnly = 20, + Overrides = 21, // In context of a method - this symbol indicates a method + // overriding a virtual method, implemented in base class. + Implements = 22, // In context of a method - this symbol indicates a method + // implementing a pure virtual method from a base class. // Update as needed FirstTag = Deprecated, - LastTag = ReadOnly + LastTag = Implements }; llvm::json::Value toJSON(SymbolTag); /// Represents programming constructs like variables, classes, interfaces etc. @@ -1548,6 +1559,9 @@ struct TypeHierarchyItem { /// The kind of this item. SymbolKind kind; + /// The symbol tags for this item. + std::vector tags; + /// More detail for this item, e.g. the signature of a function. std::optional detail; diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp index 8a24d19a7d129..fc1bc75de26e5 100644 --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -1810,8 +1810,9 @@ declToHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) { HierarchyItem HI; HI.name = printName(Ctx, ND); - // FIXME: Populate HI.detail the way we do in symbolToHierarchyItem? + HI.detail = printQualifiedName(ND); HI.kind = SK; + HI.tags = getSymbolTags(ND); HI.range = Range{sourceLocToPosition(SM, DeclRange->getBegin()), sourceLocToPosition(SM, DeclRange->getEnd())}; HI.selectionRange = Range{NameBegin, NameEnd}; @@ -1864,6 +1865,7 @@ static std::optional symbolToHierarchyItem(const Symbol &S, HI.name = std::string(S.Name); HI.detail = (S.Scope + S.Name).str(); HI.kind = indexSymbolKindToSymbolKind(S.SymInfo.Kind); + HI.tags = getSymbolTags(S); HI.selectionRange = Loc->range; // FIXME: Populate 'range' correctly // (https://github.com/clangd/clangd/issues/59). @@ -1889,23 +1891,60 @@ symbolToCallHierarchyItem(const Symbol &S, PathRef TUPath) { if (!Result) return Result; Result->data = S.ID.str(); - if (S.Flags & Symbol::Deprecated) - Result->tags.push_back(SymbolTag::Deprecated); return Result; } +// Tries to find a NamedDecl in the AST that matches the given Symbol. +// Returns nullptr if the symbol is not found in the current AST. +const NamedDecl *getNamedDeclFromSymbol(const Symbol &Sym, + const ParsedAST &AST) { + // Try to convert the symbol to a location and find the decl at that location + auto SymLoc = symbolToLocation(Sym, AST.tuPath()); + if (!SymLoc) + return nullptr; + + // Check if the symbol location is in the main file + if (SymLoc->uri.file() != AST.tuPath()) + return nullptr; + + // Convert LSP position to source location + const auto &SM = AST.getSourceManager(); + auto CurLoc = sourceLocationInMainFile(SM, SymLoc->range.start); + if (!CurLoc) { + llvm::consumeError(CurLoc.takeError()); + return nullptr; + } + + // Get all decls at this location + auto Decls = getDeclAtPosition(const_cast(AST), *CurLoc, {}); + if (Decls.empty()) + return nullptr; + + // Return the first decl (usually the most specific one) + return Decls[0]; +} + static void fillSubTypes(const SymbolID &ID, std::vector &SubTypes, - const SymbolIndex *Index, int Levels, PathRef TUPath) { + const SymbolIndex *Index, int Levels, PathRef TUPath, + const ParsedAST &AST) { RelationsRequest Req; Req.Subjects.insert(ID); Req.Predicate = RelationKind::BaseOf; - Index->relations(Req, [&](const SymbolID &Subject, const Symbol &Object) { - if (std::optional ChildSym = - symbolToTypeHierarchyItem(Object, TUPath)) { + Index->relations(Req, [&Levels, &Index, &SubTypes, &TUPath, + &AST](const SymbolID &Subject, const Symbol &Object) { + std::optional ChildSym; + + if (auto *ND = getNamedDeclFromSymbol(Object, AST)) { + ChildSym = declToTypeHierarchyItem(*ND, AST.tuPath()); + } else { + ChildSym = symbolToTypeHierarchyItem(Object, TUPath); + } + if (ChildSym) { if (Levels > 1) { ChildSym->children.emplace(); - fillSubTypes(Object.ID, *ChildSym->children, Index, Levels - 1, TUPath); + fillSubTypes(Object.ID, *ChildSym->children, Index, Levels - 1, TUPath, + AST); } SubTypes.emplace_back(std::move(*ChildSym)); } @@ -2128,15 +2167,15 @@ static QualType typeForNode(const ASTContext &Ctx, const HeuristicResolver *H, return QualType(); } -// Given a type targeted by the cursor, return one or more types that are more interesting -// to target. -static void unwrapFindType( - QualType T, const HeuristicResolver* H, llvm::SmallVector& Out) { +// Given a type targeted by the cursor, return one or more types that are more +// interesting to target. +static void unwrapFindType(QualType T, const HeuristicResolver *H, + llvm::SmallVector &Out) { if (T.isNull()) return; // If there's a specific type alias, point at that rather than unwrapping. - if (const auto* TDT = T->getAs()) + if (const auto *TDT = T->getAs()) return Out.push_back(QualType(TDT, 0)); // Pointers etc => pointee type. @@ -2170,8 +2209,8 @@ static void unwrapFindType( } // Convenience overload, to allow calling this without the out-parameter -static llvm::SmallVector unwrapFindType( - QualType T, const HeuristicResolver* H) { +static llvm::SmallVector unwrapFindType(QualType T, + const HeuristicResolver *H) { llvm::SmallVector Result; unwrapFindType(T, H, Result); return Result; @@ -2193,9 +2232,9 @@ std::vector findType(ParsedAST &AST, Position Pos, std::vector LocatedSymbols; // NOTE: unwrapFindType might return duplicates for something like - // unique_ptr>. Let's *not* remove them, because it gives you some - // information about the type you may have not known before - // (since unique_ptr> != unique_ptr). + // unique_ptr>. Let's *not* remove them, because it gives you + // some information about the type you may have not known before (since + // unique_ptr> != unique_ptr). for (const QualType &Type : unwrapFindType( typeForNode(AST.getASTContext(), AST.getHeuristicResolver(), N), AST.getHeuristicResolver())) @@ -2288,7 +2327,8 @@ getTypeHierarchy(ParsedAST &AST, Position Pos, int ResolveLevels, if (Index) { if (auto ID = getSymbolID(CXXRD)) - fillSubTypes(ID, *Result->children, Index, ResolveLevels, TUPath); + fillSubTypes(ID, *Result->children, Index, ResolveLevels, TUPath, + AST); } } Results.emplace_back(std::move(*Result)); @@ -2298,7 +2338,8 @@ getTypeHierarchy(ParsedAST &AST, Position Pos, int ResolveLevels, } std::optional> -superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index) { +superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index, + const ParsedAST &AST) { std::vector Results; if (!Item.data.parents) return std::nullopt; @@ -2310,8 +2351,14 @@ superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index) { Req.IDs.insert(Parent.symbolID); IDToData[Parent.symbolID] = &Parent; } - Index->lookup(Req, [&Item, &Results, &IDToData](const Symbol &S) { - if (auto THI = symbolToTypeHierarchyItem(S, Item.uri.file())) { + Index->lookup(Req, [&Item, &Results, &IDToData, &AST](const Symbol &S) { + std::optional THI; + if (auto *ND = getNamedDeclFromSymbol(S, AST)) { + THI = declToTypeHierarchyItem(*ND, AST.tuPath()); + } else { + THI = symbolToTypeHierarchyItem(S, Item.uri.file()); + } + if (THI) { THI->data = *IDToData.lookup(S.ID); Results.emplace_back(std::move(*THI)); } @@ -2320,9 +2367,10 @@ superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index) { } std::vector subTypes(const TypeHierarchyItem &Item, - const SymbolIndex *Index) { + const SymbolIndex *Index, + const ParsedAST &AST) { std::vector Results; - fillSubTypes(Item.data.symbolID, Results, Index, 1, Item.uri.file()); + fillSubTypes(Item.data.symbolID, Results, Index, 1, Item.uri.file(), AST); for (auto &ChildSym : Results) ChildSym.data.parents = {Item.data}; return Results; @@ -2330,7 +2378,7 @@ std::vector subTypes(const TypeHierarchyItem &Item, void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels, TypeHierarchyDirection Direction, - const SymbolIndex *Index) { + const SymbolIndex *Index, const ParsedAST &AST) { // We only support typeHierarchy/resolve for children, because for parents // we ignore ResolveLevels and return all levels of parents eagerly. if (!Index || Direction == TypeHierarchyDirection::Parents || @@ -2339,7 +2387,7 @@ void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels, Item.children.emplace(); fillSubTypes(Item.data.symbolID, *Item.children, Index, ResolveLevels, - Item.uri.file()); + Item.uri.file(), AST); } std::vector @@ -2369,8 +2417,10 @@ prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath) { } std::vector -incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) { +incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index, + const ParsedAST &AST) { std::vector Results; + if (!Index || Item.data.empty()) return Results; auto ID = SymbolID::fromStr(Item.data); @@ -2414,7 +2464,14 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) { Index->lookup(ContainerLookup, [&](const Symbol &Caller) { auto It = CallsIn.find(Caller.ID); assert(It != CallsIn.end()); - if (auto CHI = symbolToCallHierarchyItem(Caller, Item.uri.file())) { + + std::optional CHI; + if (auto *ND = getNamedDeclFromSymbol(Caller, AST)) { + CHI = declToCallHierarchyItem(*ND, AST.tuPath()); + } else { + CHI = symbolToCallHierarchyItem(Caller, Item.uri.file()); + } + if (CHI) { std::vector FromRanges; for (const Location &L : It->second) { if (L.uri != CHI->uri) { @@ -2451,7 +2508,8 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) { } std::vector -outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) { +outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index, + const ParsedAST &AST) { std::vector Results; if (!Index || Item.data.empty()) return Results; @@ -2497,7 +2555,16 @@ outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) { auto It = CallsOut.find(Callee.ID); assert(It != CallsOut.end()); - if (auto CHI = symbolToCallHierarchyItem(Callee, Item.uri.file())) { + + std::optional CHI; + + if (auto *ND = getNamedDeclFromSymbol(Callee, AST)) { + CHI = declToCallHierarchyItem(*ND, AST.tuPath()); + } else { + CHI = symbolToCallHierarchyItem(Callee, Item.uri.file()); + } + + if (CHI) { std::vector FromRanges; for (const Location &L : It->second) { if (L.uri != Item.uri) { diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h index 247e52314c3f9..f2652b5f274db 100644 --- a/clang-tools-extra/clangd/XRefs.h +++ b/clang-tools-extra/clangd/XRefs.h @@ -132,26 +132,31 @@ std::vector getTypeHierarchy( const SymbolIndex *Index = nullptr, PathRef TUPath = PathRef{}); /// Returns direct parents of a TypeHierarchyItem using SymbolIDs stored inside -/// the item. +/// the item or using the AST. std::optional> -superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index); +superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index, + const ParsedAST &AST); + /// Returns direct children of a TypeHierarchyItem. std::vector subTypes(const TypeHierarchyItem &Item, - const SymbolIndex *Index); + const SymbolIndex *Index, + const ParsedAST &AST); void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels, TypeHierarchyDirection Direction, - const SymbolIndex *Index); + const SymbolIndex *Index, const ParsedAST &AST); /// Get call hierarchy information at \p Pos. std::vector prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath); std::vector -incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index); +incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index, + const ParsedAST &AST); std::vector -outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index); +outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index, + const ParsedAST &AST); /// Returns all decls that are referenced in the \p FD except local symbols. llvm::DenseSet getNonLocalDeclRefs(ParsedAST &AST, diff --git a/clang-tools-extra/clangd/test/call-hierarchy.test b/clang-tools-extra/clangd/test/call-hierarchy.test index 6548ea0068a8d..aba1418e3ec84 100644 --- a/clang-tools-extra/clangd/test/call-hierarchy.test +++ b/clang-tools-extra/clangd/test/call-hierarchy.test @@ -9,6 +9,7 @@ # CHECK-NEXT: "result": [ # CHECK-NEXT: { # CHECK-NEXT: "data": "{{.*}}", +# CHECK-NEXT: "detail": "callee", # CHECK-NEXT: "kind": 12, # CHECK-NEXT: "name": "callee", # CHECK-NEXT: "range": { @@ -31,6 +32,9 @@ # CHECK-NEXT: "line": 0 # CHECK-NEXT: } # CHECK-NEXT: }, +# CHECK-NEXT: "tags": [ +# CHECK-NEXT: 18 +# CHECK-NEXT: ], # CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp" # CHECK-NEXT: } --- diff --git a/clang-tools-extra/clangd/test/symbol-tags.test b/clang-tools-extra/clangd/test/symbol-tags.test index 6b17dc994d029..34c06c4b9ba63 100644 --- a/clang-tools-extra/clangd/test/symbol-tags.test +++ b/clang-tools-extra/clangd/test/symbol-tags.test @@ -41,8 +41,6 @@ # CHECK: "tags": [ # CHECK: 2, # CHECK: 9, -# CHECK: 15, -# CHECK: 18, # CHECK: 20 # CHECK: ] # CHECK: } diff --git a/clang-tools-extra/clangd/test/symbols.test b/clang-tools-extra/clangd/test/symbols.test index a16a226e48c05..4f0d7b8b08569 100644 --- a/clang-tools-extra/clangd/test/symbols.test +++ b/clang-tools-extra/clangd/test/symbols.test @@ -24,7 +24,10 @@ # CHECK-NEXT: "uri": "file://{{.*}}/vector.h" # CHECK-NEXT: }, # CHECK-NEXT: "name": "vector", -# CHECK-NEXT: "score": {{.*}} +# CHECK-NEXT: "score": {{.*}}, +# CHECK-NEXT: "tags": [ +# CHECK-NEXT: 18 +# CHECK-NEXT: ] # CHECK-NEXT: } # CHECK-NEXT: ] # CHECK-NEXT:} diff --git a/clang-tools-extra/clangd/test/type-hierarchy-ext.test b/clang-tools-extra/clangd/test/type-hierarchy-ext.test index 8d1a5dc31da0f..d635b199d002c 100644 --- a/clang-tools-extra/clangd/test/type-hierarchy-ext.test +++ b/clang-tools-extra/clangd/test/type-hierarchy-ext.test @@ -17,11 +17,11 @@ # CHECK-NEXT: "name": "Child3", # CHECK-NEXT: "range": { # CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 13, +# CHECK-NEXT: "character": 25, # CHECK-NEXT: "line": 3 # CHECK-NEXT: }, # CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 7, +# CHECK-NEXT: "character": 0, # CHECK-NEXT: "line": 3 # CHECK-NEXT: } # CHECK-NEXT: }, @@ -52,6 +52,7 @@ # CHECK-NEXT: ], # CHECK-NEXT: "symbolID": "8A991335E4E67D08" # CHECK-NEXT: }, +# CHECK-NEXT: "detail": "Child2", # CHECK-NEXT: "kind": 23, # CHECK-NEXT: "name": "Child2", # CHECK-NEXT: "parents": [ @@ -65,6 +66,7 @@ # CHECK-NEXT: ], # CHECK-NEXT: "symbolID": "ECDC0C46D75120F4" # CHECK-NEXT: }, +# CHECK-NEXT: "detail": "Child1", # CHECK-NEXT: "kind": 23, # CHECK-NEXT: "name": "Child1", # CHECK-NEXT: "parents": [ @@ -73,6 +75,7 @@ # CHECK-NEXT: "parents": [], # CHECK-NEXT: "symbolID": "FE546E7B648D69A7" # CHECK-NEXT: }, +# CHECK-NEXT: "detail": "Parent", # CHECK-NEXT: "kind": 23, # CHECK-NEXT: "name": "Parent", # CHECK-NEXT: "parents": [], @@ -159,11 +162,11 @@ # CHECK-NEXT: "name": "Child4", # CHECK-NEXT: "range": { # CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 13, +# CHECK-NEXT: "character": 25, # CHECK-NEXT: "line": 4 # CHECK-NEXT: }, # CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 7, +# CHECK-NEXT: "character": 0, # CHECK-NEXT: "line": 4 # CHECK-NEXT: } # CHECK-NEXT: }, diff --git a/clang-tools-extra/clangd/test/type-hierarchy.test b/clang-tools-extra/clangd/test/type-hierarchy.test index a5f13ab13d0b3..c7b8a16ae51e2 100644 --- a/clang-tools-extra/clangd/test/type-hierarchy.test +++ b/clang-tools-extra/clangd/test/type-hierarchy.test @@ -22,6 +22,7 @@ # CHECK-NEXT: ], # CHECK-NEXT: "symbolID": "8A991335E4E67D08" # CHECK-NEXT: }, +# CHECK-NEXT: "detail": "Child2", # CHECK-NEXT: "kind": 23, # CHECK-NEXT: "name": "Child2", # CHECK-NEXT: "range": { @@ -44,6 +45,10 @@ # CHECK-NEXT: "line": 2 # CHECK-NEXT: } # CHECK-NEXT: }, +# CHECK-NEXT: "tags": [ +# CHECK-NEXT: 18, +# CHECK-NEXT: 19 +# CHECK-NEXT: ], # CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp" # CHECK-NEXT: } # CHECK-NEXT: ] @@ -67,11 +72,11 @@ # CHECK-NEXT: "name": "Child1", # CHECK-NEXT: "range": { # CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 13, +# CHECK-NEXT: "character": 25, # CHECK-NEXT: "line": 1 # CHECK-NEXT: }, # CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 7, +# CHECK-NEXT: "character": 0, # CHECK-NEXT: "line": 1 # CHECK-NEXT: } # CHECK-NEXT: }, @@ -85,6 +90,10 @@ # CHECK-NEXT: "line": 1 # CHECK-NEXT: } # CHECK-NEXT: }, +# CHECK-NEXT: "tags": [ +# CHECK-NEXT: 18, +# CHECK-NEXT: 19 +# CHECK-NEXT: ], # CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp" # CHECK-NEXT: } # CHECK-NEXT: ] @@ -118,11 +127,11 @@ # CHECK-NEXT: "name": "Child3", # CHECK-NEXT: "range": { # CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 13, +# CHECK-NEXT: "character": 25, # CHECK-NEXT: "line": 3 # CHECK-NEXT: }, # CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 7, +# CHECK-NEXT: "character": 0, # CHECK-NEXT: "line": 3 # CHECK-NEXT: } # CHECK-NEXT: }, @@ -136,6 +145,10 @@ # CHECK-NEXT: "line": 3 # CHECK-NEXT: } # CHECK-NEXT: }, +# CHECK-NEXT: "tags": [ +# CHECK-NEXT: 18, +# CHECK-NEXT: 19 +# CHECK-NEXT: ], # CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp" # CHECK-NEXT: } # CHECK-NEXT: ] diff --git a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp index 9859577c7cf7e..fb911ba07354a 100644 --- a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp +++ b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp @@ -48,6 +48,12 @@ MATCHER_P(withDetail, N, "") { return arg.detail == N; } MATCHER_P(withFile, N, "") { return arg.uri.file() == N; } MATCHER_P(withSelectionRange, R, "") { return arg.selectionRange == R; } +template +::testing::Matcher withSymbolTags(Tags... tags) { + // Matches the tags vector ignoring element order. + return Field(&CallHierarchyItem::tags, UnorderedElementsAre(tags...)); +} + template ::testing::Matcher from(ItemMatcher M) { return Field(&CallHierarchyIncomingCall::from, M); @@ -89,12 +95,12 @@ TEST(CallHierarchy, IncomingOneFileCpp) { std::vector Items = prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); ASSERT_THAT(Items, ElementsAre(withName("callee"))); - auto IncomingLevel1 = incomingCalls(Items[0], Index.get()); + auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST); ASSERT_THAT( IncomingLevel1, ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("caller1"))), iFromRanges(Source.range("Callee"))))); - auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get()); + auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST); ASSERT_THAT( IncomingLevel2, ElementsAre(AllOf(from(AllOf(withName("caller2"), withDetail("caller2"))), @@ -103,13 +109,13 @@ TEST(CallHierarchy, IncomingOneFileCpp) { AllOf(from(AllOf(withName("caller3"), withDetail("caller3"))), iFromRanges(Source.range("Caller1C"))))); - auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get()); + auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get(), AST); ASSERT_THAT( IncomingLevel3, ElementsAre(AllOf(from(AllOf(withName("caller3"), withDetail("caller3"))), iFromRanges(Source.range("Caller2"))))); - auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get()); + auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get(), AST); EXPECT_THAT(IncomingLevel4, IsEmpty()); } @@ -137,12 +143,12 @@ TEST(CallHierarchy, IncomingOneFileObjC) { std::vector Items = prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); ASSERT_THAT(Items, ElementsAre(withName("callee"))); - auto IncomingLevel1 = incomingCalls(Items[0], Index.get()); + auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST); ASSERT_THAT(IncomingLevel1, ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("MyClass::caller1"))), iFromRanges(Source.range("Callee"))))); - auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get()); + auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST); ASSERT_THAT(IncomingLevel2, ElementsAre(AllOf(from(AllOf(withName("caller2"), withDetail("MyClass::caller2"))), @@ -152,13 +158,13 @@ TEST(CallHierarchy, IncomingOneFileObjC) { withDetail("MyClass::caller3"))), iFromRanges(Source.range("Caller1C"))))); - auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get()); + auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get(), AST); ASSERT_THAT(IncomingLevel3, ElementsAre(AllOf(from(AllOf(withName("caller3"), withDetail("MyClass::caller3"))), iFromRanges(Source.range("Caller2"))))); - auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get()); + auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get(), AST); EXPECT_THAT(IncomingLevel4, IsEmpty()); } @@ -184,18 +190,18 @@ TEST(CallHierarchy, IncomingIncludeOverrides) { std::vector Items = prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); ASSERT_THAT(Items, ElementsAre(withName("callee"))); - auto IncomingLevel1 = incomingCalls(Items[0], Index.get()); + auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST); ASSERT_THAT(IncomingLevel1, ElementsAre(AllOf(from(AllOf(withName("Func"), withDetail("Implementation::Func"))), iFromRanges(Source.range("Callee"))))); - auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get()); + auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST); ASSERT_THAT( IncomingLevel2, ElementsAre(AllOf(from(AllOf(withName("Test"), withDetail("Test"))), iFromRanges(Source.range("FuncCall"))))); - auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get()); + auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get(), AST); EXPECT_THAT(IncomingLevel3, IsEmpty()); } @@ -221,13 +227,13 @@ TEST(CallHierarchy, MainFileOnlyRef) { std::vector Items = prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); ASSERT_THAT(Items, ElementsAre(withName("callee"))); - auto IncomingLevel1 = incomingCalls(Items[0], Index.get()); + auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST); ASSERT_THAT( IncomingLevel1, ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("caller1"))), iFromRanges(Source.range("Callee"))))); - auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get()); + auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST); EXPECT_THAT( IncomingLevel2, ElementsAre(AllOf(from(AllOf(withName("caller2"), withDetail("caller2"))), @@ -256,7 +262,7 @@ TEST(CallHierarchy, IncomingQualified) { std::vector Items = prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); ASSERT_THAT(Items, ElementsAre(withName("Waldo::find"))); - auto Incoming = incomingCalls(Items[0], Index.get()); + auto Incoming = incomingCalls(Items[0], Index.get(), AST); EXPECT_THAT( Incoming, ElementsAre( @@ -296,29 +302,29 @@ TEST(CallHierarchy, OutgoingOneFile) { std::vector Items = prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); ASSERT_THAT(Items, ElementsAre(withName("caller3"))); - auto OugoingLevel1 = outgoingCalls(Items[0], Index.get()); + auto OugoingLevel1 = outgoingCalls(Items[0], Index.get(), AST); ASSERT_THAT( OugoingLevel1, - ElementsAre( - AllOf(to(AllOf(withName("caller1"), withDetail("ns::Foo::caller1"))), - oFromRanges(Source.range("Caller1C"))), - AllOf(to(AllOf(withName("caller2"), withDetail("caller2"))), - oFromRanges(Source.range("Caller2"))))); + ElementsAre(AllOf(to(AllOf(withName("Foo::caller1"), + withDetail("ns::Foo::caller1"))), + oFromRanges(Source.range("Caller1C"))), + AllOf(to(AllOf(withName("caller2"), withDetail("caller2"))), + oFromRanges(Source.range("Caller2"))))); - auto OutgoingLevel2 = outgoingCalls(OugoingLevel1[1].to, Index.get()); + auto OutgoingLevel2 = outgoingCalls(OugoingLevel1[1].to, Index.get(), AST); ASSERT_THAT( OutgoingLevel2, ElementsAre(AllOf( - to(AllOf(withName("caller1"), withDetail("ns::Foo::caller1"))), + to(AllOf(withName("Foo::caller1"), withDetail("ns::Foo::caller1"))), oFromRanges(Source.range("Caller1A"), Source.range("Caller1B"))))); - auto OutgoingLevel3 = outgoingCalls(OutgoingLevel2[0].to, Index.get()); + auto OutgoingLevel3 = outgoingCalls(OutgoingLevel2[0].to, Index.get(), AST); ASSERT_THAT( OutgoingLevel3, ElementsAre(AllOf(to(AllOf(withName("callee"), withDetail("callee"))), oFromRanges(Source.range("Callee"))))); - auto OutgoingLevel4 = outgoingCalls(OutgoingLevel3[0].to, Index.get()); + auto OutgoingLevel4 = outgoingCalls(OutgoingLevel3[0].to, Index.get(), AST); EXPECT_THAT(OutgoingLevel4, IsEmpty()); } @@ -396,13 +402,14 @@ TEST(CallHierarchy, MultiFileCpp) { std::vector Items = prepareCallHierarchy(AST, Pos, TUPath); ASSERT_THAT(Items, ElementsAre(withName("callee"))); - auto IncomingLevel1 = incomingCalls(Items[0], Index.get()); + auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST); ASSERT_THAT(IncomingLevel1, ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("nsa::caller1"))), iFromRanges(Caller1C.range())))); - auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get()); + auto IncomingLevel2 = + incomingCalls(IncomingLevel1[0].from, Index.get(), AST); ASSERT_THAT( IncomingLevel2, ElementsAre( @@ -411,13 +418,15 @@ TEST(CallHierarchy, MultiFileCpp) { AllOf(from(AllOf(withName("caller3"), withDetail("nsa::caller3"))), iFromRanges(Caller3C.range("Caller1"))))); - auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get()); + auto IncomingLevel3 = + incomingCalls(IncomingLevel2[0].from, Index.get(), AST); ASSERT_THAT(IncomingLevel3, ElementsAre(AllOf(from(AllOf(withName("caller3"), withDetail("nsa::caller3"))), iFromRanges(Caller3C.range("Caller2"))))); - auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get()); + auto IncomingLevel4 = + incomingCalls(IncomingLevel3[0].from, Index.get(), AST); EXPECT_THAT(IncomingLevel4, IsEmpty()); }; @@ -430,7 +439,7 @@ TEST(CallHierarchy, MultiFileCpp) { ElementsAre(AllOf( withName("caller3"), withFile(testPath(IsDeclaration ? "caller3.hh" : "caller3.cc"))))); - auto OutgoingLevel1 = outgoingCalls(Items[0], Index.get()); + auto OutgoingLevel1 = outgoingCalls(Items[0], Index.get(), AST); ASSERT_THAT( OutgoingLevel1, // fromRanges are interpreted in the context of Items[0]'s file. @@ -444,19 +453,19 @@ TEST(CallHierarchy, MultiFileCpp) { IsDeclaration ? oFromRanges() : oFromRanges(Caller3C.range("Caller2"))))); - auto OutgoingLevel2 = outgoingCalls(OutgoingLevel1[1].to, Index.get()); + auto OutgoingLevel2 = outgoingCalls(OutgoingLevel1[1].to, Index.get(), AST); ASSERT_THAT(OutgoingLevel2, ElementsAre(AllOf( to(AllOf(withName("caller1"), withDetail("nsa::caller1"))), oFromRanges(Caller2C.range("A"), Caller2C.range("B"))))); - auto OutgoingLevel3 = outgoingCalls(OutgoingLevel2[0].to, Index.get()); + auto OutgoingLevel3 = outgoingCalls(OutgoingLevel2[0].to, Index.get(), AST); ASSERT_THAT( OutgoingLevel3, ElementsAre(AllOf(to(AllOf(withName("callee"), withDetail("callee"))), oFromRanges(Caller1C.range())))); - auto OutgoingLevel4 = outgoingCalls(OutgoingLevel3[0].to, Index.get()); + auto OutgoingLevel4 = outgoingCalls(OutgoingLevel3[0].to, Index.get(), AST); EXPECT_THAT(OutgoingLevel4, IsEmpty()); }; @@ -553,12 +562,13 @@ TEST(CallHierarchy, IncomingMultiFileObjC) { std::vector Items = prepareCallHierarchy(AST, Pos, TUPath); ASSERT_THAT(Items, ElementsAre(withName("callee"))); - auto IncomingLevel1 = incomingCalls(Items[0], Index.get()); + auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST); ASSERT_THAT(IncomingLevel1, ElementsAre(AllOf(from(withName("caller1")), iFromRanges(Caller1C.range())))); - auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get()); + auto IncomingLevel2 = + incomingCalls(IncomingLevel1[0].from, Index.get(), AST); ASSERT_THAT(IncomingLevel2, ElementsAre(AllOf(from(withName("caller2")), iFromRanges(Caller2C.range("A"), @@ -566,12 +576,14 @@ TEST(CallHierarchy, IncomingMultiFileObjC) { AllOf(from(withName("caller3")), iFromRanges(Caller3C.range("Caller1"))))); - auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get()); + auto IncomingLevel3 = + incomingCalls(IncomingLevel2[0].from, Index.get(), AST); ASSERT_THAT(IncomingLevel3, ElementsAre(AllOf(from(withName("caller3")), iFromRanges(Caller3C.range("Caller2"))))); - auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get()); + auto IncomingLevel4 = + incomingCalls(IncomingLevel3[0].from, Index.get(), AST); EXPECT_THAT(IncomingLevel4, IsEmpty()); }; @@ -616,7 +628,7 @@ TEST(CallHierarchy, CallInLocalVarDecl) { prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); ASSERT_THAT(Items, ElementsAre(withName("callee"))); - auto Incoming = incomingCalls(Items[0], Index.get()); + auto Incoming = incomingCalls(Items[0], Index.get(), AST); ASSERT_THAT(Incoming, ElementsAre(AllOf(from(withName("caller1")), iFromRanges(Source.range("call1"))), AllOf(from(withName("caller2")), @@ -643,7 +655,7 @@ TEST(CallHierarchy, HierarchyOnField) { std::vector Items = prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); ASSERT_THAT(Items, ElementsAre(withName("var1"))); - auto IncomingLevel1 = incomingCalls(Items[0], Index.get()); + auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST); ASSERT_THAT(IncomingLevel1, ElementsAre(AllOf(from(withName("caller")), iFromRanges(Source.range("Callee"))))); @@ -664,7 +676,7 @@ TEST(CallHierarchy, HierarchyOnVar) { std::vector Items = prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); ASSERT_THAT(Items, ElementsAre(withName("var"))); - auto IncomingLevel1 = incomingCalls(Items[0], Index.get()); + auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST); ASSERT_THAT(IncomingLevel1, ElementsAre(AllOf(from(withName("caller")), iFromRanges(Source.range("Callee"))))); @@ -686,14 +698,14 @@ TEST(CallHierarchy, HierarchyOnEnumConstant) { std::vector Items = prepareCallHierarchy(AST, Source.point("Heads"), testPath(TU.Filename)); ASSERT_THAT(Items, ElementsAre(withName("heads"))); - auto IncomingLevel1 = incomingCalls(Items[0], Index.get()); + auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST); ASSERT_THAT(IncomingLevel1, ElementsAre(AllOf(from(withName("caller")), iFromRanges(Source.range("CallerH"))))); Items = prepareCallHierarchy(AST, Source.point("Tails"), testPath(TU.Filename)); ASSERT_THAT(Items, ElementsAre(withName("tails"))); - IncomingLevel1 = incomingCalls(Items[0], Index.get()); + IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST); ASSERT_THAT(IncomingLevel1, ElementsAre(AllOf(from(withName("caller")), iFromRanges(Source.range("CallerT"))))); @@ -718,7 +730,7 @@ TEST(CallHierarchy, CallInDifferentFileThanCaller) { prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); ASSERT_THAT(Items, ElementsAre(withName("callee"))); - auto Incoming = incomingCalls(Items[0], Index.get()); + auto Incoming = incomingCalls(Items[0], Index.get(), AST); // The only call site is in the source file, which is a different file from // the declaration of the function containing the call, which is in the @@ -728,6 +740,56 @@ TEST(CallHierarchy, CallInDifferentFileThanCaller) { ElementsAre(AllOf(from(withName("caller")), iFromRanges()))); } +TEST(CallHierarchy, IncomingCalls) { + Annotations Source(R"cpp( + class A { + public: + void call^ee() {}; + }; + void caller(A &a) { + a.callee(); + } + )cpp"); + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + auto Index = TU.index(); + + std::vector Items = + prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); + ASSERT_THAT(Items, ElementsAre(withName("callee"))); + + auto Incoming = incomingCalls(Items[0], Index.get(), AST); + EXPECT_THAT( + Incoming, + UnorderedElementsAre(AllOf(from( + AllOf(withName("caller"), withSymbolTags(SymbolTag::Declaration, + SymbolTag::Definition)))))); +} + +TEST(CallHierarchy, OutgoingCalls) { + Annotations Source(R"cpp( + void callee() {} + class A { + public: + void call^er() { + callee(); + }; + }; + )cpp"); + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + auto Index = TU.index(); + + std::vector Items = + prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); + ASSERT_THAT(Items, ElementsAre(withName("caller"))); + + auto Outgoing = outgoingCalls(Items[0], Index.get(), AST); + EXPECT_THAT(Outgoing, UnorderedElementsAre(AllOf( + to(AllOf(withName("callee"), + withSymbolTags(SymbolTag::Declaration, + SymbolTag::Definition)))))); +} } // namespace } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp index d0dd5d0f8f434..c67302922e512 100644 --- a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp +++ b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp @@ -1138,12 +1138,125 @@ TEST(DocumentSymbolsTest, PragmaMarkGroupsNoNesting) { withName("Core"), withName("coreMethod"))); } -TEST(DocumentSymbolsTest, SymbolTags) { +TEST(DocumentSymbolsTest, SymbolTagsMustContainPublicAbstract) { TestTU TU; Annotations Main(R"cpp( - class AbstractClass { + class A { public: - virtual ~AbstractClass() = default; + virtual void f1() = 0; + }; + + class B : public A { + public: + virtual void f2() = 0; + }; + )cpp"); + + TU.Code = Main.code().str(); + auto Symbols = getSymbols(TU.build()); + EXPECT_THAT(Symbols, + UnorderedElementsAre( + AllOf(withName("A"), + children(AllOf(withName("f1"), + withSymbolTags(SymbolTag::Public, + SymbolTag::Abstract)))), + AllOf(withName("B"), + children(AllOf(withName("f2"), + withSymbolTags(SymbolTag::Public, + SymbolTag::Abstract)))))); +} + +TEST(DocumentSymbolsTest, SymbolTagsMustContainPublicVirtualAndOverrides) { + TestTU TU; + Annotations Main(R"cpp( + class A { + public: + virtual void f1() {}; + }; + + class B : public A { + public: + void f1() override {} + }; + + class C : public B { + public: + void f1() override {} + }; + )cpp"); + + TU.Code = Main.code().str(); + auto Symbols = getSymbols(TU.build()); + EXPECT_THAT( + Symbols, + UnorderedElementsAre( + AllOf(withName("A"), + children( + AllOf(withName("f1"), withSymbolTags(SymbolTag::Public, + SymbolTag::Virtual)))), + AllOf(withName("B"), + children(AllOf( + withName("f1"), + withSymbolTags(SymbolTag::Public, SymbolTag::Overrides)))), + AllOf(withName("C"), + children(AllOf(withName("f1"), + withSymbolTags(SymbolTag::Public, + SymbolTag::Overrides)))))); +} + +TEST(DocumentSymbolsTest, + SymbolTagsMustContainPublicAbstractImplementsOverridesAndFinal) { + TestTU TU; + Annotations Main(R"cpp( + class A { + public: + virtual void f1() = 0; + }; + + class B : public A { + public: + void f1() override {} + }; + + class C : public B { + public: + void f1() override {} + }; + + class D : public C { + public: + void f1() final override {} + }; + )cpp"); + + TU.Code = Main.code().str(); + auto Symbols = getSymbols(TU.build()); + EXPECT_THAT(Symbols, + UnorderedElementsAre( + AllOf(withName("A"), + children(AllOf(withName("f1"), + withSymbolTags(SymbolTag::Public, + SymbolTag::Abstract)))), + AllOf(withName("B"), + children(AllOf(withName("f1"), + withSymbolTags(SymbolTag::Public, + SymbolTag::Implements)))), + AllOf(withName("C"), + children(AllOf(withName("f1"), + withSymbolTags(SymbolTag::Public, + SymbolTag::Overrides)))), + AllOf(withName("D"), + children(AllOf(withName("f1"), + withSymbolTags(SymbolTag::Public, + SymbolTag::Final)))))); +} + +TEST(DocumentSymbolsTest, SymbolTagsCompilation) { + TestTU TU; + Annotations Main(R"cpp( + class A { + public: + virtual ~A() = default; virtual void f1() = 0; void f2() const; protected: @@ -1152,9 +1265,9 @@ TEST(DocumentSymbolsTest, SymbolTags) { static void f4(){} }; - void AbstractClass::f2() const {} + void A::f2() const {} - class ImplClass final: public AbstractClass { + class B final: public A { public: void f1() final {} }; @@ -1166,18 +1279,14 @@ TEST(DocumentSymbolsTest, SymbolTags) { Symbols, UnorderedElementsAre( AllOf( - withName("AbstractClass"), + withName("A"), withSymbolTags(SymbolTag::Abstract, SymbolTag::Declaration, SymbolTag::Definition), children( - AllOf(withName("~AbstractClass"), - withSymbolTags(SymbolTag::Public, SymbolTag::Virtual, - SymbolTag::Declaration, - SymbolTag::Definition)), + AllOf(withName("~A"), + withSymbolTags(SymbolTag::Public, SymbolTag::Virtual)), AllOf(withName("f1"), - withSymbolTags(SymbolTag::Public, SymbolTag::Abstract, - SymbolTag::Virtual, - SymbolTag::Declaration)), + withSymbolTags(SymbolTag::Public, SymbolTag::Abstract)), AllOf(withName("f2"), withSymbolTags(SymbolTag::Public, SymbolTag::Declaration, SymbolTag::ReadOnly)), @@ -1188,17 +1297,16 @@ TEST(DocumentSymbolsTest, SymbolTags) { withSymbolTags(SymbolTag::Private, SymbolTag::Static, SymbolTag::Declaration, SymbolTag::Definition)))), - AllOf(withName("AbstractClass::f2"), + AllOf(withName("A::f2"), withSymbolTags(SymbolTag::Public, SymbolTag::Declaration, SymbolTag::Definition, SymbolTag::ReadOnly)), - AllOf(withName("ImplClass"), - withSymbolTags(SymbolTag::Final, SymbolTag::Declaration, - SymbolTag::Definition), - children(AllOf( - withName("f1"), - withSymbolTags(SymbolTag::Public, SymbolTag::Final, - SymbolTag::Virtual, SymbolTag::Declaration, - SymbolTag::Definition)))))); + AllOf( + withName("B"), + withSymbolTags(SymbolTag::Final, SymbolTag::Declaration, + SymbolTag::Definition), + children(AllOf(withName("f1"), + withSymbolTags(SymbolTag::Public, SymbolTag::Final, + SymbolTag::Implements)))))); } } // namespace diff --git a/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp b/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp index 406a842f5a008..21adbb6d0d3a3 100644 --- a/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp +++ b/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp @@ -54,6 +54,12 @@ MATCHER_P(withResolveParents, M, "") { return testing::ExplainMatchResult(M, arg.data.parents, result_listener); } +template +::testing::Matcher withSymbolTags(Tags... tags) { + // Matches the tags vector ignoring element order. + return Field(&TypeHierarchyItem::tags, UnorderedElementsAre(tags...)); +} + TEST(FindRecordTypeAt, TypeOrVariable) { Annotations Source(R"cpp( struct Ch^ild2 { @@ -756,7 +762,7 @@ struct Child2b : Child1 {}; parentsNotResolved(), childrenNotResolved())))); resolveTypeHierarchy((*Result.front().children)[0], /*ResolveLevels=*/1, - TypeHierarchyDirection::Children, Index.get()); + TypeHierarchyDirection::Children, Index.get(), AST); EXPECT_THAT( (*Result.front().children)[0], @@ -770,10 +776,10 @@ struct Child2b : Child1 {}; TEST(Standard, SubTypes) { Annotations Source(R"cpp( -struct Pare^nt1 {}; -struct Parent2 {}; -struct Child : Parent1, Parent2 {}; -)cpp"); + struct Pare^nt1 {}; + struct Parent2 {}; + struct Child final : Parent1, Parent2 {}; + )cpp"); TestTU TU = TestTU::withCode(Source.code()); auto AST = TU.build(); @@ -783,7 +789,7 @@ struct Child : Parent1, Parent2 {}; TypeHierarchyDirection::Children, Index.get(), testPath(TU.Filename)); ASSERT_THAT(Result, SizeIs(1)); - auto Children = subTypes(Result.front(), Index.get()); + auto Children = subTypes(Result.front(), Index.get(), AST); // Make sure parents are populated when getting children. // FIXME: This is partial. @@ -791,15 +797,17 @@ struct Child : Parent1, Parent2 {}; Children, UnorderedElementsAre( AllOf(withName("Child"), + withSymbolTags(SymbolTag::Declaration, SymbolTag::Definition, + SymbolTag::Final), withResolveParents(Optional(UnorderedElementsAre(withResolveID( getSymbolID(&findDecl(AST, "Parent1")).str()))))))); } TEST(Standard, SuperTypes) { Annotations Source(R"cpp( -struct Parent {}; -struct Chil^d : Parent {}; -)cpp"); + struct Parent {}; + struct Chil^d : Parent {}; + )cpp"); TestTU TU = TestTU::withCode(Source.code()); auto AST = TU.build(); @@ -809,11 +817,13 @@ struct Chil^d : Parent {}; TypeHierarchyDirection::Children, Index.get(), testPath(TU.Filename)); ASSERT_THAT(Result, SizeIs(1)); - auto Parents = superTypes(Result.front(), Index.get()); + auto Parents = superTypes(Result.front(), Index.get(), AST); - EXPECT_THAT(Parents, Optional(UnorderedElementsAre( - AllOf(withName("Parent"), - withResolveParents(Optional(IsEmpty())))))); + EXPECT_THAT(Parents, + Optional(UnorderedElementsAre(AllOf( + withName("Parent"), + withSymbolTags(SymbolTag::Declaration, SymbolTag::Definition), + withResolveParents(Optional(IsEmpty())))))); } } // namespace } // namespace clangd