Index: clangd/index/FileIndex.cpp =================================================================== --- clangd/index/FileIndex.cpp +++ clangd/index/FileIndex.cpp @@ -19,16 +19,18 @@ SymbolSlab indexAST(ASTContext &AST, std::shared_ptr PP, llvm::ArrayRef URISchemes) { SymbolCollector::Options CollectorOpts; + SymbolCollector::Options::CollectSymbolOptions SymbolOpts; // FIXME(ioeric): we might also want to collect include headers. We would need // to make sure all includes are canonicalized (with CanonicalIncludes), which // is not trivial given the current way of collecting symbols: we only have // AST at this point, but we also need preprocessor callbacks (e.g. // CommentHandler for IWYU pragma) to canonicalize includes. - CollectorOpts.CollectIncludePath = false; - CollectorOpts.CountReferences = false; + SymbolOpts.CollectIncludePath = false; + SymbolOpts.CountReferences = false; if (!URISchemes.empty()) CollectorOpts.URISchemes = URISchemes; - CollectorOpts.Origin = SymbolOrigin::Dynamic; + SymbolOpts.Origin = SymbolOrigin::Dynamic; + CollectorOpts.SymOpts = &SymbolOpts; SymbolCollector Collector(std::move(CollectorOpts)); Collector.setPreprocessor(PP); Index: clangd/index/Index.h =================================================================== --- clangd/index/Index.h +++ clangd/index/Index.h @@ -31,9 +31,6 @@ uint32_t Line = 0; // 0-based // Using UTF-16 code units. uint32_t Column = 0; // 0-based - bool operator==(const Position& P) const { - return Line == P.Line && Column == P.Column; - } }; // The URI of the source file where a symbol occurs. @@ -44,11 +41,23 @@ Position End; explicit operator bool() const { return !FileURI.empty(); } - bool operator==(const SymbolLocation& Loc) const { - return std::tie(FileURI, Start, End) == - std::tie(Loc.FileURI, Loc.Start, Loc.End); - } }; +inline bool operator==(const SymbolLocation::Position &L, + const SymbolLocation::Position &R) { + return std::tie(L.Line, L.Column) == std::tie(R.Line, R.Column); +} +inline bool operator<(const SymbolLocation::Position &L, + const SymbolLocation::Position &R) { + return std::tie(L.Line, L.Column) < std::tie(R.Line, R.Column); +} +inline bool operator==(const SymbolLocation &L, const SymbolLocation &R) { + return std::tie(L.FileURI, L.Start, L.End) == + std::tie(R.FileURI, R.Start, R.End); +} +inline bool operator<(const SymbolLocation &L, const SymbolLocation &R) { + return std::tie(L.FileURI, L.Start, L.End) < + std::tie(R.FileURI, R.Start, R.End); +} llvm::raw_ostream &operator<<(llvm::raw_ostream &, const SymbolLocation &); // The class identifies a particular C++ symbol (class, function, method, etc). @@ -234,6 +243,49 @@ // and signals -> score, so it can be reused for Sema completions. double quality(const Symbol &S); +// Describes the kind of a symbol occurrence. +// +// This is a bitfield which can be combined from different kinds. +enum class SymbolOccurrenceKind : uint8_t { + Unknown = 0, + Declaration = static_cast(index::SymbolRole::Declaration), + Definition = static_cast(index::SymbolRole::Definition), + Reference = static_cast(index::SymbolRole::Reference), +}; +raw_ostream &operator<<(raw_ostream &OS, SymbolOccurrenceKind K); +inline SymbolOccurrenceKind operator|(SymbolOccurrenceKind L, + SymbolOccurrenceKind R) { + return static_cast(static_cast(L) | + static_cast(R)); +} +inline SymbolOccurrenceKind &operator|=(SymbolOccurrenceKind &L, + SymbolOccurrenceKind R) { + return L = L | R; +} +inline SymbolOccurrenceKind operator&(SymbolOccurrenceKind A, + SymbolOccurrenceKind B) { + return static_cast(static_cast(A) & + static_cast(B)); +} + +// Represents a symbol occurrence in the source file. It could be a +// declaration/definition/reference occurrence. +// +// WARNING: Location does not own the underlying data - Copies are shallow. +struct SymbolOccurrence { + // The location of the occurrence. + SymbolLocation Location; + SymbolOccurrenceKind Kind = SymbolOccurrenceKind::Unknown; +}; +inline bool operator<(const SymbolOccurrence &L, const SymbolOccurrence &R) { + return std::tie(L.Location, L.Kind) < std::tie(R.Location, R.Kind); +} +inline bool operator==(const SymbolOccurrence &L, const SymbolOccurrence &R) { + return std::tie(L.Location, L.Kind) == std::tie(R.Location, R.Kind); +} +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const SymbolOccurrence &Occurrence); + // An immutable symbol container that stores a set of symbols. // The container will maintain the lifetime of the symbols. class SymbolSlab { @@ -251,7 +303,15 @@ // Estimates the total memory usage. size_t bytes() const { return sizeof(*this) + Arena.getTotalMemory() + - Symbols.capacity() * sizeof(Symbol); + Symbols.capacity() * sizeof(Symbol) + + SymbolOccurrences.getMemorySize(); + } + + llvm::ArrayRef findOccurrences(const SymbolID &ID) const { + auto It = SymbolOccurrences.find(ID); + if (It == SymbolOccurrences.end()) + return {}; + return It->second; } // SymbolSlab::Builder is a mutable container that can 'freeze' to SymbolSlab. @@ -264,6 +324,10 @@ // This is a deep copy: underlying strings will be owned by the slab. void insert(const Symbol &S); + // Adds a symbol occurrence. + // This is a deep copy: underlying strings will be owned by the slab. + void insert(const SymbolID &ID, SymbolOccurrence Occurrence); + // Returns the symbol with an ID, if it exists. Valid until next insert(). const Symbol *find(const SymbolID &ID) { auto I = SymbolIndex.find(ID); @@ -280,48 +344,19 @@ std::vector Symbols; // Values are indices into Symbols vector. llvm::DenseMap SymbolIndex; + llvm::DenseMap> SymbolOccurrences; }; private: - SymbolSlab(llvm::BumpPtrAllocator Arena, std::vector Symbols) - : Arena(std::move(Arena)), Symbols(std::move(Symbols)) {} + SymbolSlab( + llvm::BumpPtrAllocator Arena, std::vector Symbols, + llvm::DenseMap> SymbolOccurrences) + : Arena(std::move(Arena)), Symbols(std::move(Symbols)), + SymbolOccurrences(std::move(SymbolOccurrences)) {} llvm::BumpPtrAllocator Arena; // Owns Symbol data that the Symbols do not. std::vector Symbols; // Sorted by SymbolID to allow lookup. -}; - -// Describes the kind of a symbol occurrence. -// -// This is a bitfield which can be combined from different kinds. -enum class SymbolOccurrenceKind : uint8_t { - Unknown = 0, - Declaration = static_cast(index::SymbolRole::Declaration), - Definition = static_cast(index::SymbolRole::Definition), - Reference = static_cast(index::SymbolRole::Reference), -}; -inline SymbolOccurrenceKind operator|(SymbolOccurrenceKind L, - SymbolOccurrenceKind R) { - return static_cast(static_cast(L) | - static_cast(R)); -} -inline SymbolOccurrenceKind &operator|=(SymbolOccurrenceKind &L, - SymbolOccurrenceKind R) { - return L = L | R; -} -inline SymbolOccurrenceKind operator&(SymbolOccurrenceKind A, - SymbolOccurrenceKind B) { - return static_cast(static_cast(A) & - static_cast(B)); -} - -// Represents a symbol occurrence in the source file. It could be a -// declaration/definition/reference occurrence. -// -// WARNING: Location does not own the underlying data - Copies are shallow. -struct SymbolOccurrence { - // The location of the occurrence. - SymbolLocation Location; - SymbolOccurrenceKind Kind = SymbolOccurrenceKind::Unknown; + llvm::DenseMap> SymbolOccurrences; }; struct FuzzyFindRequest { Index: clangd/index/Index.cpp =================================================================== --- clangd/index/Index.cpp +++ clangd/index/Index.cpp @@ -114,6 +114,11 @@ own(Copy, UniqueStrings, Arena); } } +void SymbolSlab::Builder::insert(const SymbolID &ID, + SymbolOccurrence Occurrence) { + Occurrence.Location.FileURI = UniqueStrings.save(Occurrence.Location.FileURI); + SymbolOccurrences[ID].push_back(std::move(Occurrence)); +} SymbolSlab SymbolSlab::Builder::build() && { Symbols = {Symbols.begin(), Symbols.end()}; // Force shrink-to-fit. @@ -125,7 +130,47 @@ llvm::UniqueStringSaver Strings(NewArena); for (auto &S : Symbols) own(S, Strings, NewArena); - return SymbolSlab(std::move(NewArena), std::move(Symbols)); + + // We may have duplicated symbol occurrences (as some AST nodes have been + // visited multiple times). Deduplicate them. + for (auto &IDAndOccurrences : SymbolOccurrences) { + auto &Occurrences = IDAndOccurrences.getSecond(); + std::sort(Occurrences.begin(), Occurrences.end()); + Occurrences.erase(std::unique(Occurrences.begin(), Occurrences.end()), + Occurrences.end()); + + for (auto &O : Occurrences) + O.Location.FileURI = UniqueStrings.save(O.Location.FileURI); + } + + return SymbolSlab(std::move(NewArena), std::move(Symbols), + std::move(SymbolOccurrences)); +} + +raw_ostream &operator<<(raw_ostream &OS, SymbolOccurrenceKind K) { + if (K == SymbolOccurrenceKind::Unknown) + return OS << "Unknown"; + static const std::vector Messages = { + "Declaration", + "Definition", + "Reference" + }; + bool VisitedOnce = false; + for (unsigned I = 0; I < Messages.size(); ++I) { + if (static_cast(K) & 1u << I) { + if (VisitedOnce) + OS << ", "; + OS << Messages[I]; + VisitedOnce = true; + } + } + return OS; +} + +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const SymbolOccurrence &Occurrence) { + OS << Occurrence.Location << ":" << Occurrence.Kind; + return OS; } } // namespace clangd Index: clangd/index/SymbolCollector.h =================================================================== --- clangd/index/SymbolCollector.h +++ clangd/index/SymbolCollector.h @@ -37,42 +37,56 @@ class SymbolCollector : public index::IndexDataConsumer { public: struct Options { + struct CollectSymbolOptions { + bool CollectIncludePath = false; + /// If set, this is used to map symbol #include path to a potentially + /// different #include path. + const CanonicalIncludes *Includes = nullptr; + // Populate the Symbol.References field. + bool CountReferences = false; + // Every symbol collected will be stamped with this origin. + SymbolOrigin Origin = SymbolOrigin::Unknown; + /// Collect macros. + /// Note that SymbolCollector must be run with preprocessor in order to + /// collect macros. For example, `indexTopLevelDecls` will not index any + /// macro even if this is true. + bool CollectMacro = false; + }; + struct CollectOccurrenceOptions { + SymbolOccurrenceKind Filter; + // A whitelist symbols which will be collected. + // If none, all symbol occurrences will be collected. + llvm::Optional> IDs = llvm::None; + }; + + /// Specifies URI schemes that can be used to generate URIs for file paths + /// in symbols. The list of schemes will be tried in order until a working + /// scheme is found. If no scheme works, symbol location will be dropped. + std::vector URISchemes = {"file"}; + /// When symbol paths cannot be resolved to absolute paths (e.g. files in /// VFS that does not have absolute path), combine the fallback directory /// with symbols' paths to get absolute paths. This must be an absolute /// path. std::string FallbackDir; - /// Specifies URI schemes that can be used to generate URIs for file paths - /// in symbols. The list of schemes will be tried in order until a working - /// scheme is found. If no scheme works, symbol location will be dropped. - std::vector URISchemes = {"file"}; - bool CollectIncludePath = false; - /// If set, this is used to map symbol #include path to a potentially - /// different #include path. - const CanonicalIncludes *Includes = nullptr; - // Populate the Symbol.References field. - bool CountReferences = false; - // Every symbol collected will be stamped with this origin. - SymbolOrigin Origin = SymbolOrigin::Unknown; - /// Collect macros. - /// Note that SymbolCollector must be run with preprocessor in order to - /// collect macros. For example, `indexTopLevelDecls` will not index any - /// macro even if this is true. - bool CollectMacro = false; + + // If not null, SymbolCollector will collect symbols. + const CollectSymbolOptions *SymOpts; + // If not null, SymbolCollector will collect symbol occurrences. + const CollectOccurrenceOptions *OccurrenceOpts; }; SymbolCollector(Options Opts); + ~SymbolCollector(); + /// Returns true is \p ND should be collected. /// AST matchers require non-const ASTContext. - static bool shouldCollectSymbol(const NamedDecl &ND, ASTContext &ASTCtx, - const Options &Opts); + static bool shouldCollectSymbol(const NamedDecl &ND, ASTContext &ASTCtx); void initialize(ASTContext &Ctx) override; - void setPreprocessor(std::shared_ptr PP) override { - this->PP = std::move(PP); - } + void setPreprocessor(std::shared_ptr PP) override; bool handleDeclOccurence(const Decl *D, index::SymbolRoleSet Roles, @@ -89,25 +103,16 @@ void finish() override; private: - const Symbol *addDeclaration(const NamedDecl &, SymbolID); - void addDefinition(const NamedDecl &, const Symbol &DeclSymbol); + Options Opts; - // All Symbols collected from the AST. - SymbolSlab::Builder Symbols; - ASTContext *ASTCtx; std::shared_ptr PP; - std::shared_ptr CompletionAllocator; - std::unique_ptr CompletionTUInfo; - Options Opts; - // Symbols referenced from the current TU, flushed on finish(). - llvm::DenseSet ReferencedDecls; - llvm::DenseSet ReferencedMacros; - // Maps canonical declaration provided by clang to canonical declaration for - // an index symbol, if clangd prefers a different declaration than that - // provided by clang. For example, friend declaration might be considered - // canonical by clang but should not be considered canonical in the index - // unless it's a definition. - llvm::DenseMap CanonicalDecls; + + class CollectSymbol; + class CollectOccurrence; + std::unique_ptr CollectSym; + std::unique_ptr CollectOccur; + // All symbols and symbol occurrences collected from the AST. + SymbolSlab::Builder Symbols; }; } // namespace clangd Index: clangd/index/SymbolCollector.cpp =================================================================== --- clangd/index/SymbolCollector.cpp +++ clangd/index/SymbolCollector.cpp @@ -174,8 +174,9 @@ if (Headers.empty()) return llvm::None; llvm::StringRef Header = Headers[0]; - if (Opts.Includes) { - Header = Opts.Includes->mapHeader(Headers, QName); + assert(Opts.SymOpts && "SymbolOptions must be set."); + if (Opts.SymOpts->Includes) { + Header = Opts.SymOpts->Includes->mapHeader(Headers, QName); if (Header.startswith("<") || Header.startswith("\"")) return Header.str(); } @@ -224,20 +225,144 @@ match(decl(isExpansionInMainFile()), ND, ND.getASTContext()).empty(); } +SymbolOccurrenceKind ToOccurrenceKind(index::SymbolRoleSet Roles) { + SymbolOccurrenceKind Kind; + for (auto Mask : + {SymbolOccurrenceKind::Declaration, SymbolOccurrenceKind::Definition, + SymbolOccurrenceKind::Reference}) { + if (Roles & static_cast(Mask)) + Kind |= Mask; + } + return Kind; +} + } // namespace -SymbolCollector::SymbolCollector(Options Opts) : Opts(std::move(Opts)) {} +class SymbolCollector::CollectOccurrence { +public: + CollectOccurrence(const SymbolCollector::Options &CollectorOpts, + SymbolSlab::Builder *Builder) + : Opts(CollectorOpts), Builder(Builder) { + assert(Opts.OccurrenceOpts && "Occurrence options must be set."); + } + + void initialize(ASTContext &Ctx) { ASTCtx = &Ctx; } + + void collectDecl(const Decl *D, index::SymbolRoleSet Roles, + ArrayRef Relations, + SourceLocation Loc, + index::IndexDataConsumer::ASTNodeInfo ASTNode) { + assert(ASTCtx && "ASTContext must be set."); + if (D->isImplicit()) + return; + + // We only collect symbol occurrences in current main file. + if (!ASTCtx->getSourceManager().isInMainFile(Loc)) + return; + std::string FileURI; + auto AddOccurrence = [&](SourceLocation L, const SymbolID &ID) { + if (auto Location = + getTokenLocation(Loc, ASTCtx->getSourceManager(), Opts, + ASTCtx->getLangOpts(), FileURI)) { + SymbolOccurrence Occurrence; + Occurrence.Location = *Location; + Occurrence.Kind = ToOccurrenceKind(Roles); + Builder->insert(ID, Occurrence); + } + }; + if (!(static_cast(Opts.OccurrenceOpts->Filter) & Roles)) + return; + + if (auto ID = getSymbolID(D)) { + if (!Opts.OccurrenceOpts->IDs || + llvm::is_contained(*Opts.OccurrenceOpts->IDs, *ID)) + AddOccurrence(Loc, *ID); + } + } + +private: + const SymbolCollector::Options &Opts; + + SymbolSlab::Builder *Builder; + ASTContext *ASTCtx; +}; + +class SymbolCollector::CollectSymbol { +public: + CollectSymbol(const SymbolCollector::Options &CollectorOpts, + SymbolSlab::Builder *Builder) + : Opts(CollectorOpts), Symbols(Builder) { + assert(Opts.SymOpts && "Symbol option must be set."); + } + + void collectDecl(const Decl *D, index::SymbolRoleSet Roles, + ArrayRef Relations, + SourceLocation Loc, + index::IndexDataConsumer::ASTNodeInfo ASTNode); + + void collectMacro(const IdentifierInfo *Name, const MacroInfo *MI, + index::SymbolRoleSet Roles, SourceLocation Loc); + + void initialize(ASTContext &Ctx) { + ASTCtx = &Ctx; + CompletionAllocator = std::make_shared(); + CompletionTUInfo = + llvm::make_unique(CompletionAllocator); + } + + void setPreprocessor(std::shared_ptr PP) { + this->PP = std::move(PP); + } + + void finish(); + +private: + const Symbol *addDeclaration(const NamedDecl &, SymbolID); + void addDefinition(const NamedDecl &ND, const Symbol &DeclSym); + + const SymbolCollector::Options &Opts; + + SymbolSlab::Builder *Symbols; + ASTContext *ASTCtx; + + std::shared_ptr PP; + std::shared_ptr CompletionAllocator; + std::unique_ptr CompletionTUInfo; + // Symbols referenced from the current TU, flushed on finish(). + llvm::DenseSet ReferencedDecls; + llvm::DenseSet ReferencedMacros; + // Maps canonical declaration provided by clang to canonical declaration for + // an index symbol, if clangd prefers a different declaration than that + // provided by clang. For example, friend declaration might be considered + // canonical by clang but should not be considered canonical in the index + // unless it's a definition. + llvm::DenseMap CanonicalDecls; +}; + +SymbolCollector::SymbolCollector(Options CollectorOpts) + : Opts(std::move(CollectorOpts)) { + if (this->Opts.SymOpts) + CollectSym = llvm::make_unique(Opts, &Symbols); + if (this->Opts.OccurrenceOpts) + CollectOccur = llvm::make_unique(Opts, &Symbols); +} + +SymbolCollector::~SymbolCollector() {} void SymbolCollector::initialize(ASTContext &Ctx) { - ASTCtx = &Ctx; - CompletionAllocator = std::make_shared(); - CompletionTUInfo = - llvm::make_unique(CompletionAllocator); + if (CollectSym) + CollectSym->initialize(Ctx); + if (CollectOccur) + CollectOccur->initialize(Ctx); +} + +void SymbolCollector::setPreprocessor(std::shared_ptr PP) { + if (CollectSym) + CollectSym->setPreprocessor(PP); } bool SymbolCollector::shouldCollectSymbol(const NamedDecl &ND, - ASTContext &ASTCtx, - const Options &Opts) { + ASTContext &ASTCtx) { using namespace clang::ast_matchers; if (ND.isImplicit()) return false; @@ -282,7 +407,7 @@ } // Always return true to continue indexing. -bool SymbolCollector::handleDeclOccurence( +void SymbolCollector::CollectSymbol::collectDecl( const Decl *D, index::SymbolRoleSet Roles, ArrayRef Relations, SourceLocation Loc, index::IndexDataConsumer::ASTNodeInfo ASTNode) { @@ -295,7 +420,7 @@ if ((ASTNode.OrigD->getFriendObjectKind() != Decl::FriendObjectKind::FOK_None) && !(Roles & static_cast(index::SymbolRole::Definition))) - return true; + return; // A declaration created for a friend declaration should not be used as the // canonical declaration in the index. Use OrigD instead, unless we've already // picked a replacement for D @@ -303,12 +428,12 @@ D = CanonicalDecls.try_emplace(D, ASTNode.OrigD).first->second; const NamedDecl *ND = llvm::dyn_cast(D); if (!ND) - return true; + return; // Mark D as referenced if this is a reference coming from the main file. // D may not be an interesting symbol, but it's cheaper to check at the end. auto &SM = ASTCtx->getSourceManager(); - if (Opts.CountReferences && + if (Opts.SymOpts->CountReferences && (Roles & static_cast(index::SymbolRole::Reference)) && SM.getFileID(SM.getSpellingLoc(Loc)) == SM.getMainFileID()) ReferencedDecls.insert(ND); @@ -316,16 +441,16 @@ // Don't continue indexing if this is a mere reference. if (!(Roles & static_cast(index::SymbolRole::Declaration) || Roles & static_cast(index::SymbolRole::Definition))) - return true; - if (!shouldCollectSymbol(*ND, *ASTCtx, Opts)) - return true; + return; + if (!shouldCollectSymbol(*ND, *ASTCtx)) + return; auto ID = getSymbolID(ND); if (!ID) - return true; + return; const NamedDecl &OriginalDecl = *cast(ASTNode.OrigD); - const Symbol *BasicSymbol = Symbols.find(*ID); + const Symbol *BasicSymbol = Symbols->find(*ID); if (!BasicSymbol) // Regardless of role, ND is the canonical declaration. BasicSymbol = addDeclaration(*ND, std::move(*ID)); else if (isPreferredDeclaration(OriginalDecl, Roles)) @@ -337,29 +462,28 @@ if (Roles & static_cast(index::SymbolRole::Definition)) addDefinition(OriginalDecl, *BasicSymbol); - return true; } -bool SymbolCollector::handleMacroOccurence(const IdentifierInfo *Name, - const MacroInfo *MI, - index::SymbolRoleSet Roles, - SourceLocation Loc) { - if (!Opts.CollectMacro) - return true; +void SymbolCollector::CollectSymbol::collectMacro(const IdentifierInfo *Name, + const MacroInfo *MI, + index::SymbolRoleSet Roles, + SourceLocation Loc) { + if (!Opts.SymOpts->CollectMacro) + return; assert(PP.get()); const auto &SM = PP->getSourceManager(); if (SM.isInMainFile(SM.getExpansionLoc(MI->getDefinitionLoc()))) - return true; + return; // Header guards are not interesting in index. Builtin macros don't have // useful locations and are not needed for code completions. if (MI->isUsedForHeaderGuard() || MI->isBuiltinMacro()) - return true; + return; // Mark the macro as referenced if this is a reference coming from the main // file. The macro may not be an interesting symbol, but it's cheaper to check // at the end. - if (Opts.CountReferences && + if (Opts.SymOpts->CountReferences && (Roles & static_cast(index::SymbolRole::Reference)) && SM.getFileID(SM.getSpellingLoc(Loc)) == SM.getMainFileID()) ReferencedMacros.insert(Name); @@ -367,17 +491,17 @@ // FIXME: remove macro with ID if it is undefined. if (!(Roles & static_cast(index::SymbolRole::Declaration) || Roles & static_cast(index::SymbolRole::Definition))) - return true; + return; llvm::SmallString<128> USR; if (index::generateUSRForMacro(Name->getName(), MI->getDefinitionLoc(), SM, USR)) - return true; + return; SymbolID ID(USR); // Only collect one instance in case there are multiple. - if (Symbols.find(ID) != nullptr) - return true; + if (Symbols->find(ID) != nullptr) + return; Symbol S; S.ID = std::move(ID); @@ -397,7 +521,8 @@ getSignature(*CCS, &Signature, &SnippetSuffix); std::string Include; - if (Opts.CollectIncludePath && shouldCollectIncludePath(S.SymInfo.Kind)) { + if (Opts.SymOpts->CollectIncludePath && + shouldCollectIncludePath(S.SymInfo.Kind)) { if (auto Header = getIncludeHeader(Name->getName(), SM, SM.getExpansionLoc(MI->getDefinitionLoc()), Opts)) @@ -408,17 +533,16 @@ Symbol::Details Detail; Detail.IncludeHeader = Include; S.Detail = &Detail; - Symbols.insert(S); - return true; + Symbols->insert(S); } -void SymbolCollector::finish() { +void SymbolCollector::CollectSymbol::finish() { // At the end of the TU, add 1 to the refcount of all referenced symbols. auto IncRef = [this](const SymbolID &ID) { - if (const auto *S = Symbols.find(ID)) { + if (const auto *S = Symbols->find(ID)) { Symbol Inc = *S; ++Inc.References; - Symbols.insert(Inc); + Symbols->insert(Inc); } }; for (const NamedDecl *ND : ReferencedDecls) { @@ -426,7 +550,7 @@ IncRef(*ID); } } - if (Opts.CollectMacro) { + if (Opts.SymOpts->CollectMacro) { assert(PP); for (const IdentifierInfo *II : ReferencedMacros) { llvm::SmallString<128> USR; @@ -440,8 +564,9 @@ ReferencedMacros.clear(); } -const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND, - SymbolID ID) { +const Symbol * +SymbolCollector::CollectSymbol::addDeclaration(const NamedDecl &ND, + SymbolID ID) { auto &Ctx = ND.getASTContext(); auto &SM = Ctx.getSourceManager(); @@ -477,7 +602,8 @@ std::string ReturnType = getReturnType(*CCS); std::string Include; - if (Opts.CollectIncludePath && shouldCollectIncludePath(S.SymInfo.Kind)) { + if (Opts.SymOpts->CollectIncludePath && + shouldCollectIncludePath(S.SymInfo.Kind)) { // Use the expansion location to get the #include header since this is // where the symbol is exposed. if (auto Header = getIncludeHeader( @@ -492,13 +618,13 @@ Detail.IncludeHeader = Include; S.Detail = &Detail; - S.Origin = Opts.Origin; - Symbols.insert(S); - return Symbols.find(S.ID); + S.Origin = Opts.SymOpts->Origin; + Symbols->insert(S); + return Symbols->find(S.ID); } -void SymbolCollector::addDefinition(const NamedDecl &ND, - const Symbol &DeclSym) { +void SymbolCollector::CollectSymbol::addDefinition(const NamedDecl &ND, + const Symbol &DeclSym) { if (DeclSym.Definition) return; // If we saw some forward declaration, we end up copying the symbol. @@ -510,7 +636,32 @@ ND.getASTContext().getSourceManager(), Opts, ASTCtx->getLangOpts(), FileURI)) S.Definition = *DefLoc; - Symbols.insert(S); + Symbols->insert(S); +} + +bool SymbolCollector::handleDeclOccurence( + const Decl *D, index::SymbolRoleSet Roles, + ArrayRef Relations, SourceLocation Loc, + index::IndexDataConsumer::ASTNodeInfo ASTNode) { + if (CollectSym) + CollectSym->collectDecl(D, Roles, Relations, Loc, ASTNode); + if (CollectOccur) + CollectOccur->collectDecl(D, Roles, Relations, Loc, ASTNode); + return true; +} + +bool SymbolCollector::handleMacroOccurence(const IdentifierInfo *Name, + const MacroInfo *MI, + index::SymbolRoleSet Roles, + SourceLocation Loc) { + if (CollectSym) + CollectSym->collectMacro(Name, MI, Roles, Loc); + return true; +} + +void SymbolCollector::finish() { + if (CollectSym) + CollectSym->finish(); } } // namespace clangd Index: unittests/clangd/SymbolCollectorTests.cpp =================================================================== --- unittests/clangd/SymbolCollectorTests.cpp +++ unittests/clangd/SymbolCollectorTests.cpp @@ -74,6 +74,14 @@ MATCHER_P(ForCodeCompletion, IsIndexedForCodeCompletion, "") { return arg.IsIndexedForCodeCompletion == IsIndexedForCodeCompletion; } +MATCHER(OccurrenceRange, "") { + const clang::clangd::SymbolOccurrence &Pos = testing::get<0>(arg); + const clang::clangd::Range &Range = testing::get<1>(arg); + return std::tie(Pos.Location.Start.Line, Pos.Location.Start.Column, + Pos.Location.End.Line, Pos.Location.End.Column) == + std::tie(Range.start.line, Range.start.character, Range.end.line, + Range.end.character); +} namespace clang { namespace clangd { @@ -95,7 +103,7 @@ assert(AST.hasValue()); return SymbolCollector::shouldCollectSymbol( Qualified ? findDecl(*AST, Name) : findAnyDecl(*AST, Name), - AST->getASTContext(), SymbolCollector::Options()); + AST->getASTContext()); } protected: @@ -162,8 +170,10 @@ class SymbolIndexActionFactory : public tooling::FrontendActionFactory { public: SymbolIndexActionFactory(SymbolCollector::Options COpts, - CommentHandler *PragmaHandler) - : COpts(std::move(COpts)), PragmaHandler(PragmaHandler) {} + CommentHandler *PragmaHandler, + index::IndexingOptions IndexOpts) + : COpts(std::move(COpts)), PragmaHandler(PragmaHandler), + IndexOpts(IndexOpts) {} clang::FrontendAction *create() override { class WrappedIndexAction : public WrapperFrontendAction { @@ -186,10 +196,6 @@ index::IndexingOptions IndexOpts; CommentHandler *PragmaHandler; }; - index::IndexingOptions IndexOpts; - IndexOpts.SystemSymbolFilter = - index::IndexingOptions::SystemSymbolFilterKind::All; - IndexOpts.IndexFunctionLocals = false; Collector = std::make_shared(COpts); return new WrappedIndexAction(Collector, std::move(IndexOpts), PragmaHandler); @@ -198,6 +204,8 @@ std::shared_ptr Collector; SymbolCollector::Options COpts; CommentHandler *PragmaHandler; + + index::IndexingOptions IndexOpts; }; class SymbolCollectorTest : public ::testing::Test { @@ -210,13 +218,52 @@ TestFileURI = URI::createFile(TestFileName).toString(); } + bool collectSymbols(StringRef HeaderCode, StringRef MainCode, + const std::vector &ExtraArgs = {}) { + index::IndexingOptions IndexOpts; + IndexOpts.SystemSymbolFilter = + index::IndexingOptions::SystemSymbolFilterKind::All; + IndexOpts.IndexFunctionLocals = false; + CollectorOpts.SymOpts = &CollectSymOpts; + CollectorOpts.OccurrenceOpts = nullptr; + return runSymbolCollector(HeaderCode, MainCode, CollectorOpts, IndexOpts, + ExtraArgs); + } + + bool collectOccurrences(StringRef HeaderCode, StringRef MainCode, + const std::vector &ExtraArgs = {}) { + index::IndexingOptions IndexOpts; + IndexOpts.SystemSymbolFilter = + index::IndexingOptions::SystemSymbolFilterKind::All; + IndexOpts.IndexFunctionLocals = true; + CollectorOpts.SymOpts = nullptr; + CollectorOpts.OccurrenceOpts = &CollectOccurrenceOpts; + return runSymbolCollector(HeaderCode, MainCode, CollectorOpts, IndexOpts, + ExtraArgs); + } + +protected: + llvm::IntrusiveRefCntPtr InMemoryFileSystem; + std::string TestHeaderName; + std::string TestHeaderURI; + std::string TestFileName; + std::string TestFileURI; + SymbolSlab Symbols; + SymbolCollector::Options CollectorOpts; + SymbolCollector::Options::CollectSymbolOptions CollectSymOpts; + SymbolCollector::Options::CollectOccurrenceOptions CollectOccurrenceOpts; + std::unique_ptr PragmaHandler; + +private: bool runSymbolCollector(StringRef HeaderCode, StringRef MainCode, + SymbolCollector::Options Opts, + index::IndexingOptions IndexOpts, const std::vector &ExtraArgs = {}) { llvm::IntrusiveRefCntPtr Files( new FileManager(FileSystemOptions(), InMemoryFileSystem)); auto Factory = llvm::make_unique( - CollectorOpts, PragmaHandler.get()); + Opts, PragmaHandler.get(), std::move(IndexOpts)); std::vector Args = { "symbol_collector", "-fsyntax-only", "-xc++", @@ -239,16 +286,6 @@ Symbols = Factory->Collector->takeSymbols(); return true; } - -protected: - llvm::IntrusiveRefCntPtr InMemoryFileSystem; - std::string TestHeaderName; - std::string TestHeaderURI; - std::string TestFileName; - std::string TestFileURI; - SymbolSlab Symbols; - SymbolCollector::Options CollectorOpts; - std::unique_ptr PragmaHandler; }; TEST_F(SymbolCollectorTest, CollectSymbols) { @@ -300,7 +337,7 @@ using bar::v2; } // namespace foo )"; - runSymbolCollector(Header, /*Main=*/""); + collectSymbols(Header, /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAreArray( {AllOf(QName("Foo"), ForCodeCompletion(true)), @@ -334,7 +371,7 @@ extern template struct Tmpl; template struct Tmpl; )"); - runSymbolCollector(Header.code(), /*Main=*/""); + collectSymbols(Header.code(), /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAreArray( {AllOf(QName("Tmpl"), DeclRange(Header.range())), @@ -369,7 +406,7 @@ @end )"; TestFileName = "test.m"; - runSymbolCollector(Header, /*Main=*/"", {"-fblocks", "-xobjective-c++"}); + collectSymbols(Header, /*Main=*/"", {"-fblocks", "-xobjective-c++"}); EXPECT_THAT(Symbols, UnorderedElementsAre( QName("Person"), QName("Person::someMethodName:lastName:"), @@ -398,7 +435,7 @@ // Declared/defined in main only. int Y; )cpp"); - runSymbolCollector(Header.code(), Main.code()); + collectSymbols(Header.code(), Main.code()); EXPECT_THAT( Symbols, UnorderedElementsAre( @@ -431,8 +468,9 @@ class Y{}; // definition doesn't count as a reference GLOBAL_Z(z); // Not a reference to Z, we don't spell the type. )"; - CollectorOpts.CountReferences = true; - runSymbolCollector(Header, Main); + + CollectSymOpts.CountReferences = true; + collectSymbols(Header, Main); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("W"), Refs(1)), AllOf(QName("X"), Refs(1)), @@ -441,7 +479,7 @@ } TEST_F(SymbolCollectorTest, SymbolRelativeNoFallback) { - runSymbolCollector("class Foo {};", /*Main=*/""); + collectSymbols("class Foo {};", /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre( AllOf(QName("Foo"), DeclURI(TestHeaderURI)))); } @@ -451,7 +489,7 @@ TestFileName = "x.cpp"; TestHeaderURI = URI::createFile(testPath(TestHeaderName)).toString(); CollectorOpts.FallbackDir = testRoot(); - runSymbolCollector("class Foo {};", /*Main=*/""); + collectSymbols("class Foo {};", /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("Foo"), DeclURI(TestHeaderURI)))); } @@ -461,7 +499,7 @@ CollectorOpts.URISchemes.insert(CollectorOpts.URISchemes.begin(), "unittest"); TestHeaderName = testPath("x.h"); TestFileName = testPath("x.cpp"); - runSymbolCollector("class Foo {};", /*Main=*/""); + collectSymbols("class Foo {};", /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre( AllOf(QName("Foo"), DeclURI("unittest:///x.h")))); } @@ -469,14 +507,14 @@ TEST_F(SymbolCollectorTest, InvalidURIScheme) { // Use test URI scheme from URITests.cpp CollectorOpts.URISchemes = {"invalid"}; - runSymbolCollector("class Foo {};", /*Main=*/""); + collectSymbols("class Foo {};", /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("Foo"), DeclURI("")))); } TEST_F(SymbolCollectorTest, FallbackToFileURI) { // Use test URI scheme from URITests.cpp CollectorOpts.URISchemes = {"invalid", "file"}; - runSymbolCollector("class Foo {};", /*Main=*/""); + collectSymbols("class Foo {};", /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre( AllOf(QName("Foo"), DeclURI(TestHeaderURI)))); } @@ -498,7 +536,7 @@ }; } )"; - runSymbolCollector(Header, /*Main=*/""); + collectSymbols(Header, /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre( AllOf(QName("Red"), ForCodeCompletion(true)), @@ -516,7 +554,7 @@ int a; } Foo; )"; - runSymbolCollector(Header, /*Main=*/""); + collectSymbols(Header, /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(QName("Foo"), QName("(anonymous struct)::a"))); } @@ -535,7 +573,7 @@ FF2(); )"); - runSymbolCollector(Header.code(), /*Main=*/""); + collectSymbols(Header.code(), /*Main=*/""); EXPECT_THAT( Symbols, UnorderedElementsAre( @@ -552,8 +590,8 @@ #endif )"); - runSymbolCollector(Header.code(), /*Main=*/"", - /*ExtraArgs=*/{"-DNAME=name"}); + collectSymbols(Header.code(), /*Main=*/"", + /*ExtraArgs=*/{"-DNAME=name"}); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf( QName("name"), @@ -574,7 +612,7 @@ void main_f() {} // ignore void f1() {} )"; - runSymbolCollector(Header, Main); + collectSymbols(Header, Main); EXPECT_THAT(Symbols, UnorderedElementsAre(QName("Foo"), QName("f1"), QName("f2"))); } @@ -593,7 +631,7 @@ void Foo::g() {} void Foo::ssf() {} )"; - runSymbolCollector(Header, Main); + collectSymbols(Header, Main); EXPECT_THAT(Symbols, UnorderedElementsAre(QName("Foo"), QName("Foo::f"), QName("Foo::g"), QName("Foo::sf"), @@ -609,7 +647,7 @@ } } )"; - runSymbolCollector(Header, /*Main=*/""); + collectSymbols(Header, /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(QName("na"), QName("na::nb"), QName("na::Foo"), QName("na::nb::Bar"))); @@ -622,7 +660,7 @@ extern "C" { class Bar {}; } } )"; - runSymbolCollector(Header, /*Main=*/""); + collectSymbols(Header, /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(QName("na"), QName("Foo"), QName("na::Bar"))); } @@ -641,7 +679,7 @@ } } )"; - runSymbolCollector(Header, /*Main=*/""); + collectSymbols(Header, /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(QName("na"), QName("na::nb"), QName("na::Foo"), QName("na::Bar"))); @@ -654,7 +692,7 @@ int ff(int x, double y) { return 0; } } )"; - runSymbolCollector(Header, /*Main=*/""); + collectSymbols(Header, /*Main=*/""); EXPECT_THAT( Symbols, UnorderedElementsAre( @@ -669,7 +707,7 @@ int ff(int x, double y) { return 0; } } )"; - runSymbolCollector(Header, /*Main=*/""); + collectSymbols(Header, /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre( QName("nx"), @@ -749,8 +787,8 @@ } TEST_F(SymbolCollectorTest, IncludeHeaderSameAsFileURI) { - CollectorOpts.CollectIncludePath = true; - runSymbolCollector("class Foo {};", /*Main=*/""); + CollectSymOpts.CollectIncludePath = true; + collectSymbols("class Foo {};", /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("Foo"), DeclURI(TestHeaderURI), IncludeHeader(TestHeaderURI)))); @@ -758,15 +796,15 @@ #ifndef _WIN32 TEST_F(SymbolCollectorTest, CanonicalSTLHeader) { - CollectorOpts.CollectIncludePath = true; + CollectSymOpts.CollectIncludePath = true; CanonicalIncludes Includes; addSystemHeadersMapping(&Includes); - CollectorOpts.Includes = &Includes; + CollectSymOpts.Includes = &Includes; // bits/basic_string.h$ should be mapped to TestHeaderName = "/nasty/bits/basic_string.h"; TestFileName = "/nasty/bits/basic_string.cpp"; TestHeaderURI = URI::createFile(TestHeaderName).toString(); - runSymbolCollector("class string {};", /*Main=*/""); + collectSymbols("class string {};", /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("string"), DeclURI(TestHeaderURI), IncludeHeader("")))); @@ -774,10 +812,10 @@ #endif TEST_F(SymbolCollectorTest, STLiosfwd) { - CollectorOpts.CollectIncludePath = true; + CollectSymOpts.CollectIncludePath = true; CanonicalIncludes Includes; addSystemHeadersMapping(&Includes); - CollectorOpts.Includes = &Includes; + CollectSymOpts.Includes = &Includes; // Symbols from should be mapped individually. TestHeaderName = testPath("iosfwd"); TestFileName = testPath("iosfwd.cpp"); @@ -789,7 +827,7 @@ class filebuf {}; } // namespace std )"; - runSymbolCollector(Header, /*Main=*/""); + collectSymbols(Header, /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre( QName("std"), @@ -800,46 +838,46 @@ } TEST_F(SymbolCollectorTest, IWYUPragma) { - CollectorOpts.CollectIncludePath = true; + CollectSymOpts.CollectIncludePath = true; CanonicalIncludes Includes; PragmaHandler = collectIWYUHeaderMaps(&Includes); - CollectorOpts.Includes = &Includes; + CollectSymOpts.Includes = &Includes; const std::string Header = R"( // IWYU pragma: private, include the/good/header.h class Foo {}; )"; - runSymbolCollector(Header, /*Main=*/""); + collectSymbols(Header, /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre( AllOf(QName("Foo"), DeclURI(TestHeaderURI), IncludeHeader("\"the/good/header.h\"")))); } TEST_F(SymbolCollectorTest, IWYUPragmaWithDoubleQuotes) { - CollectorOpts.CollectIncludePath = true; + CollectSymOpts.CollectIncludePath = true; CanonicalIncludes Includes; PragmaHandler = collectIWYUHeaderMaps(&Includes); - CollectorOpts.Includes = &Includes; + CollectSymOpts.Includes = &Includes; const std::string Header = R"( // IWYU pragma: private, include "the/good/header.h" class Foo {}; )"; - runSymbolCollector(Header, /*Main=*/""); + collectSymbols(Header, /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre( AllOf(QName("Foo"), DeclURI(TestHeaderURI), IncludeHeader("\"the/good/header.h\"")))); } TEST_F(SymbolCollectorTest, SkipIncFileWhenCanonicalizeHeaders) { - CollectorOpts.CollectIncludePath = true; + CollectSymOpts.CollectIncludePath = true; CanonicalIncludes Includes; Includes.addMapping(TestHeaderName, ""); - CollectorOpts.Includes = &Includes; + CollectSymOpts.Includes = &Includes; auto IncFile = testPath("test.inc"); auto IncURI = URI::createFile(IncFile).toString(); InMemoryFileSystem->addFile(IncFile, 0, llvm::MemoryBuffer::getMemBuffer("class X {};")); - runSymbolCollector("#include \"test.inc\"\nclass Y {};", /*Main=*/"", - /*ExtraArgs=*/{"-I", testRoot()}); + collectSymbols("#include \"test.inc\"\nclass Y {};", /*Main=*/"", + /*ExtraArgs=*/{"-I", testRoot()}); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("X"), DeclURI(IncURI), IncludeHeader("")), @@ -848,53 +886,53 @@ } TEST_F(SymbolCollectorTest, MainFileIsHeaderWhenSkipIncFile) { - CollectorOpts.CollectIncludePath = true; + CollectSymOpts.CollectIncludePath = true; CanonicalIncludes Includes; - CollectorOpts.Includes = &Includes; + CollectSymOpts.Includes = &Includes; TestFileName = testPath("main.h"); TestFileURI = URI::createFile(TestFileName).toString(); auto IncFile = testPath("test.inc"); auto IncURI = URI::createFile(IncFile).toString(); InMemoryFileSystem->addFile(IncFile, 0, llvm::MemoryBuffer::getMemBuffer("class X {};")); - runSymbolCollector("", /*Main=*/"#include \"test.inc\"", - /*ExtraArgs=*/{"-I", testRoot()}); + collectSymbols("", /*Main=*/"#include \"test.inc\"", + /*ExtraArgs=*/{"-I", testRoot()}); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("X"), DeclURI(IncURI), IncludeHeader(TestFileURI)))); } TEST_F(SymbolCollectorTest, MainFileIsHeaderWithoutExtensionWhenSkipIncFile) { - CollectorOpts.CollectIncludePath = true; + CollectSymOpts.CollectIncludePath = true; CanonicalIncludes Includes; - CollectorOpts.Includes = &Includes; + CollectSymOpts.Includes = &Includes; TestFileName = testPath("no_ext_main"); TestFileURI = URI::createFile(TestFileName).toString(); auto IncFile = testPath("test.inc"); auto IncURI = URI::createFile(IncFile).toString(); InMemoryFileSystem->addFile(IncFile, 0, llvm::MemoryBuffer::getMemBuffer("class X {};")); - runSymbolCollector("", /*Main=*/"#include \"test.inc\"", - /*ExtraArgs=*/{"-I", testRoot()}); + collectSymbols("", /*Main=*/"#include \"test.inc\"", + /*ExtraArgs=*/{"-I", testRoot()}); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("X"), DeclURI(IncURI), IncludeHeader(TestFileURI)))); } TEST_F(SymbolCollectorTest, FallbackToIncFileWhenIncludingFileIsCC) { - CollectorOpts.CollectIncludePath = true; + CollectSymOpts.CollectIncludePath = true; CanonicalIncludes Includes; - CollectorOpts.Includes = &Includes; + CollectSymOpts.Includes = &Includes; auto IncFile = testPath("test.inc"); auto IncURI = URI::createFile(IncFile).toString(); InMemoryFileSystem->addFile(IncFile, 0, llvm::MemoryBuffer::getMemBuffer("class X {};")); - runSymbolCollector("", /*Main=*/"#include \"test.inc\"", - /*ExtraArgs=*/{"-I", testRoot()}); + collectSymbols("", /*Main=*/"#include \"test.inc\"", + /*ExtraArgs=*/{"-I", testRoot()}); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("X"), DeclURI(IncURI), IncludeHeader(IncURI)))); } TEST_F(SymbolCollectorTest, AvoidUsingFwdDeclsAsCanonicalDecls) { - CollectorOpts.CollectIncludePath = true; + CollectSymOpts.CollectIncludePath = true; Annotations Header(R"( // Forward declarations of TagDecls. class C; @@ -906,7 +944,7 @@ struct $sdecl[[S]] {}; union $udecl[[U]] {int $xdecl[[x]]; bool $ydecl[[y]];}; )"); - runSymbolCollector(Header.code(), /*Main=*/""); + collectSymbols(Header.code(), /*Main=*/""); EXPECT_THAT( Symbols, UnorderedElementsAre( @@ -928,8 +966,9 @@ } TEST_F(SymbolCollectorTest, ClassForwardDeclarationIsCanonical) { - CollectorOpts.CollectIncludePath = true; - runSymbolCollector(/*Header=*/"class X;", /*Main=*/"class X {};"); + CollectSymOpts.CollectIncludePath = true; + + collectSymbols(/*Header=*/"class X;", /*Main=*/"class X {};"); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf( QName("X"), DeclURI(TestHeaderURI), IncludeHeader(TestHeaderURI), DefURI(TestFileURI)))); @@ -938,7 +977,7 @@ TEST_F(SymbolCollectorTest, UTF16Character) { // ö is 2-bytes. Annotations Header(/*Header=*/"class [[pörk]] {};"); - runSymbolCollector(Header.code(), /*Main=*/""); + collectSymbols(Header.code(), /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre( AllOf(QName("pörk"), DeclRange(Header.range())))); } @@ -957,7 +996,7 @@ void $foo[[foo]](); } )"); - runSymbolCollector(Header.code(), /*Main=*/""); + collectSymbols(Header.code(), /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre( @@ -979,21 +1018,21 @@ friend class Y; }; )"; - CollectorOpts.CountReferences = true; - runSymbolCollector(Header, Main); + CollectSymOpts.CountReferences = true; + collectSymbols(Header, Main); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("X"), Refs(1)), AllOf(QName("Y"), Refs(1)))); } TEST_F(SymbolCollectorTest, Origin) { - CollectorOpts.Origin = SymbolOrigin::Static; - runSymbolCollector("class Foo {};", /*Main=*/""); + CollectSymOpts.Origin = SymbolOrigin::Static; + collectSymbols("class Foo {};", /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre( Field(&Symbol::Origin, SymbolOrigin::Static))); } TEST_F(SymbolCollectorTest, CollectMacros) { - CollectorOpts.CollectIncludePath = true; + CollectSymOpts.CollectIncludePath = true; Annotations Header(R"( #define X 1 #define $mac[[MAC]](x) int x @@ -1005,9 +1044,9 @@ #define MAIN 1 // not indexed USED(t); )"; - CollectorOpts.CountReferences = true; - CollectorOpts.CollectMacro = true; - runSymbolCollector(Header.code(), Main); + CollectSymOpts.CountReferences = true; + CollectSymOpts.CollectMacro = true; + collectSymbols(Header.code(), Main); EXPECT_THAT( Symbols, UnorderedElementsAre( @@ -1018,6 +1057,59 @@ AllOf(Labeled("USED(y)"), Refs(1), DeclRange(Header.range("used"))))); } +TEST_F(SymbolCollectorTest, CollectReference) { + const std::string Header(R"( + class Foo { + public: + Foo() {} + Foo(int); + }; + class Bar; + void func();)"); + + Annotations Main(R"( + class $bar[[Bar]] {}; + + void $func[[func]](); + + void fff() { + $foo[[Foo]] foo; + $bar[[Bar]] bar; + $func[[func]](); + int abc = 0; + $foo[[Foo]] foo2 = abc; + })"); + + auto H = TestTU::withHeaderCode(Header); + auto HeaderSymbols = H.headerSymbols(); + auto Foo = findSymbol(HeaderSymbols, "Foo"); + auto Bar = findSymbol(HeaderSymbols, "Bar"); + auto Func = findSymbol(HeaderSymbols, "func"); + + CollectOccurrenceOpts.Filter = SymbolOccurrenceKind::Declaration | + SymbolOccurrenceKind::Definition | + SymbolOccurrenceKind::Reference; + CollectOccurrenceOpts.IDs = llvm::None; + collectOccurrences(Header, Main.code()); + EXPECT_THAT( + Symbols.findOccurrences(Foo.ID), + testing::UnorderedPointwise(OccurrenceRange(), Main.ranges("foo"))); + EXPECT_THAT( + Symbols.findOccurrences(Bar.ID), + testing::UnorderedPointwise(OccurrenceRange(), Main.ranges("bar"))); + EXPECT_THAT( + Symbols.findOccurrences(Func.ID), + testing::UnorderedPointwise(OccurrenceRange(), Main.ranges("func"))); + + CollectOccurrenceOpts.IDs = {Foo.ID}; + collectOccurrences(Header, Main.code()); + EXPECT_THAT( + Symbols.findOccurrences(Foo.ID), + testing::UnorderedPointwise(OccurrenceRange(), Main.ranges("foo"))); + EXPECT_THAT(Symbols.findOccurrences(Bar.ID), testing::IsEmpty()); + EXPECT_THAT(Symbols.findOccurrences(Func.ID), testing::IsEmpty()); +} + } // namespace } // namespace clangd } // namespace clang