[clangd] Implement findOccurrences interface in dynamic index.

Summary:
Implement the interface in
  - FileIndex
  - MemIndex
  - MergeIndex

Depends on https://reviews.llvm.org/D50385.

Reviewers: sammccall, ilya-biryukov

Reviewed By: sammccall

Subscribers: mgrang, ilya-biryukov, ioeric, MaskRay, jkorous, arphaman, kadircet, cfe-commits

Differential Revision: https://reviews.llvm.org/D51279

llvm-svn: 341242
This commit is contained in:
Haojian Wu 2018-08-31 19:53:37 +00:00
parent 4cae04873b
commit e8064b6f6d
12 changed files with 361 additions and 56 deletions

View File

@ -16,9 +16,10 @@
namespace clang {
namespace clangd {
SymbolSlab indexAST(ASTContext &AST, std::shared_ptr<Preprocessor> PP,
llvm::Optional<llvm::ArrayRef<Decl *>> TopLevelDecls,
llvm::ArrayRef<std::string> URISchemes) {
std::pair<SymbolSlab, SymbolOccurrenceSlab>
indexAST(ASTContext &AST, std::shared_ptr<Preprocessor> PP,
llvm::Optional<llvm::ArrayRef<Decl *>> TopLevelDecls,
llvm::ArrayRef<std::string> 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 @@ SymbolSlab indexAST(ASTContext &AST, std::shared_ptr<Preprocessor> PP,
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 @@ SymbolSlab indexAST(ASTContext &AST, std::shared_ptr<Preprocessor> PP,
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<std::string> URISchemes)
: URISchemes(std::move(URISchemes)) {}
void FileSymbols::update(PathRef Path, std::unique_ptr<SymbolSlab> Slab) {
void FileSymbols::update(PathRef Path, std::unique_ptr<SymbolSlab> Slab,
std::unique_ptr<SymbolOccurrenceSlab> Occurrences) {
std::lock_guard<std::mutex> 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<std::vector<const Symbol *>> FileSymbols::allSymbols() {
@ -85,19 +109,47 @@ std::shared_ptr<std::vector<const Symbol *>> FileSymbols::allSymbols() {
return {std::move(Snap), Pointers};
}
std::shared_ptr<MemIndex::OccurrenceMap> 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<std::shared_ptr<SymbolOccurrenceSlab>> KeepAlive;
};
auto Snap = std::make_shared<Snapshot>();
{
std::lock_guard<std::mutex> 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<Preprocessor> PP,
llvm::Optional<llvm::ArrayRef<Decl *>> TopLevelDecls) {
if (!AST) {
FSymbols.update(Path, nullptr);
FSymbols.update(Path, nullptr, nullptr);
} else {
assert(PP);
auto Slab = llvm::make_unique<SymbolSlab>();
*Slab = indexAST(*AST, PP, TopLevelDecls, URISchemes);
FSymbols.update(Path, std::move(Slab));
auto OccurrenceSlab = llvm::make_unique<SymbolOccurrenceSlab>();
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::lookup(
void FileIndex::findOccurrences(
const OccurrencesRequest &Req,
llvm::function_ref<void(const SymbolOccurrence &)> Callback) const {
log("findOccurrences is not implemented.");
Index.findOccurrences(Req, Callback);
}
size_t FileIndex::estimateMemoryUsage() const {

View File

@ -39,18 +39,25 @@ namespace clangd {
/// 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<SymbolSlab> 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<SymbolSlab> Slab,
std::unique_ptr<SymbolOccurrenceSlab> Occurrences);
// The shared_ptr keeps the symbols alive
std::shared_ptr<std::vector<const Symbol *>> allSymbols();
/// Returns all symbol occurrences for all active files.
std::shared_ptr<MemIndex::OccurrenceMap> allOccurrences() const;
private:
mutable std::mutex Mutex;
/// \brief Stores the latest snapshots for all active files.
llvm::StringMap<std::shared_ptr<SymbolSlab>> FileToSlabs;
/// Stores the latest occurrence slabs for all active files.
llvm::StringMap<std::shared_ptr<SymbolOccurrenceSlab>> FileToOccurrenceSlabs;
};
/// \brief This manages symbls from files and an in-memory index on all symbols.
@ -90,12 +97,12 @@ private:
std::vector<std::string> 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<SymbolSlab, SymbolOccurrenceSlab>
indexAST(ASTContext &AST, std::shared_ptr<Preprocessor> PP,
llvm::Optional<llvm::ArrayRef<Decl *>> TopLevelDecls = llvm::None,
llvm::ArrayRef<std::string> URISchemes = {});

View File

@ -344,6 +344,12 @@ public:
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 @@ public:
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);

View File

@ -15,7 +15,27 @@
namespace clang {
namespace clangd {
void MemIndex::build(std::shared_ptr<std::vector<const Symbol *>> Syms) {
static std::shared_ptr<MemIndex::OccurrenceMap>
getOccurrencesFromSlab(SymbolOccurrenceSlab OccurrencesSlab) {
struct Snapshot {
SymbolOccurrenceSlab Slab;
MemIndex::OccurrenceMap Occurrences;
};
auto Snap = std::make_shared<Snapshot>();
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<std::vector<const Symbol *>> Syms,
std::shared_ptr<OccurrenceMap> AllOccurrences) {
assert(Syms && "Syms must be set when build MemIndex");
assert(AllOccurrences && "Occurrences must be set when build MemIndex");
llvm::DenseMap<SymbolID, const Symbol *> TempIndex;
for (const Symbol *Sym : *Syms)
TempIndex[Sym->ID] = Sym;
@ -25,15 +45,18 @@ void MemIndex::build(std::shared_ptr<std::vector<const Symbol *>> Syms) {
std::lock_guard<std::mutex> 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<SymbolIndex> MemIndex::build(SymbolSlab Slab) {
std::unique_ptr<SymbolIndex> MemIndex::build(SymbolSlab Symbols,
SymbolOccurrenceSlab Occurrences) {
auto Idx = llvm::make_unique<MemIndex>();
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::lookup(const LookupRequest &Req,
void MemIndex::findOccurrences(
const OccurrencesRequest &Req,
llvm::function_ref<void(const SymbolOccurrence &)> Callback) const {
log("findOccurrences is not implemented.");
std::lock_guard<std::mutex> 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<int>(Req.Filter & O->Kind))
Callback(*O);
}
}
}
std::shared_ptr<std::vector<const Symbol *>>

View File

@ -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<std::vector<const Symbol *>> Symbols);
/// Maps from a symbol ID to all corresponding symbol occurrences.
/// The map doesn't own occurrence objects.
using OccurrenceMap =
llvm::DenseMap<SymbolID, std::vector<const SymbolOccurrence *>>;
/// \brief Build index from a symbol slab.
static std::unique_ptr<SymbolIndex> build(SymbolSlab Slab);
/// \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<std::vector<const Symbol *>> Symbols,
std::shared_ptr<OccurrenceMap> Occurrences);
/// \brief Build index from a symbol slab and a symbol occurrence slab.
static std::unique_ptr<SymbolIndex> build(SymbolSlab Symbols,
SymbolOccurrenceSlab Occurrences);
bool
fuzzyFind(const FuzzyFindRequest &Req,
@ -47,6 +55,8 @@ private:
// Index is a set of symbols that are deduplicated by symbol IDs.
// FIXME: build smarter index structure.
llvm::DenseMap<SymbolID, const Symbol *> Index;
// A map from symbol ID to symbol occurrences, support query by IDs.
std::shared_ptr<OccurrenceMap> Occurrences;
mutable std::mutex Mutex;
};

View File

@ -7,6 +7,8 @@
//
//===----------------------------------------------------------------------===//
#include <set>
#include "Merge.h"
#include "../Logger.h"
#include "llvm/ADT/STLExtras.h"
@ -79,7 +81,26 @@ class MergedIndex : public SymbolIndex {
void findOccurrences(const OccurrencesRequest &Req,
llvm::function_ref<void(const SymbolOccurrence &)>
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<std::string> 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 {

View File

@ -24,6 +24,10 @@ Symbol mergeSymbol(const Symbol &L, const Symbol &R);
// - 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<SymbolIndex> mergeIndex(const SymbolIndex *Dynamic,
const SymbolIndex *Static);

View File

@ -54,7 +54,8 @@ std::unique_ptr<SymbolIndex> buildStaticIndex(llvm::StringRef YamlSymbolFile) {
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

View File

@ -76,7 +76,8 @@ std::unique_ptr<SymbolIndex> memIndex(std::vector<Symbol> Symbols) {
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,

View File

@ -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 @@ std::unique_ptr<SymbolSlab> numSlab(int Begin, int End) {
return llvm::make_unique<SymbolSlab>(std::move(Slab).build());
}
std::unique_ptr<SymbolOccurrenceSlab> occurrenceSlab(const SymbolID &ID,
llvm::StringRef Path) {
auto Slab = llvm::make_unique<SymbolOccurrenceSlab>();
SymbolOccurrence Occurrence;
Occurrence.Location.FileURI = Path;
Slab->insert(ID, Occurrence);
return Slab;
}
std::vector<std::string>
getSymbolNames(const std::vector<const Symbol *> &Symbols) {
std::vector<std::string> Names;
@ -47,19 +66,38 @@ getSymbolNames(const std::vector<const Symbol *> &Symbols) {
return Names;
}
std::vector<std::string>
getOccurrencePath(const std::vector<const SymbolOccurrence *> &Occurrences) {
std::vector<std::string> Paths;
for (const auto *O : Occurrences)
Paths.push_back(O->Location.FileURI);
return Paths;
}
std::unique_ptr<SymbolOccurrenceSlab> emptyOccurrence() {
auto EmptySlab = llvm::make_unique<SymbolOccurrenceSlab>();
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, Overlap) {
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<std::string> match(const SymbolIndex &I,
@ -270,6 +316,52 @@ TEST(FileIndexTest, RebuildWithPreamble) {
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<SymbolOccurrence> 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

View File

@ -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<MemIndex::OccurrenceMap> emptyOccurrences() {
return llvm::make_unique<MemIndex::OccurrenceMap>();
}
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(SymbolSlab, FindAndIterate) {
TEST(MemIndexTest, MemIndexSymbolsRecycled) {
MemIndex I;
std::weak_ptr<SlabAndPointers> 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 @@ TEST(MemIndexTest, MemIndexDeduplicate) {
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, MemIndexLimitedNumMatches) {
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, FuzzyMatch) {
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, MatchQualifiedNamesWithoutSpecificScope) {
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, MatchQualifiedNamesWithGlobalScope) {
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, MatchQualifiedNamesWithOneScope) {
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, MatchQualifiedNamesWithMultipleScopes) {
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, NoMatchNestedScopes) {
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, IgnoreCases) {
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(MemIndexTest, Lookup) {
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, Lookup) {
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 @@ TEST(MergeTest, PreferSymbolWithDefn) {
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<SymbolOccurrence> 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

View File

@ -45,11 +45,12 @@ ParsedAST TestTU::build() const {
SymbolSlab TestTU::headerSymbols() const {
auto AST = build();
return indexAST(AST.getASTContext(), AST.getPreprocessorPtr());
return indexAST(AST.getASTContext(), AST.getPreprocessorPtr()).first;
}
std::unique_ptr<SymbolIndex> 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) {