[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:
parent
4cae04873b
commit
e8064b6f6d
@ -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 {
|
||||
|
||||
@ -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 = {});
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 *>>
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user