diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h index 6ada3fd9e6e47..bd9c5e6bc6954 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/clangd/ClangdLSPServer.h @@ -243,7 +243,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). diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp index 147dd38db8a8a..36d2e42307dea 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" @@ -135,6 +138,42 @@ bool isFinal(const Decl *D) { return false; } +// 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) +bool isOverrides(const NamedDecl *ND) { + if (const auto *MD = llvm::dyn_cast(ND)) { + 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; +} + +// 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 +bool isImplements(const NamedDecl *ND) { + if (const auto *MD = llvm::dyn_cast(ND)) { + 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; +} + // Indicates whether declaration D is a unique definition (as opposed to a // declaration). bool isUniqueDefinition(const NamedDecl *Decl) { @@ -153,6 +192,51 @@ bool isUniqueDefinition(const NamedDecl *Decl) { isa(Decl) || isa(Decl) || isa(Decl); } + +// Filter symbol tags based on the presence of other tags and the kind of +// symbol. This is needed to avoid redundant tags, e.g. Overrides implies +// Virtual and Implements implies Overrides/Virtual. +SymbolTags filterSymbolTags(SymbolTags ST) { + const SymbolTags VirtualMask = toSymbolTagBitmask(SymbolTag::Virtual); + const SymbolTags OverridesMask = toSymbolTagBitmask(SymbolTag::Overrides); + const SymbolTags ImplementsMask = toSymbolTagBitmask(SymbolTag::Implements); + const SymbolTags AbstractMask = toSymbolTagBitmask(SymbolTag::Abstract); + const SymbolTags FinalMask = toSymbolTagBitmask(SymbolTag::Final); + + const SymbolTags RemoveVirtualAndOverrides = VirtualMask | OverridesMask; + + // Implements implies both Overrides and Virtual. + if (ST & ImplementsMask) + ST &= ~RemoveVirtualAndOverrides; + + // Final also suppresses both Virtual and Overrides in this model. + if (ST & FinalMask) + ST &= ~RemoveVirtualAndOverrides; + + // Overrides or Abstract each imply Virtual. + if (ST & (OverridesMask | AbstractMask)) + ST &= ~VirtualMask; + + return ST; +} + +bool isCXXClassMethod(const clang::clangd::Symbol &S) { + using clang::index::SymbolKind; + using clang::index::SymbolLanguage; + + if (S.SymInfo.Lang != SymbolLanguage::CXX) + return false; + + return llvm::is_contained({SymbolKind::InstanceMethod, + SymbolKind::StaticMethod, SymbolKind::Constructor, + SymbolKind::Destructor, + SymbolKind::ConversionFunction}, + S.SymInfo.Kind); +} + +template constexpr E enumIncrement(E Value) { + return static_cast(static_cast>(Value) + 1); +} } // namespace SymbolTags toSymbolTagBitmask(const SymbolTag ST) { @@ -178,9 +262,15 @@ SymbolTags computeSymbolTags(const NamedDecl &ND) { if (isAbstract(&ND)) Result |= toSymbolTagBitmask(SymbolTag::Abstract); + if (isOverrides(&ND)) + Result |= toSymbolTagBitmask(SymbolTag::Overrides); + if (isFinal(&ND)) Result |= toSymbolTagBitmask(SymbolTag::Final); + 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,13 +298,16 @@ SymbolTags computeSymbolTags(const NamedDecl &ND) { return Result; } -std::vector getSymbolTags(const NamedDecl &ND) { - const auto symbolTags = computeSymbolTags(ND); +std::vector expandTagBitmask(const SymbolTags STGS) { std::vector Tags; - if (symbolTags == 0) + if (STGS == 0) return Tags; + // No filtering required since this function is only used for Symbols from the + // index, which have already been filtered in getSymbolTags(const NamedDecl + // &ND). + // 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 +315,41 @@ 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 (STGS & toSymbolTagBitmask(ST)) Tags.push_back(ST); } return Tags; } +std::vector getSymbolTags(const Symbol &S) { + const SymbolTags Tags = + isCXXClassMethod(S) ? filterSymbolTags(S.Tags) : S.Tags; + return expandTagBitmask(Tags); +} + +std::vector getSymbolTags(const NamedDecl &ND) { + const auto STGS = computeSymbolTags(ND); + SymbolTags FilteredTags = STGS; + std::vector Tags; + + if (STGS == 0) + return Tags; + + // Apply specific filter to the symbol tags only on CXX class methods. + if (isa(ND)) + FilteredTags = filterSymbolTags(STGS); + + // Iterate through SymbolTag enum values and collect any that are present in + // the bitmask. SymbolTag values are in the numeric range + // [FirstTag .. LastTag]. + for (SymbolTag Tag = SymbolTag::FirstTag; Tag <= SymbolTag::LastTag; + Tag = enumIncrement(Tag)) { + if (FilteredTags & toSymbolTagBitmask(Tag)) + Tags.push_back(Tag); + } + return Tags; +} + namespace { using ScoredSymbolInfo = std::pair; struct ScoredSymbolGreater { @@ -359,6 +481,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..9604940f5030e 100644 --- a/clang-tools-extra/clangd/FindSymbols.h +++ b/clang-tools-extra/clangd/FindSymbols.h @@ -12,24 +12,18 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_FINDSYMBOLS_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FINDSYMBOLS_H -#include "Protocol.h" #include "index/Symbol.h" #include "clang/AST/Decl.h" #include "llvm/ADT/StringRef.h" namespace clang { +class NamedDecl; + namespace clangd { class ParsedAST; class SymbolIndex; - -/// A bitmask type representing symbol tags supported by LSP. -/// \see -/// https://microsoft.github.io/language-server-protocol/specifications/specification-current/#symbolTag -using SymbolTags = uint32_t; -/// Ensure we have enough bits to represent all SymbolTag values. -static_assert(static_cast(SymbolTag::LastTag) <= 32, - "Too many SymbolTags to fit in uint32_t. Change to uint64_t if " - "we ever have more than 32 tags."); +struct Symbol; +struct SymbolLocation; /// Helper function for deriving an LSP Location from an index SymbolLocation. llvm::Expected indexToLSPLocation(const SymbolLocation &Loc, @@ -69,6 +63,19 @@ SymbolTags computeSymbolTags(const NamedDecl &ND); /// \p ND The declaration to get tags for. std::vector getSymbolTags(const NamedDecl &ND); +/// Returns the \c SymbolTag values for the given indexed \p S. +/// +/// Converts the bitmask stored in \c Symbol::Tags into a flat vector of +/// \c SymbolTag enum values. For C++ class methods (instance methods, static +/// methods, constructors, destructors, and conversion functions), a semantic +/// filter is applied first to remove tags that are implied by higher-priority +/// tags (e.g. \c Overrides implies \c Virtual, so \c Virtual is suppressed). +/// For all other symbol kinds the bitmask is expanded as-is. +/// +/// \param S The indexed symbol whose tags should be returned. +/// \return A vector of \c SymbolTag values present in \c S.Tags, after +/// applying any applicable filters. +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 793db7b052990..778ac70584094 100644 --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -867,6 +867,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); } @@ -1445,6 +1447,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 7a99721a1e856..baa2d659b218f 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 c0ef0131c238f..d35ac77a8494a 100644 --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -1818,8 +1818,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}; @@ -1872,6 +1873,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); + HI.tags = getSymbolTags(S); HI.selectionRange = Loc->range; // FIXME: Populate 'range' correctly // (https://github.com/clangd/clangd/issues/59). @@ -1897,8 +1899,6 @@ 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; } @@ -2144,7 +2144,7 @@ static void unwrapFindType( 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. @@ -2307,24 +2307,23 @@ getTypeHierarchy(ParsedAST &AST, Position Pos, int ResolveLevels, std::optional> superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index) { - std::vector Results; - if (!Item.data.parents) + if (!Index || !Item.data.parents) return std::nullopt; - if (Item.data.parents->empty()) - return Results; LookupRequest Req; llvm::DenseMap IDToData; for (const auto &Parent : *Item.data.parents) { Req.IDs.insert(Parent.symbolID); IDToData[Parent.symbolID] = &Parent; } + std::vector Results; Index->lookup(Req, [&Item, &Results, &IDToData](const Symbol &S) { if (auto THI = symbolToTypeHierarchyItem(S, Item.uri.file())) { THI->data = *IDToData.lookup(S.ID); Results.emplace_back(std::move(*THI)); } }); - return Results; + return Results.empty() ? std::nullopt + : std::make_optional(std::move(Results)); } std::vector subTypes(const TypeHierarchyItem &Item, diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h index 247e52314c3f9..3cc35e5dfbd2c 100644 --- a/clang-tools-extra/clangd/XRefs.h +++ b/clang-tools-extra/clangd/XRefs.h @@ -132,7 +132,8 @@ std::vector getTypeHierarchy( const SymbolIndex *Index = nullptr, PathRef TUPath = PathRef{}); /// Returns direct parents of a TypeHierarchyItem using SymbolIDs stored inside -/// the item. +/// the item. Returns nullopt if the item does not have parents or if +/// the index is not provided. std::optional> superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index); /// Returns direct children of a TypeHierarchyItem. diff --git a/clang-tools-extra/clangd/index/Merge.cpp b/clang-tools-extra/clangd/index/Merge.cpp index 625b1c6926a28..4a7caeb9ace60 100644 --- a/clang-tools-extra/clangd/index/Merge.cpp +++ b/clang-tools-extra/clangd/index/Merge.cpp @@ -319,6 +319,7 @@ Symbol mergeSymbol(const Symbol &L, const Symbol &R) { S.Origin |= O.Origin | SymbolOrigin::Merge; S.Flags |= O.Flags; + S.Tags |= O.Tags; return S; } diff --git a/clang-tools-extra/clangd/index/Serialization.cpp b/clang-tools-extra/clangd/index/Serialization.cpp index f03839599612c..764ec7903aecb 100644 --- a/clang-tools-extra/clangd/index/Serialization.cpp +++ b/clang-tools-extra/clangd/index/Serialization.cpp @@ -328,6 +328,7 @@ void writeSymbol(const Symbol &Sym, const StringTableOut &Strings, writeVar(Strings.index(Sym.Documentation), OS); writeVar(Strings.index(Sym.ReturnType), OS); writeVar(Strings.index(Sym.Type), OS); + writeVar(Sym.Tags, OS); auto WriteInclude = [&](const Symbol::IncludeHeaderWithReferences &Include) { writeVar(Strings.index(Include.IncludeHeader), OS); @@ -357,6 +358,7 @@ Symbol readSymbol(Reader &Data, llvm::ArrayRef Strings, Sym.Documentation = Data.consumeString(Strings); Sym.ReturnType = Data.consumeString(Strings); Sym.Type = Data.consumeString(Strings); + Sym.Tags = Data.consumeVar(); if (!Data.consumeSize(Sym.IncludeHeaders)) return Sym; for (auto &I : Sym.IncludeHeaders) { @@ -457,7 +459,7 @@ readCompileCommand(Reader CmdReader, llvm::ArrayRef Strings) { // The current versioning scheme is simple - non-current versions are rejected. // If you make a breaking change, bump this version number to invalidate stored // data. Later we may want to support some backward compatibility. -constexpr static uint32_t Version = 20; +constexpr static uint32_t Version = 21; llvm::Expected readRIFF(llvm::StringRef Data, SymbolOrigin Origin) { diff --git a/clang-tools-extra/clangd/index/Symbol.h b/clang-tools-extra/clangd/index/Symbol.h index 62c47ddfc5758..5f83953639a55 100644 --- a/clang-tools-extra/clangd/index/Symbol.h +++ b/clang-tools-extra/clangd/index/Symbol.h @@ -9,6 +9,7 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_SYMBOL_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_SYMBOL_H +#include "Protocol.h" #include "index/SymbolID.h" #include "index/SymbolLocation.h" #include "index/SymbolOrigin.h" @@ -22,6 +23,15 @@ namespace clangd { LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE(); +/// A bitmask type representing symbol tags supported by LSP. +/// \see +/// https://microsoft.github.io/language-server-protocol/specifications/specification-current/#symbolTag +using SymbolTags = uint32_t; +/// Ensure we have enough bits to represent all SymbolTag values. +static_assert(static_cast(SymbolTag::LastTag) < 32, + "Too many SymbolTags to fit in uint32_t. Change to uint64_t if " + "we ever have more than 32 tags."); + /// The class presents a C++ symbol, e.g. class, function. /// /// WARNING: Symbols do not own much of their underlying data - typically @@ -41,6 +51,8 @@ struct Symbol { SymbolID ID; /// The symbol information, like symbol kind. index::SymbolInfo SymInfo = index::SymbolInfo(); + /// Where this symbol came from. Usually an index provides a constant value. + SymbolOrigin Origin = SymbolOrigin::Unknown; /// The unqualified name of the symbol, e.g. "bar" (for ns::bar). llvm::StringRef Name; /// The containing namespace. e.g. "" (global), "ns::" (top-level namespace). @@ -60,8 +72,10 @@ struct Symbol { /// The number of translation units that reference this symbol from their main /// file. This number is only meaningful if aggregated in an index. unsigned References = 0; - /// Where this symbol came from. Usually an index provides a constant value. - SymbolOrigin Origin = SymbolOrigin::Unknown; + /// Symbol tags for LSP protocol (Deprecated, Static, Virtual, Abstract, + /// Final, ReadOnly, Public, Protected, Private, Declaration, Definition). + /// This is a bitmask where each bit represents a SymbolTag. + SymbolTags Tags = 0; /// A brief description of the symbol that can be appended in the completion /// candidate list. For example, "(X x, Y y) const" is a function signature. /// Only set when the symbol is indexed for completion. diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index bd974e4c18818..a7c5f35f3aea9 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -11,6 +11,7 @@ #include "CodeComplete.h" #include "CodeCompletionStrings.h" #include "ExpectedTypes.h" +#include "FindSymbols.h" #include "SourceCode.h" #include "URI.h" #include "clang-include-cleaner/Analysis.h" @@ -1102,6 +1103,8 @@ const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND, SymbolID ID, if (ND.getAvailability() == AR_Deprecated) S.Flags |= Symbol::Deprecated; + S.Tags = computeSymbolTags(ND); + // Add completion info. // FIXME: we may want to choose a different redecl, or combine from several. assert(ASTCtx && PP && "ASTContext and Preprocessor must be set."); @@ -1192,7 +1195,7 @@ void SymbolCollector::addDefinition(const NamedDecl &ND, const Symbol &DeclSym, S.Flags |= Symbol::HasDocComment; S.Documentation = Documentation; } - + S.Tags |= computeSymbolTags(ND); Symbols.insert(S); } diff --git a/clang-tools-extra/clangd/index/YAMLSerialization.cpp b/clang-tools-extra/clangd/index/YAMLSerialization.cpp index 495d8a2ff487a..4d6b5822ece72 100644 --- a/clang-tools-extra/clangd/index/YAMLSerialization.cpp +++ b/clang-tools-extra/clangd/index/YAMLSerialization.cpp @@ -246,6 +246,7 @@ template <> struct MappingTraits { IO.mapOptional("Documentation", Sym.Documentation); IO.mapOptional("ReturnType", Sym.ReturnType); IO.mapOptional("Type", Sym.Type); + IO.mapOptional("Tags", Sym.Tags); IO.mapOptional("IncludeHeaders", NIncludeHeaders->Headers); } }; diff --git a/clang-tools-extra/clangd/test/Inputs/symbols.test.yaml b/clang-tools-extra/clangd/test/Inputs/symbols.test.yaml index 6d457ad8ad498..4ddc5d9c2ea30 100644 --- a/clang-tools-extra/clangd/test/Inputs/symbols.test.yaml +++ b/clang-tools-extra/clangd/test/Inputs/symbols.test.yaml @@ -6,7 +6,8 @@ Scope: 'std::' SymInfo: Kind: Class Lang: Cpp -CanonicalDeclaration: +Tags: 262144 +CanonicalDeclaration: FileURI: 'test:///vector.h' Start: Line: 215 diff --git a/clang-tools-extra/clangd/test/call-hierarchy.test b/clang-tools-extra/clangd/test/call-hierarchy.test index f0d57b60421a4..270b553302ce1 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/index-serialization/Inputs/sample.idx b/clang-tools-extra/clangd/test/index-serialization/Inputs/sample.idx index 6368e7145b1e4..6a2ce7a87925f 100644 Binary files a/clang-tools-extra/clangd/test/index-serialization/Inputs/sample.idx and b/clang-tools-extra/clangd/test/index-serialization/Inputs/sample.idx differ 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..983c7538088bf 100644 --- a/clang-tools-extra/clangd/test/type-hierarchy-ext.test +++ b/clang-tools-extra/clangd/test/type-hierarchy-ext.test @@ -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": [], diff --git a/clang-tools-extra/clangd/test/type-hierarchy.test b/clang-tools-extra/clangd/test/type-hierarchy.test index a5f13ab13d0b3..c6079bc88662a 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: ] @@ -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: ] @@ -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..b646ac4ce8515 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); @@ -728,6 +734,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()); + 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()); + 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 2d237429ebfbf..2636e583b5b09 100644 --- a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp +++ b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp @@ -1160,12 +1160,140 @@ 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::Declaration, + SymbolTag::Abstract)))), + AllOf(withName("B"), + children(AllOf(withName("f2"), + withSymbolTags(SymbolTag::Public, + SymbolTag::Declaration, + 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::Declaration, + SymbolTag::Definition, SymbolTag::Virtual)))), + AllOf(withName("B"), + children(AllOf(withName("f1"), + withSymbolTags(SymbolTag::Public, + SymbolTag::Declaration, + SymbolTag::Definition, + SymbolTag::Overrides)))), + AllOf(withName("C"), + children(AllOf(withName("f1"), + withSymbolTags(SymbolTag::Public, + SymbolTag::Declaration, + SymbolTag::Definition, + 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::Declaration, + SymbolTag::Abstract)))), + AllOf(withName("B"), + children(AllOf(withName("f1"), + withSymbolTags(SymbolTag::Public, + SymbolTag::Declaration, + SymbolTag::Definition, + SymbolTag::Implements)))), + AllOf(withName("C"), + children(AllOf(withName("f1"), + withSymbolTags(SymbolTag::Public, + SymbolTag::Declaration, + SymbolTag::Definition, + SymbolTag::Overrides)))), + AllOf(withName("D"), + children(AllOf(withName("f1"), + withSymbolTags(SymbolTag::Public, + SymbolTag::Declaration, + SymbolTag::Definition, + 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: @@ -1174,9 +1302,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 {} }; @@ -1188,18 +1316,17 @@ TEST(DocumentSymbolsTest, SymbolTags) { Symbols, UnorderedElementsAre( AllOf( - withName("AbstractClass"), + withName("A"), withSymbolTags(SymbolTag::Abstract, SymbolTag::Declaration, SymbolTag::Definition), children( - AllOf(withName("~AbstractClass"), + AllOf(withName("~A"), withSymbolTags(SymbolTag::Public, SymbolTag::Virtual, SymbolTag::Declaration, SymbolTag::Definition)), - AllOf(withName("f1"), - withSymbolTags(SymbolTag::Public, SymbolTag::Abstract, - SymbolTag::Virtual, - SymbolTag::Declaration)), + AllOf(withName("f1"), withSymbolTags(SymbolTag::Public, + SymbolTag::Declaration, + SymbolTag::Abstract)), AllOf(withName("f2"), withSymbolTags(SymbolTag::Public, SymbolTag::Declaration, SymbolTag::ReadOnly)), @@ -1210,17 +1337,17 @@ 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"), + AllOf(withName("B"), withSymbolTags(SymbolTag::Final, SymbolTag::Declaration, SymbolTag::Definition), children(AllOf( withName("f1"), - withSymbolTags(SymbolTag::Public, SymbolTag::Final, - SymbolTag::Virtual, SymbolTag::Declaration, - SymbolTag::Definition)))))); + withSymbolTags(SymbolTag::Public, SymbolTag::Declaration, + SymbolTag::Definition, SymbolTag::Final, + SymbolTag::Implements)))))); } } // namespace diff --git a/clang-tools-extra/clangd/unittests/SerializationTests.cpp b/clang-tools-extra/clangd/unittests/SerializationTests.cpp index d18ae478c1653..6b95331c8f7c5 100644 --- a/clang-tools-extra/clangd/unittests/SerializationTests.cpp +++ b/clang-tools-extra/clangd/unittests/SerializationTests.cpp @@ -49,6 +49,7 @@ Scope: 'clang::' Line: 1 Column: 1 Flags: 129 +Tags: 258 Documentation: 'Foo doc' ReturnType: 'int' IncludeHeaders: @@ -160,6 +161,10 @@ TEST(SerializationTest, YAMLConversions) { EXPECT_EQ(static_cast(Sym1.Flags), 129); EXPECT_TRUE(Sym1.Flags & Symbol::IndexedForCodeCompletion); EXPECT_FALSE(Sym1.Flags & Symbol::Deprecated); + // Tags: Deprecated (1<<1=2) | Static (1<<8=256) = 258 + EXPECT_EQ(Sym1.Tags, 258u); + EXPECT_TRUE(Sym1.Tags & (1u << static_cast(SymbolTag::Deprecated))); + EXPECT_TRUE(Sym1.Tags & (1u << static_cast(SymbolTag::Static))); EXPECT_THAT( Sym1.IncludeHeaders, UnorderedElementsAre( @@ -176,6 +181,7 @@ TEST(SerializationTest, YAMLConversions) { "file:///path/bar.h"); EXPECT_FALSE(Sym2.Flags & Symbol::IndexedForCodeCompletion); EXPECT_TRUE(Sym2.Flags & Symbol::Deprecated); + EXPECT_EQ(Sym2.Tags, 0u); // no Tags in YAML → default zero ASSERT_TRUE(bool(ParsedYAML->Refs)); EXPECT_THAT( @@ -292,6 +298,109 @@ TEST(SerializationTest, SrcsTest) { } } +// Verify that Symbol::Tags survive a full YAML -> RIFF -> read roundtrip. +// This exercises both writeSymbol (new Tags field) and readSymbol. +TEST(SerializationTest, TagsRoundTrip) { + // YAML fixture with a single symbol carrying non-zero Tags. + // Deprecated (SymbolTag=1) -> bit 1 -> 1<<1 = 2 + // Static (SymbolTag=8) -> bit 8 -> 1<<8 = 256 + // Combined: 258 + const char *TaggedYAML = R"( +--- +!Symbol +ID: AABBCCDDEEFF0011 +Name: 'Tagged' +Scope: 'ns::' +SymInfo: + Kind: Function + Lang: Cpp +CanonicalDeclaration: + FileURI: file:///tmp/tagged.h + Start: + Line: 0 + Column: 0 + End: + Line: 0 + Column: 1 +Flags: 1 +Tags: 258 +... +)"; + const SymbolID TaggedID = cantFail(SymbolID::fromStr("AABBCCDDEEFF0011")); + constexpr SymbolTags ExpectedTags = + (1u << static_cast(SymbolTag::Deprecated)) | // bit 1 = 2 + (1u << static_cast(SymbolTag::Static)); // bit 8 = 256 + static_assert(ExpectedTags == 258u, "bitmask sanity check"); + + // ── Step 1: YAML deserialization ──────────────────────────────────────── + auto FromYAML = readIndexFile(TaggedYAML); + ASSERT_TRUE(bool(FromYAML)) << FromYAML.takeError(); + ASSERT_TRUE(bool(FromYAML->Symbols)); + { + auto It = FromYAML->Symbols->find(TaggedID); + ASSERT_NE(It, FromYAML->Symbols->end()) + << "symbol not found after YAML parse"; + EXPECT_EQ(It->Tags, ExpectedTags) + << "Tags lost during YAML deserialization"; + } + + // ── Step 2: RIFF serialization + deserialization ──────────────────────── + IndexFileOut Out(*FromYAML); + Out.Format = IndexFileFormat::RIFF; + std::string Serialized = llvm::to_string(Out); + + auto FromRIFF = readIndexFile(Serialized); + ASSERT_TRUE(bool(FromRIFF)) << FromRIFF.takeError(); + ASSERT_TRUE(bool(FromRIFF->Symbols)); + { + auto It = FromRIFF->Symbols->find(TaggedID); + ASSERT_NE(It, FromRIFF->Symbols->end()) + << "symbol not found after RIFF roundtrip"; + EXPECT_EQ(It->Tags, ExpectedTags) << "Tags lost during RIFF serialization"; + // Spot-check individual bits + EXPECT_TRUE(It->Tags & + (1u << static_cast(SymbolTag::Deprecated))); + EXPECT_TRUE(It->Tags & (1u << static_cast(SymbolTag::Static))); + EXPECT_FALSE(It->Tags & (1u << static_cast(SymbolTag::Abstract))); + } + + // ── Step 3: Symbol with Tags=0 must survive too ───────────────────────── + const char *UntaggedYAML = R"( +--- +!Symbol +ID: 0000000000000001 +Name: 'Untagged' +Scope: '' +SymInfo: + Kind: Variable + Lang: Cpp +CanonicalDeclaration: + FileURI: file:///tmp/untagged.h + Start: + Line: 0 + Column: 0 + End: + Line: 0 + Column: 1 +Flags: 1 +... +)"; + auto FromUntaggedYAML = readIndexFile(UntaggedYAML); + ASSERT_TRUE(bool(FromUntaggedYAML)) << FromUntaggedYAML.takeError(); + ASSERT_TRUE(bool(FromUntaggedYAML->Symbols)); + IndexFileOut Out2(*FromUntaggedYAML); + Out2.Format = IndexFileFormat::RIFF; + auto FromUntaggedRIFF = readIndexFile(llvm::to_string(Out2)); + ASSERT_TRUE(bool(FromUntaggedRIFF)) << FromUntaggedRIFF.takeError(); + ASSERT_TRUE(bool(FromUntaggedRIFF->Symbols)); + { + const SymbolID UntaggedID = cantFail(SymbolID::fromStr("0000000000000001")); + auto It = FromUntaggedRIFF->Symbols->find(UntaggedID); + ASSERT_NE(It, FromUntaggedRIFF->Symbols->end()); + EXPECT_EQ(It->Tags, 0u) << "Tags must be zero for untagged symbol"; + } +} + TEST(SerializationTest, CmdlTest) { auto In = readIndexFile(YAML); EXPECT_TRUE(bool(In)) << In.takeError(); diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 94116fca3cbb2..f0884e2a545a1 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "Annotations.h" +#include "FindSymbols.h" #include "TestFS.h" #include "TestTU.h" #include "URI.h" @@ -1335,6 +1336,47 @@ TEST_F(SymbolCollectorTest, OverrideRelationsMultipleInheritance) { OverriddenBy(CBar, DBar), OverriddenBy(CBaz, DBaz))); } +TEST_F(SymbolCollectorTest, SymbolTagsWithIndexing) { + // Test that verifies symbol tags are correctly set when the AST is indexed + // through FileIndex, which triggers the full indexing path through + // SymbolCollector::addDeclaration where S.Tags = computeSymbolTags(ND) + std::string Header = R"cpp( + class A { + public: + virtual ~A() = default; + virtual void f1() = 0; + void f2() const; + protected: + void f3(){} + private: + static void f4(){} + }; + + void A::f2() const {} + + class B final: public A { + public: + void f1() final {} + }; + )cpp"; + + runSymbolCollector(Header, /*Main=*/""); + const Symbol &A = findSymbol(Symbols, "A"); + EXPECT_THAT(getSymbolTags(A), + UnorderedElementsAre(SymbolTag::Abstract, SymbolTag::Declaration, + SymbolTag::Definition)); + + const Symbol &B = findSymbol(Symbols, "B"); + EXPECT_THAT(getSymbolTags(B), + UnorderedElementsAre(SymbolTag::Final, SymbolTag::Declaration, + SymbolTag::Definition)); + const Symbol &Bf1 = findSymbol(Symbols, "B::f1"); + EXPECT_THAT(getSymbolTags(Bf1), + UnorderedElementsAre( + SymbolTag::Public, SymbolTag::Final, SymbolTag::Declaration, + SymbolTag::Definition, SymbolTag::Implements)); +} + TEST_F(SymbolCollectorTest, ObjCOverrideRelationsSimpleInheritance) { std::string Header = R"cpp( @interface A diff --git a/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp b/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp index 406a842f5a008..754063ede2724 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 { @@ -772,7 +778,7 @@ TEST(Standard, SubTypes) { Annotations Source(R"cpp( struct Pare^nt1 {}; struct Parent2 {}; -struct Child : Parent1, Parent2 {}; +struct Child final: Parent1, Parent2 {}; )cpp"); TestTU TU = TestTU::withCode(Source.code()); @@ -791,6 +797,8 @@ 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()))))))); } @@ -811,9 +819,11 @@ struct Chil^d : Parent {}; ASSERT_THAT(Result, SizeIs(1)); auto Parents = superTypes(Result.front(), Index.get()); - 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