Index: clangd/index/FileIndex.h =================================================================== --- clangd/index/FileIndex.h +++ clangd/index/FileIndex.h @@ -39,18 +39,25 @@ /// locking when we swap or obtain refereces to snapshots. class FileSymbols { public: - /// \brief Updates all symbols in a file. If \p Slab is nullptr, symbols for - /// \p Path will be removed. - void update(PathRef Path, std::unique_ptr Slab); + /// \brief Updates all symbols and occurrences in a file. + /// If \p Slab (Occurrence) is nullptr, symbols (occurrences) for \p Path + /// will be removed. + void update(PathRef Path, std::unique_ptr Slab, + std::unique_ptr Occurrences); // The shared_ptr keeps the symbols alive std::shared_ptr> allSymbols(); + /// Returns all symbol occurrences for all active files. + std::shared_ptr allOccurrences() const; + private: mutable std::mutex Mutex; /// \brief Stores the latest snapshots for all active files. llvm::StringMap> FileToSlabs; + /// Stores the latest occurrence slabs for all active files. + llvm::StringMap> FileToOccurrenceSlabs; }; /// \brief This manages symbls from files and an in-memory index on all symbols. @@ -90,12 +97,12 @@ std::vector URISchemes; }; -/// Retrieves namespace and class level symbols in \p AST. +/// Retrieves symbols and symbol occurrences in \p AST. /// Exposed to assist in unit tests. /// If URISchemes is empty, the default schemes in SymbolCollector will be used. /// If \p TopLevelDecls is set, only these decls are indexed. Otherwise, all top /// level decls obtained from \p AST are indexed. -SymbolSlab +std::pair indexAST(ASTContext &AST, std::shared_ptr PP, llvm::Optional> TopLevelDecls = llvm::None, llvm::ArrayRef URISchemes = {}); Index: clangd/index/FileIndex.cpp =================================================================== --- clangd/index/FileIndex.cpp +++ clangd/index/FileIndex.cpp @@ -16,9 +16,10 @@ namespace clang { namespace clangd { -SymbolSlab indexAST(ASTContext &AST, std::shared_ptr PP, - llvm::Optional> TopLevelDecls, - llvm::ArrayRef URISchemes) { +std::pair +indexAST(ASTContext &AST, std::shared_ptr PP, + llvm::Optional> TopLevelDecls, + llvm::ArrayRef URISchemes) { SymbolCollector::Options CollectorOpts; // FIXME(ioeric): we might also want to collect include headers. We would need // to make sure all includes are canonicalized (with CanonicalIncludes), which @@ -31,8 +32,6 @@ CollectorOpts.URISchemes = URISchemes; CollectorOpts.Origin = SymbolOrigin::Dynamic; - SymbolCollector Collector(std::move(CollectorOpts)); - Collector.setPreprocessor(PP); index::IndexingOptions IndexOpts; // We only need declarations, because we don't count references. IndexOpts.SystemSymbolFilter = @@ -46,20 +45,45 @@ DeclsToIndex.assign(AST.getTranslationUnitDecl()->decls().begin(), AST.getTranslationUnitDecl()->decls().end()); + // We only collect occurrences when indexing main AST. + // FIXME: this is a hacky way to detect whether we are indexing preamble AST + // or main AST, we should make it explicitly. + bool IsIndexMainAST = TopLevelDecls.hasValue(); + if (IsIndexMainAST) + CollectorOpts.OccurrenceFilter = AllOccurrenceKinds; + + SymbolCollector Collector(std::move(CollectorOpts)); + Collector.setPreprocessor(PP); index::indexTopLevelDecls(AST, DeclsToIndex, Collector, IndexOpts); - return Collector.takeSymbols(); + const auto &SM = AST.getSourceManager(); + const auto *MainFileEntry = SM.getFileEntryForID(SM.getMainFileID()); + std::string FileName = MainFileEntry ? MainFileEntry->getName() : ""; + + auto Syms = Collector.takeSymbols(); + auto Occurrences = Collector.takeOccurrences(); + vlog("index {0}AST for {1}: \n" + " symbol slab: {2} symbols, {3} bytes\n" + " occurrence slab: {4} symbols, {5} bytes", + IsIndexMainAST ? "Main" : "Preamble", FileName, Syms.size(), + Syms.bytes(), Occurrences.size(), Occurrences.bytes()); + return {std::move(Syms), std::move(Occurrences)}; } FileIndex::FileIndex(std::vector URISchemes) : URISchemes(std::move(URISchemes)) {} -void FileSymbols::update(PathRef Path, std::unique_ptr Slab) { +void FileSymbols::update(PathRef Path, std::unique_ptr Slab, + std::unique_ptr Occurrences) { std::lock_guard Lock(Mutex); if (!Slab) FileToSlabs.erase(Path); else FileToSlabs[Path] = std::move(Slab); + if (!Occurrences) + FileToOccurrenceSlabs.erase(Path); + else + FileToOccurrenceSlabs[Path] = std::move(Occurrences); } std::shared_ptr> FileSymbols::allSymbols() { @@ -85,19 +109,47 @@ return {std::move(Snap), Pointers}; } +std::shared_ptr FileSymbols::allOccurrences() const { + // The snapshot manages life time of symbol occurrence slabs and provides + // pointers to all occurrences in all occurrence slabs. + struct Snapshot { + MemIndex::OccurrenceMap Occurrences; // ID => {Occurrence} + std::vector> KeepAlive; + }; + + auto Snap = std::make_shared(); + { + std::lock_guard Lock(Mutex); + + for (const auto &FileAndSlab : FileToOccurrenceSlabs) { + Snap->KeepAlive.push_back(FileAndSlab.second); + for (const auto &IDAndOccurrences : *FileAndSlab.second) { + auto &Occurrences = Snap->Occurrences[IDAndOccurrences.first]; + for (const auto &Occurrence : IDAndOccurrences.second) + Occurrences.push_back(&Occurrence); + } + } + } + + return {std::move(Snap), &Snap->Occurrences}; +} + void FileIndex::update(PathRef Path, ASTContext *AST, std::shared_ptr PP, llvm::Optional> TopLevelDecls) { if (!AST) { - FSymbols.update(Path, nullptr); + FSymbols.update(Path, nullptr, nullptr); } else { assert(PP); auto Slab = llvm::make_unique(); - *Slab = indexAST(*AST, PP, TopLevelDecls, URISchemes); - FSymbols.update(Path, std::move(Slab)); + auto OccurrenceSlab = llvm::make_unique(); + auto IndexResults = indexAST(*AST, PP, TopLevelDecls, URISchemes); + std::tie(*Slab, *OccurrenceSlab) = + indexAST(*AST, PP, TopLevelDecls, URISchemes); + FSymbols.update(Path, std::move(Slab), std::move(OccurrenceSlab)); } auto Symbols = FSymbols.allSymbols(); - Index.build(std::move(Symbols)); + Index.build(std::move(Symbols), FSymbols.allOccurrences()); } bool FileIndex::fuzzyFind( @@ -115,7 +167,7 @@ void FileIndex::findOccurrences( const OccurrencesRequest &Req, llvm::function_ref Callback) const { - log("findOccurrences is not implemented."); + Index.findOccurrences(Req, Callback); } size_t FileIndex::estimateMemoryUsage() const { Index: clangd/index/Index.h =================================================================== --- clangd/index/Index.h +++ clangd/index/Index.h @@ -344,6 +344,12 @@ SymbolOccurrenceSlab() : UniqueStrings(Arena) {} + static SymbolOccurrenceSlab createEmpty() { + SymbolOccurrenceSlab Empty; + Empty.freeze(); + return Empty; + } + // Define move semantics for the slab, allowing assignment from an rvalue. // Implicit move assignment is deleted by the compiler because // StringSaver has a reference type member. @@ -360,6 +366,12 @@ const_iterator begin() const { return Occurrences.begin(); } const_iterator end() const { return Occurrences.end(); } + size_t bytes() const { + return sizeof(*this) + Arena.getTotalMemory() + Occurrences.getMemorySize(); + } + + size_t size() const { return Occurrences.size(); } + // Adds a symbol occurrence. // This is a deep copy: underlying FileURI will be owned by the slab. void insert(const SymbolID &SymID, const SymbolOccurrence &Occurrence); Index: clangd/index/MemIndex.h =================================================================== --- clangd/index/MemIndex.h +++ clangd/index/MemIndex.h @@ -16,16 +16,24 @@ namespace clang { namespace clangd { -/// \brief This implements an index for a (relatively small) set of symbols that -/// can be easily managed in memory. +/// \brief This implements an index for a (relatively small) set of symbols (or +/// symbol occurrences) that can be easily managed in memory. class MemIndex : public SymbolIndex { public: - /// \brief (Re-)Build index for `Symbols`. All symbol pointers must remain - /// accessible as long as `Symbols` is kept alive. - void build(std::shared_ptr> Symbols); - - /// \brief Build index from a symbol slab. - static std::unique_ptr build(SymbolSlab Slab); + /// Maps from a symbol ID to all corresponding symbol occurrences. + /// The map doesn't own occurrence objects. + using OccurrenceMap = + llvm::DenseMap>; + + /// \brief (Re-)Build index for `Symbols` and update `Occurrences`. + /// All symbol pointers and symbol occurrence pointers must remain accessible + /// as long as `Symbols` and `Occurrences` are kept alive. + void build(std::shared_ptr> Symbols, + std::shared_ptr Occurrences); + + /// \brief Build index from a symbol slab and a symbol occurrence slab. + static std::unique_ptr build(SymbolSlab Symbols, + SymbolOccurrenceSlab Occurrences); bool fuzzyFind(const FuzzyFindRequest &Req, @@ -47,6 +55,8 @@ // Index is a set of symbols that are deduplicated by symbol IDs. // FIXME: build smarter index structure. llvm::DenseMap Index; + // A map from symbol ID to symbol occurrences, support query by IDs. + std::shared_ptr Occurrences; mutable std::mutex Mutex; }; Index: clangd/index/MemIndex.cpp =================================================================== --- clangd/index/MemIndex.cpp +++ clangd/index/MemIndex.cpp @@ -15,7 +15,27 @@ namespace clang { namespace clangd { -void MemIndex::build(std::shared_ptr> Syms) { +static std::shared_ptr +getOccurrencesFromSlab(SymbolOccurrenceSlab OccurrencesSlab) { + struct Snapshot { + SymbolOccurrenceSlab Slab; + MemIndex::OccurrenceMap Occurrences; + }; + + auto Snap = std::make_shared(); + Snap->Slab = std::move(OccurrencesSlab); + for (const auto &IDAndOccurrences : Snap->Slab) { + auto &Occurrences = Snap->Occurrences[IDAndOccurrences.first]; + for (const auto &Occurrence : IDAndOccurrences.second) + Occurrences.push_back(&Occurrence); + } + return {std::move(Snap), &Snap->Occurrences}; +} + +void MemIndex::build(std::shared_ptr> Syms, + std::shared_ptr AllOccurrences) { + assert(Syms && "Syms must be set when build MemIndex"); + assert(AllOccurrences && "Occurrences must be set when build MemIndex"); llvm::DenseMap TempIndex; for (const Symbol *Sym : *Syms) TempIndex[Sym->ID] = Sym; @@ -25,15 +45,18 @@ std::lock_guard Lock(Mutex); Index = std::move(TempIndex); Symbols = std::move(Syms); // Relase old symbols. + Occurrences = std::move(AllOccurrences); } vlog("Built MemIndex with estimated memory usage {0} bytes.", estimateMemoryUsage()); } -std::unique_ptr MemIndex::build(SymbolSlab Slab) { +std::unique_ptr MemIndex::build(SymbolSlab Symbols, + SymbolOccurrenceSlab Occurrences) { auto Idx = llvm::make_unique(); - Idx->build(getSymbolsFromSlab(std::move(Slab))); + Idx->build(getSymbolsFromSlab(std::move(Symbols)), + getOccurrencesFromSlab(std::move(Occurrences))); return std::move(Idx); } @@ -84,7 +107,16 @@ void MemIndex::findOccurrences( const OccurrencesRequest &Req, llvm::function_ref Callback) const { - log("findOccurrences is not implemented."); + std::lock_guard Lock(Mutex); + for (const auto &ReqID : Req.IDs) { + auto FoundOccurrences = Occurrences->find(ReqID); + if (FoundOccurrences == Occurrences->end()) + continue; + for (const auto *O : FoundOccurrences->second) { + if (static_cast(Req.Filter & O->Kind)) + Callback(*O); + } + } } std::shared_ptr> Index: clangd/index/Merge.h =================================================================== --- clangd/index/Merge.h +++ clangd/index/Merge.h @@ -24,6 +24,10 @@ // - the Dynamic index covers few files, but is relatively up-to-date. // - the Static index covers a bigger set of files, but is relatively stale. // The returned index attempts to combine results, and avoid duplicates. +// +// FIXME: We don't have a mechanism in Index to track deleted symbols and +// occurrences in dirty files, so the merged index may return stale symbols +// and occurrences from Static index. std::unique_ptr mergeIndex(const SymbolIndex *Dynamic, const SymbolIndex *Static); Index: clangd/index/Merge.cpp =================================================================== --- clangd/index/Merge.cpp +++ clangd/index/Merge.cpp @@ -7,6 +7,8 @@ // //===----------------------------------------------------------------------===// +#include + #include "Merge.h" #include "../Logger.h" #include "llvm/ADT/STLExtras.h" @@ -79,7 +81,26 @@ void findOccurrences(const OccurrencesRequest &Req, llvm::function_ref Callback) const override { - log("findOccurrences is not implemented."); + // We don't want duplicated occurrences from the static/dynamic indexes, + // and we can't reliably duplicate them because occurrence offsets may + // differ slightly. + // We consider the dynamic index authoritative and report all its + // occurrences, and only report static index occurrences from other files. + // + // FIXME: The heuristic fails if the dynamic index containts a file, but all + // occurrences were removed (we will report stale ones from the static + // index). Ultimately we should explicit check which index has the file + // instead. + std::set DynamicIndexFileURIs; + Dynamic->findOccurrences(Req, [&](const SymbolOccurrence &O) { + DynamicIndexFileURIs.insert(O.Location.FileURI); + Callback(O); + }); + Static->findOccurrences(Req, [&](const SymbolOccurrence &O) { + if (DynamicIndexFileURIs.count(O.Location.FileURI)) + return; + Callback(O); + }); } size_t estimateMemoryUsage() const override { Index: clangd/tool/ClangdMain.cpp =================================================================== --- clangd/tool/ClangdMain.cpp +++ clangd/tool/ClangdMain.cpp @@ -54,7 +54,8 @@ SymsBuilder.insert(Sym); return UseDex ? dex::DexIndex::build(std::move(SymsBuilder).build()) - : MemIndex::build(std::move(SymsBuilder).build()); + : MemIndex::build(std::move(SymsBuilder).build(), + SymbolOccurrenceSlab::createEmpty()); } } // namespace Index: unittests/clangd/CodeCompleteTests.cpp =================================================================== --- unittests/clangd/CodeCompleteTests.cpp +++ unittests/clangd/CodeCompleteTests.cpp @@ -76,7 +76,8 @@ SymbolSlab::Builder Slab; for (const auto &Sym : Symbols) Slab.insert(Sym); - return MemIndex::build(std::move(Slab).build()); + return MemIndex::build(std::move(Slab).build(), + SymbolOccurrenceSlab::createEmpty()); } CodeCompleteResult completions(ClangdServer &Server, StringRef TestCode, Index: unittests/clangd/FileIndexTests.cpp =================================================================== --- unittests/clangd/FileIndexTests.cpp +++ unittests/clangd/FileIndexTests.cpp @@ -7,18 +7,28 @@ // //===----------------------------------------------------------------------===// +#include "Annotations.h" #include "ClangdUnit.h" #include "TestFS.h" #include "TestTU.h" +#include "gmock/gmock.h" #include "index/FileIndex.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/PCHContainerOperations.h" #include "clang/Lex/Preprocessor.h" #include "clang/Tooling/CompilationDatabase.h" -#include "gmock/gmock.h" #include "gtest/gtest.h" using testing::UnorderedElementsAre; +using testing::AllOf; + +MATCHER_P(OccurrenceRange, Range, "") { + return std::tie(arg.Location.Start.Line, arg.Location.Start.Column, + arg.Location.End.Line, arg.Location.End.Column) == + std::tie(Range.start.line, Range.start.character, Range.end.line, + Range.end.character); +} +MATCHER_P(FileURI, F, "") { return arg.Location.FileURI == F; } namespace clang { namespace clangd { @@ -39,6 +49,15 @@ return llvm::make_unique(std::move(Slab).build()); } +std::unique_ptr occurrenceSlab(const SymbolID &ID, + llvm::StringRef Path) { + auto Slab = llvm::make_unique(); + SymbolOccurrence Occurrence; + Occurrence.Location.FileURI = Path; + Slab->insert(ID, Occurrence); + return Slab; +} + std::vector getSymbolNames(const std::vector &Symbols) { std::vector Names; @@ -47,19 +66,38 @@ return Names; } +std::vector +getOccurrencePath(const std::vector &Occurrences) { + std::vector Paths; + for (const auto *O : Occurrences) + Paths.push_back(O->Location.FileURI); + return Paths; +} + +std::unique_ptr emptyOccurrence() { + auto EmptySlab = llvm::make_unique(); + EmptySlab->freeze(); + return EmptySlab; +} + TEST(FileSymbolsTest, UpdateAndGet) { FileSymbols FS; EXPECT_THAT(getSymbolNames(*FS.allSymbols()), UnorderedElementsAre()); + EXPECT_TRUE(FS.allOccurrences()->empty()); - FS.update("f1", numSlab(1, 3)); + SymbolID ID("1"); + FS.update("f1", numSlab(1, 3), occurrenceSlab(ID, "f1.cc")); EXPECT_THAT(getSymbolNames(*FS.allSymbols()), UnorderedElementsAre("1", "2", "3")); + auto Occurrences = FS.allOccurrences(); + EXPECT_THAT(getOccurrencePath((*Occurrences)[ID]), + UnorderedElementsAre("f1.cc")); } TEST(FileSymbolsTest, Overlap) { FileSymbols FS; - FS.update("f1", numSlab(1, 3)); - FS.update("f2", numSlab(3, 5)); + FS.update("f1", numSlab(1, 3), emptyOccurrence()); + FS.update("f2", numSlab(3, 5), emptyOccurrence()); EXPECT_THAT(getSymbolNames(*FS.allSymbols()), UnorderedElementsAre("1", "2", "3", "3", "4", "5")); } @@ -67,14 +105,22 @@ TEST(FileSymbolsTest, SnapshotAliveAfterRemove) { FileSymbols FS; - FS.update("f1", numSlab(1, 3)); + SymbolID ID("1"); + FS.update("f1", numSlab(1, 3), occurrenceSlab(ID, "f1.cc")); auto Symbols = FS.allSymbols(); EXPECT_THAT(getSymbolNames(*Symbols), UnorderedElementsAre("1", "2", "3")); + auto Occurrences = FS.allOccurrences(); + EXPECT_THAT(getOccurrencePath((*Occurrences)[ID]), + UnorderedElementsAre("f1.cc")); - FS.update("f1", nullptr); + FS.update("f1", nullptr, nullptr); EXPECT_THAT(getSymbolNames(*FS.allSymbols()), UnorderedElementsAre()); EXPECT_THAT(getSymbolNames(*Symbols), UnorderedElementsAre("1", "2", "3")); + + EXPECT_TRUE(FS.allOccurrences()->empty()); + EXPECT_THAT(getOccurrencePath((*Occurrences)[ID]), + UnorderedElementsAre("f1.cc")); } std::vector match(const SymbolIndex &I, @@ -270,6 +316,52 @@ UnorderedElementsAre("ns_in_header", "ns_in_header::func_in_header")); } +TEST(FileIndexTest, Occurrences) { + const char *HeaderCode = "class Foo {};"; + Annotations MainCode(R"cpp( + void f() { + $foo[[Foo]] foo; + } + )cpp"); + + auto Foo = + findSymbol(TestTU::withHeaderCode(HeaderCode).headerSymbols(), "Foo"); + + OccurrencesRequest Request; + Request.IDs = {Foo.ID}; + Request.Filter = SymbolOccurrenceKind::Declaration | + SymbolOccurrenceKind::Definition | + SymbolOccurrenceKind::Reference; + + FileIndex Index(/*URISchemes*/ {"unittest"}); + // Add test.cc + TestTU Test; + Test.HeaderCode = HeaderCode; + Test.Code = MainCode.code(); + Test.Filename = "test.cc"; + auto AST = Test.build(); + Index.update(Test.Filename, &AST.getASTContext(), AST.getPreprocessorPtr(), + AST.getLocalTopLevelDecls()); + // Add test2.cc + TestTU Test2; + Test2.HeaderCode = HeaderCode; + Test2.Code = MainCode.code(); + Test2.Filename = "test2.cc"; + AST = Test2.build(); + Index.update(Test2.Filename, &AST.getASTContext(), AST.getPreprocessorPtr(), + AST.getLocalTopLevelDecls()); + + std::vector Results; + Index.findOccurrences( + Request, [&Results](const SymbolOccurrence &O) { Results.push_back(O); }); + + EXPECT_THAT(Results, + UnorderedElementsAre(AllOf(OccurrenceRange(MainCode.range("foo")), + FileURI("unittest:///test.cc")), + AllOf(OccurrenceRange(MainCode.range("foo")), + FileURI("unittest:///test2.cc")))); +} + } // namespace } // namespace clangd } // namespace clang Index: unittests/clangd/IndexTests.cpp =================================================================== --- unittests/clangd/IndexTests.cpp +++ unittests/clangd/IndexTests.cpp @@ -7,21 +7,36 @@ // //===----------------------------------------------------------------------===// +#include "Annotations.h" #include "TestIndex.h" +#include "TestTU.h" +#include "gmock/gmock.h" +#include "index/FileIndex.h" #include "index/Index.h" #include "index/MemIndex.h" #include "index/Merge.h" -#include "gmock/gmock.h" #include "gtest/gtest.h" using testing::Pointee; using testing::UnorderedElementsAre; +using testing::AllOf; namespace clang { namespace clangd { namespace { +std::shared_ptr emptyOccurrences() { + return llvm::make_unique(); +} + MATCHER_P(Named, N, "") { return arg.Name == N; } +MATCHER_P(OccurrenceRange, Range, "") { + return std::tie(arg.Location.Start.Line, arg.Location.Start.Column, + arg.Location.End.Line, arg.Location.End.Column) == + std::tie(Range.start.line, Range.start.character, Range.end.line, + Range.end.character); +} +MATCHER_P(FileURI, F, "") { return arg.Location.FileURI == F; } TEST(SymbolSlab, FindAndIterate) { SymbolSlab::Builder B; @@ -42,14 +57,14 @@ TEST(MemIndexTest, MemIndexSymbolsRecycled) { MemIndex I; std::weak_ptr Symbols; - I.build(generateNumSymbols(0, 10, &Symbols)); + I.build(generateNumSymbols(0, 10, &Symbols), emptyOccurrences()); FuzzyFindRequest Req; Req.Query = "7"; EXPECT_THAT(match(I, Req), UnorderedElementsAre("7")); EXPECT_FALSE(Symbols.expired()); // Release old symbols. - I.build(generateNumSymbols(0, 0)); + I.build(generateNumSymbols(0, 0), emptyOccurrences()); EXPECT_TRUE(Symbols.expired()); } @@ -65,14 +80,14 @@ FuzzyFindRequest Req; Req.Query = "7"; MemIndex I; - I.build(std::move(Symbols)); + I.build(std::move(Symbols), emptyOccurrences()); auto Matches = match(I, Req); EXPECT_EQ(Matches.size(), 1u); } TEST(MemIndexTest, MemIndexLimitedNumMatches) { MemIndex I; - I.build(generateNumSymbols(0, 100)); + I.build(generateNumSymbols(0, 100), emptyOccurrences()); FuzzyFindRequest Req; Req.Query = "5"; Req.MaxCandidateCount = 3; @@ -85,7 +100,8 @@ TEST(MemIndexTest, FuzzyMatch) { MemIndex I; I.build( - generateSymbols({"LaughingOutLoud", "LionPopulation", "LittleOldLady"})); + generateSymbols({"LaughingOutLoud", "LionPopulation", "LittleOldLady"}), + emptyOccurrences()); FuzzyFindRequest Req; Req.Query = "lol"; Req.MaxCandidateCount = 2; @@ -95,7 +111,7 @@ TEST(MemIndexTest, MatchQualifiedNamesWithoutSpecificScope) { MemIndex I; - I.build(generateSymbols({"a::y1", "b::y2", "y3"})); + I.build(generateSymbols({"a::y1", "b::y2", "y3"}), emptyOccurrences()); FuzzyFindRequest Req; Req.Query = "y"; EXPECT_THAT(match(I, Req), UnorderedElementsAre("a::y1", "b::y2", "y3")); @@ -103,7 +119,7 @@ TEST(MemIndexTest, MatchQualifiedNamesWithGlobalScope) { MemIndex I; - I.build(generateSymbols({"a::y1", "b::y2", "y3"})); + I.build(generateSymbols({"a::y1", "b::y2", "y3"}), emptyOccurrences()); FuzzyFindRequest Req; Req.Query = "y"; Req.Scopes = {""}; @@ -112,7 +128,8 @@ TEST(MemIndexTest, MatchQualifiedNamesWithOneScope) { MemIndex I; - I.build(generateSymbols({"a::y1", "a::y2", "a::x", "b::y2", "y3"})); + I.build(generateSymbols({"a::y1", "a::y2", "a::x", "b::y2", "y3"}), + emptyOccurrences()); FuzzyFindRequest Req; Req.Query = "y"; Req.Scopes = {"a::"}; @@ -121,7 +138,8 @@ TEST(MemIndexTest, MatchQualifiedNamesWithMultipleScopes) { MemIndex I; - I.build(generateSymbols({"a::y1", "a::y2", "a::x", "b::y3", "y3"})); + I.build(generateSymbols({"a::y1", "a::y2", "a::x", "b::y3", "y3"}), + emptyOccurrences()); FuzzyFindRequest Req; Req.Query = "y"; Req.Scopes = {"a::", "b::"}; @@ -130,7 +148,7 @@ TEST(MemIndexTest, NoMatchNestedScopes) { MemIndex I; - I.build(generateSymbols({"a::y1", "a::b::y2"})); + I.build(generateSymbols({"a::y1", "a::b::y2"}), emptyOccurrences()); FuzzyFindRequest Req; Req.Query = "y"; Req.Scopes = {"a::"}; @@ -139,7 +157,7 @@ TEST(MemIndexTest, IgnoreCases) { MemIndex I; - I.build(generateSymbols({"ns::ABC", "ns::abc"})); + I.build(generateSymbols({"ns::ABC", "ns::abc"}), emptyOccurrences()); FuzzyFindRequest Req; Req.Query = "AB"; Req.Scopes = {"ns::"}; @@ -148,7 +166,7 @@ TEST(MemIndexTest, Lookup) { MemIndex I; - I.build(generateSymbols({"ns::abc", "ns::xyz"})); + I.build(generateSymbols({"ns::abc", "ns::xyz"}), emptyOccurrences()); EXPECT_THAT(lookup(I, SymbolID("ns::abc")), UnorderedElementsAre("ns::abc")); EXPECT_THAT(lookup(I, {SymbolID("ns::abc"), SymbolID("ns::xyz")}), UnorderedElementsAre("ns::abc", "ns::xyz")); @@ -159,8 +177,8 @@ TEST(MergeIndexTest, Lookup) { MemIndex I, J; - I.build(generateSymbols({"ns::A", "ns::B"})); - J.build(generateSymbols({"ns::B", "ns::C"})); + I.build(generateSymbols({"ns::A", "ns::B"}), emptyOccurrences()); + J.build(generateSymbols({"ns::B", "ns::C"}), emptyOccurrences()); EXPECT_THAT(lookup(*mergeIndex(&I, &J), SymbolID("ns::A")), UnorderedElementsAre("ns::A")); EXPECT_THAT(lookup(*mergeIndex(&I, &J), SymbolID("ns::B")), @@ -180,8 +198,8 @@ TEST(MergeIndexTest, FuzzyFind) { MemIndex I, J; - I.build(generateSymbols({"ns::A", "ns::B"})); - J.build(generateSymbols({"ns::B", "ns::C"})); + I.build(generateSymbols({"ns::A", "ns::B"}), emptyOccurrences()); + J.build(generateSymbols({"ns::B", "ns::C"}), emptyOccurrences()); FuzzyFindRequest Req; Req.Scopes = {"ns::"}; EXPECT_THAT(match(*mergeIndex(&I, &J), Req), @@ -234,6 +252,60 @@ EXPECT_EQ(M.Name, "right"); } +TEST(MergeIndexTest, FindOccurrences) { + FileIndex Dyn({"unittest"}); + FileIndex StaticIndex({"unittest"}); + auto MergedIndex = mergeIndex(&Dyn, &StaticIndex); + + const char *HeaderCode = "class Foo;"; + auto HeaderSymbols = TestTU::withHeaderCode("class Foo;").headerSymbols(); + auto Foo = findSymbol(HeaderSymbols, "Foo"); + + // Build dynamic index for test.cc. + Annotations Test1Code(R"(class $Foo[[Foo]];)"); + TestTU Test; + Test.HeaderCode = HeaderCode; + Test.Code = Test1Code.code(); + Test.Filename = "test.cc"; + auto AST = Test.build(); + Dyn.update(Test.Filename, &AST.getASTContext(), AST.getPreprocessorPtr(), + AST.getLocalTopLevelDecls()); + + // Build static index for test.cc. + Test.HeaderCode = HeaderCode; + Test.Code = "// static\nclass Foo {};"; + Test.Filename = "test.cc"; + auto StaticAST = Test.build(); + // Add stale occurrences for test.cc. + StaticIndex.update(Test.Filename, &StaticAST.getASTContext(), + StaticAST.getPreprocessorPtr(), + StaticAST.getLocalTopLevelDecls()); + + // Add occcurrences for test2.cc + Annotations Test2Code(R"(class $Foo[[Foo]] {};)"); + TestTU Test2; + Test2.HeaderCode = HeaderCode; + Test2.Code = Test2Code.code(); + Test2.Filename = "test2.cc"; + StaticAST = Test2.build(); + StaticIndex.update(Test2.Filename, &StaticAST.getASTContext(), + StaticAST.getPreprocessorPtr(), + StaticAST.getLocalTopLevelDecls()); + + OccurrencesRequest Request; + Request.IDs = {Foo.ID}; + Request.Filter = AllOccurrenceKinds; + std::vector Results; + MergedIndex->findOccurrences( + Request, [&](const SymbolOccurrence &O) { Results.push_back(O); }); + + EXPECT_THAT(Results, UnorderedElementsAre( + AllOf(OccurrenceRange(Test1Code.range("Foo")), + FileURI("unittest:///test.cc")), + AllOf(OccurrenceRange(Test2Code.range("Foo")), + FileURI("unittest:///test2.cc")))); +} + } // namespace } // namespace clangd } // namespace clang Index: unittests/clangd/TestTU.cpp =================================================================== --- unittests/clangd/TestTU.cpp +++ unittests/clangd/TestTU.cpp @@ -45,11 +45,12 @@ SymbolSlab TestTU::headerSymbols() const { auto AST = build(); - return indexAST(AST.getASTContext(), AST.getPreprocessorPtr()); + return indexAST(AST.getASTContext(), AST.getPreprocessorPtr()).first; } std::unique_ptr TestTU::index() const { - return MemIndex::build(headerSymbols()); + // FIXME: we should generate proper occurrences for TestTU. + return MemIndex::build(headerSymbols(), SymbolOccurrenceSlab::createEmpty()); } const Symbol &findSymbol(const SymbolSlab &Slab, llvm::StringRef QName) {