[clangd] Collect definitions when indexing.

Within a TU:
 - as now, collect a declaration from the first occurrence of a symbol
   (taking clang's canonical declaration)
 - when we first see a definition occurrence, copy the symbol and add it
Across TUs/sources:
 - mergeSymbol in Merge.h is responsible for combining matching Symbols.
   This covers dynamic/static merges and cross-TU merges in the static index.
 - it prefers declarations from Symbols that have a definition.
 - GlobalSymbolBuilderMain is modified to use mergeSymbol as a reduce step.
Random cleanups (can be pulled out):
 - SymbolFromYAML -> SymbolsFromYAML, new singular SymbolFromYAML added
 - avoid uninit'd SymbolLocations. Add an idiomatic way to check "absent".
 - CanonicalDeclaration (as well as Definition) are mapped as optional in YAML.
 - added operator<< for Symbol & SymbolLocation, for debugging

Reviewers: ioeric, hokein

Subscribers: klimek, ilya-biryukov, jkorous-apple, cfe-commits

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

llvm-svn: 324735
This commit is contained in:
Sam McCall 2018-02-09 14:42:01 +00:00
parent 991af7a558
commit 6003951c66
11 changed files with 299 additions and 150 deletions

View File

@ -14,9 +14,11 @@
//===---------------------------------------------------------------------===//
#include "index/Index.h"
#include "index/Merge.h"
#include "index/SymbolCollector.h"
#include "index/SymbolYAML.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Index/IndexDataConsumer.h"
#include "clang/Index/IndexingAction.h"
#include "clang/Tooling/CommonOptionsParser.h"
@ -34,6 +36,7 @@ using clang::clangd::SymbolSlab;
namespace clang {
namespace clangd {
namespace {
static llvm::cl::opt<std::string> AssumedHeaderDir(
"assume-header-dir",
@ -91,6 +94,25 @@ public:
tooling::ExecutionContext *Ctx;
};
// Combine occurrences of the same symbol across translation units.
SymbolSlab mergeSymbols(tooling::ToolResults *Results) {
SymbolSlab::Builder UniqueSymbols;
llvm::BumpPtrAllocator Arena;
Symbol::Details Scratch;
Results->forEachResult([&](llvm::StringRef Key, llvm::StringRef Value) {
Arena.Reset();
auto Sym = clang::clangd::SymbolFromYAML(Value, Arena);
clang::clangd::SymbolID ID;
Key >> ID;
if (const auto *Existing = UniqueSymbols.find(ID))
UniqueSymbols.insert(mergeSymbol(*Existing, Sym, &Scratch));
else
UniqueSymbols.insert(Sym);
});
return std::move(UniqueSymbols).build();
}
} // namespace
} // namespace clangd
} // namespace clang
@ -115,6 +137,7 @@ int main(int argc, const char **argv) {
return 1;
}
// Map phase: emit symbols found in each translation unit.
auto Err = Executor->get()->execute(
llvm::make_unique<clang::clangd::SymbolIndexActionFactory>(
Executor->get()->getExecutionContext()));
@ -122,22 +145,11 @@ int main(int argc, const char **argv) {
llvm::errs() << llvm::toString(std::move(Err)) << "\n";
}
// Deduplicate the result by key and keep the longest value.
// FIXME(ioeric): Merge occurrences, rather than just dropping all but one.
// Definitions and forward declarations have the same key and may both have
// information. Usage count will need to be aggregated across occurrences,
// too.
llvm::StringMap<llvm::StringRef> UniqueSymbols;
Executor->get()->getToolResults()->forEachResult(
[&UniqueSymbols](llvm::StringRef Key, llvm::StringRef Value) {
auto Ret = UniqueSymbols.try_emplace(Key, Value);
if (!Ret.second) {
// If key already exists, keep the longest value.
llvm::StringRef &V = Ret.first->second;
V = V.size() < Value.size() ? Value : V;
}
});
for (const auto &Sym : UniqueSymbols)
llvm::outs() << Sym.second;
// Reduce phase: combine symbols using the ID as a key.
auto UniqueSymbols =
clang::clangd::mergeSymbols(Executor->get()->getToolResults());
// Output phase: emit YAML for result symbols.
SymbolsToYAML(UniqueSymbols, llvm::outs());
return 0;
}

View File

@ -10,11 +10,18 @@
#include "Index.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/SHA1.h"
#include "llvm/Support/raw_ostream.h"
namespace clang {
namespace clangd {
using namespace llvm;
raw_ostream &operator<<(raw_ostream &OS, const SymbolLocation &L) {
if (!L)
return OS << "(none)";
return OS << L.FileURI << "[" << L.StartOffset << "-" << L.EndOffset << "]";
}
SymbolID::SymbolID(StringRef USR)
: HashValue(SHA1::hash(arrayRefFromStringRef(USR))) {}
@ -29,6 +36,10 @@ void operator>>(StringRef Str, SymbolID &ID) {
std::copy(HexString.begin(), HexString.end(), ID.HashValue.begin());
}
raw_ostream &operator<<(raw_ostream &OS, const Symbol &S) {
return OS << S.Scope << S.Name;
}
SymbolSlab::const_iterator SymbolSlab::find(const SymbolID &ID) const {
auto It = std::lower_bound(Symbols.begin(), Symbols.end(), ID,
[](const Symbol &S, const SymbolID &I) {
@ -55,6 +66,7 @@ static void own(Symbol &S, DenseSet<StringRef> &Strings,
Intern(S.Name);
Intern(S.Scope);
Intern(S.CanonicalDeclaration.FileURI);
Intern(S.Definition.FileURI);
Intern(S.CompletionLabel);
Intern(S.CompletionFilterText);

View File

@ -27,11 +27,14 @@ struct SymbolLocation {
llvm::StringRef FileURI;
// The 0-based offset to the first character of the symbol from the beginning
// of the source file.
unsigned StartOffset;
unsigned StartOffset = 0;
// The 0-based offset to the last character of the symbol from the beginning
// of the source file.
unsigned EndOffset;
unsigned EndOffset = 0;
operator bool() const { return !FileURI.empty(); }
};
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const SymbolLocation &);
// The class identifies a particular C++ symbol (class, function, method, etc).
//
@ -44,7 +47,7 @@ struct SymbolLocation {
class SymbolID {
public:
SymbolID() = default;
SymbolID(llvm::StringRef USR);
explicit SymbolID(llvm::StringRef USR);
bool operator==(const SymbolID &Sym) const {
return HashValue == Sym.HashValue;
@ -117,13 +120,16 @@ struct Symbol {
llvm::StringRef Name;
// The containing namespace. e.g. "" (global), "ns::" (top-level namespace).
llvm::StringRef Scope;
// The location of the canonical declaration of the symbol.
// The location of the symbol's definition, if one was found.
// This covers the whole definition (e.g. class body).
SymbolLocation Definition;
// The location of the preferred declaration of the symbol.
// This may be the same as Definition.
//
// A C++ symbol could have multiple declarations and one definition (e.g.
// a function is declared in ".h" file, and is defined in ".cc" file).
// * For classes, the canonical declaration is usually definition.
// * For non-inline functions, the canonical declaration is a declaration
// (not a definition), which is usually declared in ".h" file.
// A C++ symbol may have multiple declarations, and we pick one to prefer.
// * For classes, the canonical declaration should be the definition.
// * For non-inline functions, the canonical declaration typically appears
// in the ".h" file corresponding to the definition.
SymbolLocation CanonicalDeclaration;
/// A brief description of the symbol that can be displayed in the completion
@ -160,12 +166,14 @@ struct Symbol {
// FIXME: add all occurrences support.
// FIXME: add extra fields for index scoring signals.
};
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Symbol &S);
// An immutable symbol container that stores a set of symbols.
// The container will maintain the lifetime of the symbols.
class SymbolSlab {
public:
using const_iterator = std::vector<Symbol>::const_iterator;
using iterator = const_iterator;
SymbolSlab() = default;

View File

@ -60,32 +60,40 @@ private:
Symbol
mergeSymbol(const Symbol &L, const Symbol &R, Symbol::Details *Scratch) {
assert(L.ID == R.ID);
Symbol S = L;
// For each optional field, fill it from R if missing in L.
// (It might be missing in R too, but that's a no-op).
if (S.CanonicalDeclaration.FileURI == "")
S.CanonicalDeclaration = R.CanonicalDeclaration;
if (S.CompletionLabel == "")
S.CompletionLabel = R.CompletionLabel;
if (S.CompletionFilterText == "")
S.CompletionFilterText = R.CompletionFilterText;
if (S.CompletionPlainInsertText == "")
S.CompletionPlainInsertText = R.CompletionPlainInsertText;
if (S.CompletionSnippetInsertText == "")
S.CompletionSnippetInsertText = R.CompletionSnippetInsertText;
// We prefer information from TUs that saw the definition.
// Classes: this is the def itself. Functions: hopefully the header decl.
// If both did (or both didn't), continue to prefer L over R.
bool PreferR = R.Definition && !L.Definition;
Symbol S = PreferR ? R : L; // The target symbol we're merging into.
const Symbol &O = PreferR ? L : R; // The "other" less-preferred symbol.
if (L.Detail && R.Detail) {
// Copy into scratch space so we can merge.
*Scratch = *L.Detail;
if (Scratch->Documentation == "")
Scratch->Documentation = R.Detail->Documentation;
if (Scratch->CompletionDetail == "")
Scratch->CompletionDetail = R.Detail->CompletionDetail;
S.Detail = Scratch;
} else if (L.Detail)
S.Detail = L.Detail;
else if (R.Detail)
S.Detail = R.Detail;
// For each optional field, fill it from O if missing in S.
// (It might be missing in O too, but that's a no-op).
if (!S.Definition)
S.Definition = O.Definition;
if (!S.CanonicalDeclaration)
S.CanonicalDeclaration = O.CanonicalDeclaration;
if (S.CompletionLabel == "")
S.CompletionLabel = O.CompletionLabel;
if (S.CompletionFilterText == "")
S.CompletionFilterText = O.CompletionFilterText;
if (S.CompletionPlainInsertText == "")
S.CompletionPlainInsertText = O.CompletionPlainInsertText;
if (S.CompletionSnippetInsertText == "")
S.CompletionSnippetInsertText = O.CompletionSnippetInsertText;
if (O.Detail) {
if (S.Detail) {
// Copy into scratch space so we can merge.
*Scratch = *S.Detail;
if (Scratch->Documentation == "")
Scratch->Documentation = O.Detail->Documentation;
if (Scratch->CompletionDetail == "")
Scratch->CompletionDetail = O.Detail->CompletionDetail;
S.Detail = Scratch;
} else
S.Detail = O.Detail;
}
return S;
}

View File

@ -132,13 +132,19 @@ bool shouldFilterDecl(const NamedDecl *ND, ASTContext *ASTCtx,
// For symbols defined inside macros:
// * use expansion location, if the symbol is formed via macro concatenation.
// * use spelling location, otherwise.
//
// FIXME: EndOffset is inclusive (closed range), and should be exclusive.
// FIXME: Because the underlying ranges are token ranges, this code chops the
// last token in half if it contains multiple characters.
// FIXME: We probably want to get just the location of the symbol name, not
// the whole e.g. class.
llvm::Optional<SymbolLocation>
getSymbolLocation(const NamedDecl *D, SourceManager &SM,
getSymbolLocation(const NamedDecl &D, SourceManager &SM,
const SymbolCollector::Options &Opts,
std::string &FileURIStorage) {
SourceLocation Loc = D->getLocation();
SourceLocation StartLoc = SM.getSpellingLoc(D->getLocStart());
SourceLocation EndLoc = SM.getSpellingLoc(D->getLocEnd());
SourceLocation Loc = D.getLocation();
SourceLocation StartLoc = SM.getSpellingLoc(D.getLocStart());
SourceLocation EndLoc = SM.getSpellingLoc(D.getLocEnd());
if (Loc.isMacroID()) {
std::string PrintLoc = SM.getSpellingLoc(Loc).printToString(SM);
if (llvm::StringRef(PrintLoc).startswith("<scratch") ||
@ -149,8 +155,8 @@ getSymbolLocation(const NamedDecl *D, SourceManager &SM,
// be "<scratch space>"
// * symbols controlled and defined by a compile command-line option
// `-DName=foo`, the spelling location will be "<command line>".
StartLoc = SM.getExpansionRange(D->getLocStart()).first;
EndLoc = SM.getExpansionRange(D->getLocEnd()).second;
StartLoc = SM.getExpansionRange(D.getLocStart()).first;
EndLoc = SM.getExpansionRange(D.getLocEnd()).second;
}
}
@ -158,8 +164,11 @@ getSymbolLocation(const NamedDecl *D, SourceManager &SM,
if (!U)
return llvm::None;
FileURIStorage = std::move(*U);
return SymbolLocation{FileURIStorage, SM.getFileOffset(StartLoc),
SM.getFileOffset(EndLoc)};
SymbolLocation Result;
Result.FileURI = FileURIStorage;
Result.StartOffset = SM.getFileOffset(StartLoc);
Result.EndOffset = SM.getFileOffset(EndLoc);
return std::move(Result);
}
} // namespace
@ -195,63 +204,87 @@ bool SymbolCollector::handleDeclOccurence(
return true;
auto ID = SymbolID(USR);
if (Symbols.find(ID) != nullptr)
return true;
auto &SM = ND->getASTContext().getSourceManager();
std::string QName;
llvm::raw_string_ostream OS(QName);
PrintingPolicy Policy(ASTCtx->getLangOpts());
// Note that inline namespaces are treated as transparent scopes. This
// reflects the way they're most commonly used for lookup. Ideally we'd
// include them, but at query time it's hard to find all the inline
// namespaces to query: the preamble doesn't have a dedicated list.
Policy.SuppressUnwrittenScope = true;
ND->printQualifiedName(OS, Policy);
OS.flush();
Symbol S;
S.ID = std::move(ID);
std::tie(S.Scope, S.Name) = splitQualifiedName(QName);
S.SymInfo = index::getSymbolInfo(D);
std::string URIStorage;
if (auto DeclLoc = getSymbolLocation(ND, SM, Opts, URIStorage))
S.CanonicalDeclaration = *DeclLoc;
// Add completion info.
assert(ASTCtx && PP.get() && "ASTContext and Preprocessor must be set.");
CodeCompletionResult SymbolCompletion(ND, 0);
const auto *CCS = SymbolCompletion.CreateCodeCompletionString(
*ASTCtx, *PP, CodeCompletionContext::CCC_Name, *CompletionAllocator,
*CompletionTUInfo,
/*IncludeBriefComments*/ true);
std::string Label;
std::string SnippetInsertText;
std::string IgnoredLabel;
std::string PlainInsertText;
getLabelAndInsertText(*CCS, &Label, &SnippetInsertText,
/*EnableSnippets=*/true);
getLabelAndInsertText(*CCS, &IgnoredLabel, &PlainInsertText,
/*EnableSnippets=*/false);
std::string FilterText = getFilterText(*CCS);
std::string Documentation = getDocumentation(*CCS);
std::string CompletionDetail = getDetail(*CCS);
S.CompletionFilterText = FilterText;
S.CompletionLabel = Label;
S.CompletionPlainInsertText = PlainInsertText;
S.CompletionSnippetInsertText = SnippetInsertText;
Symbol::Details Detail;
Detail.Documentation = Documentation;
Detail.CompletionDetail = CompletionDetail;
S.Detail = &Detail;
Symbols.insert(S);
const Symbol* BasicSymbol = Symbols.find(ID);
if (!BasicSymbol) // Regardless of role, ND is the canonical declaration.
BasicSymbol = addDeclaration(*ND, std::move(ID));
if (Roles & static_cast<unsigned>(index::SymbolRole::Definition))
addDefinition(*cast<NamedDecl>(ASTNode.OrigD), *BasicSymbol);
}
return true;
}
const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND,
SymbolID ID) {
auto &SM = ND.getASTContext().getSourceManager();
std::string QName;
llvm::raw_string_ostream OS(QName);
PrintingPolicy Policy(ASTCtx->getLangOpts());
// Note that inline namespaces are treated as transparent scopes. This
// reflects the way they're most commonly used for lookup. Ideally we'd
// include them, but at query time it's hard to find all the inline
// namespaces to query: the preamble doesn't have a dedicated list.
Policy.SuppressUnwrittenScope = true;
ND.printQualifiedName(OS, Policy);
OS.flush();
Symbol S;
S.ID = std::move(ID);
std::tie(S.Scope, S.Name) = splitQualifiedName(QName);
S.SymInfo = index::getSymbolInfo(&ND);
std::string FileURI;
// FIXME: we may want a different "canonical" heuristic than clang chooses.
// Clang seems to choose the first, which may not have the most information.
if (auto DeclLoc = getSymbolLocation(ND, SM, Opts, FileURI))
S.CanonicalDeclaration = *DeclLoc;
// Add completion info.
// FIXME: we may want to choose a different redecl, or combine from several.
assert(ASTCtx && PP.get() && "ASTContext and Preprocessor must be set.");
CodeCompletionResult SymbolCompletion(&ND, 0);
const auto *CCS = SymbolCompletion.CreateCodeCompletionString(
*ASTCtx, *PP, CodeCompletionContext::CCC_Name, *CompletionAllocator,
*CompletionTUInfo,
/*IncludeBriefComments*/ true);
std::string Label;
std::string SnippetInsertText;
std::string IgnoredLabel;
std::string PlainInsertText;
getLabelAndInsertText(*CCS, &Label, &SnippetInsertText,
/*EnableSnippets=*/true);
getLabelAndInsertText(*CCS, &IgnoredLabel, &PlainInsertText,
/*EnableSnippets=*/false);
std::string FilterText = getFilterText(*CCS);
std::string Documentation = getDocumentation(*CCS);
std::string CompletionDetail = getDetail(*CCS);
S.CompletionFilterText = FilterText;
S.CompletionLabel = Label;
S.CompletionPlainInsertText = PlainInsertText;
S.CompletionSnippetInsertText = SnippetInsertText;
Symbol::Details Detail;
Detail.Documentation = Documentation;
Detail.CompletionDetail = CompletionDetail;
S.Detail = &Detail;
Symbols.insert(S);
return Symbols.find(S.ID);
}
void SymbolCollector::addDefinition(const NamedDecl &ND,
const Symbol &DeclSym) {
if (DeclSym.Definition)
return;
// If we saw some forward declaration, we end up copying the symbol.
// This is not ideal, but avoids duplicating the "is this a definition" check
// in clang::index. We should only see one definition.
Symbol S = DeclSym;
std::string FileURI;
if (auto DefLoc = getSymbolLocation(ND, ND.getASTContext().getSourceManager(),
Opts, FileURI))
S.Definition = *DefLoc;
Symbols.insert(S);
}
} // namespace clangd
} // namespace clang

View File

@ -58,6 +58,9 @@ public:
SymbolSlab takeSymbols() { return std::move(Symbols).build(); }
private:
const Symbol *addDeclaration(const NamedDecl &, SymbolID);
void addDefinition(const NamedDecl &, const Symbol &DeclSymbol);
// All Symbols collected from the AST.
SymbolSlab::Builder Symbols;
ASTContext *ASTCtx;

View File

@ -97,7 +97,9 @@ template <> struct MappingTraits<Symbol> {
IO.mapRequired("Name", Sym.Name);
IO.mapRequired("Scope", Sym.Scope);
IO.mapRequired("SymInfo", Sym.SymInfo);
IO.mapRequired("CanonicalDeclaration", Sym.CanonicalDeclaration);
IO.mapOptional("CanonicalDeclaration", Sym.CanonicalDeclaration,
SymbolLocation());
IO.mapOptional("Definition", Sym.Definition, SymbolLocation());
IO.mapRequired("CompletionLabel", Sym.CompletionLabel);
IO.mapRequired("CompletionFilterText", Sym.CompletionFilterText);
IO.mapRequired("CompletionPlainInsertText", Sym.CompletionPlainInsertText);
@ -160,7 +162,7 @@ template <> struct ScalarEnumerationTraits<SymbolKind> {
namespace clang {
namespace clangd {
SymbolSlab SymbolFromYAML(llvm::StringRef YAMLContent) {
SymbolSlab SymbolsFromYAML(llvm::StringRef YAMLContent) {
// Store data of pointer fields (excl. `StringRef`) like `Detail`.
llvm::BumpPtrAllocator Arena;
llvm::yaml::Input Yin(YAMLContent, &Arena);
@ -173,13 +175,18 @@ SymbolSlab SymbolFromYAML(llvm::StringRef YAMLContent) {
return std::move(Syms).build();
}
std::string SymbolsToYAML(const SymbolSlab& Symbols) {
std::string Str;
llvm::raw_string_ostream OS(Str);
Symbol SymbolFromYAML(llvm::StringRef YAMLContent,
llvm::BumpPtrAllocator &Arena) {
llvm::yaml::Input Yin(YAMLContent, &Arena);
Symbol S;
Yin >> S;
return S;
}
void SymbolsToYAML(const SymbolSlab& Symbols, llvm::raw_ostream &OS) {
llvm::yaml::Output Yout(OS);
for (Symbol S : Symbols) // copy: Yout<< requires mutability.
Yout << S;
return OS.str();
}
std::string SymbolToYAML(Symbol Sym) {

View File

@ -20,12 +20,17 @@
#include "Index.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/raw_ostream.h"
namespace clang {
namespace clangd {
// Read symbols from a YAML-format string.
SymbolSlab SymbolFromYAML(llvm::StringRef YAMLContent);
SymbolSlab SymbolsFromYAML(llvm::StringRef YAMLContent);
// Read one symbol from a YAML-format string, backed by the arena.
Symbol SymbolFromYAML(llvm::StringRef YAMLContent,
llvm::BumpPtrAllocator &Arena);
// Convert a single symbol to YAML-format string.
// The YAML result is safe to concatenate.
@ -33,7 +38,7 @@ std::string SymbolToYAML(Symbol Sym);
// Convert symbols to a YAML-format string.
// The YAML result is safe to concatenate if you have multiple symbol slabs.
std::string SymbolsToYAML(const SymbolSlab& Symbols);
void SymbolsToYAML(const SymbolSlab& Symbols, llvm::raw_ostream &OS);
} // namespace clangd
} // namespace clang

View File

@ -37,7 +37,7 @@ std::unique_ptr<SymbolIndex> BuildStaticIndex(llvm::StringRef YamlSymbolFile) {
llvm::errs() << "Can't open " << YamlSymbolFile << "\n";
return nullptr;
}
auto Slab = SymbolFromYAML(Buffer.get()->getBuffer());
auto Slab = SymbolsFromYAML(Buffer.get()->getBuffer());
SymbolSlab::Builder SymsBuilder;
for (auto Sym : Slab)
SymsBuilder.insert(Sym);

View File

@ -248,6 +248,28 @@ TEST(MergeTest, Merge) {
EXPECT_EQ(M.Detail->Documentation, "--doc--");
}
TEST(MergeTest, PreferSymbolWithDefn) {
Symbol L, R;
Symbol::Details Scratch;
L.ID = R.ID = SymbolID("hello");
L.CanonicalDeclaration.FileURI = "file:/left.h";
R.CanonicalDeclaration.FileURI = "file:/right.h";
L.CompletionPlainInsertText = "left-insert";
R.CompletionPlainInsertText = "right-insert";
Symbol M = mergeSymbol(L, R, &Scratch);
EXPECT_EQ(M.CanonicalDeclaration.FileURI, "file:/left.h");
EXPECT_EQ(M.Definition.FileURI, "");
EXPECT_EQ(M.CompletionPlainInsertText, "left-insert");
R.Definition.FileURI = "file:/right.cpp"; // Now right will be favored.
M = mergeSymbol(L, R, &Scratch);
EXPECT_EQ(M.CanonicalDeclaration.FileURI, "file:/right.h");
EXPECT_EQ(M.Definition.FileURI, "file:/right.cpp");
EXPECT_EQ(M.CompletionPlainInsertText, "right-insert");
}
} // namespace
} // namespace clangd
} // namespace clang

View File

@ -47,12 +47,17 @@ MATCHER_P(Snippet, S, "") {
}
MATCHER_P(QName, Name, "") { return (arg.Scope + arg.Name).str() == Name; }
MATCHER_P(DeclURI, P, "") { return arg.CanonicalDeclaration.FileURI == P; }
MATCHER_P(LocationOffsets, Offsets, "") {
MATCHER_P(DeclRange, Offsets, "") {
// Offset range in SymbolLocation is [start, end] while in Clangd is [start,
// end).
// FIXME: SymbolLocation should be [start, end).
return arg.CanonicalDeclaration.StartOffset == Offsets.first &&
arg.CanonicalDeclaration.EndOffset == Offsets.second - 1;
}
MATCHER_P(DefRange, Offsets, "") {
return arg.Definition.StartOffset == Offsets.first &&
arg.Definition.EndOffset == Offsets.second - 1;
}
namespace clang {
namespace clangd {
@ -97,7 +102,8 @@ public:
auto Factory = llvm::make_unique<SymbolIndexActionFactory>(CollectorOpts);
std::vector<std::string> Args = {"symbol_collector", "-fsyntax-only",
"-std=c++11", TestFileName};
"-std=c++11", "-include",
TestHeaderName, TestFileName};
Args.insert(Args.end(), ExtraArgs.begin(), ExtraArgs.end());
tooling::ToolInvocation Invocation(
Args,
@ -106,14 +112,8 @@ public:
InMemoryFileSystem->addFile(TestHeaderName, 0,
llvm::MemoryBuffer::getMemBuffer(HeaderCode));
std::string Content = MainCode;
if (!HeaderCode.empty())
Content = (llvm::Twine("#include\"") +
llvm::sys::path::filename(TestHeaderName) + "\"\n" + Content)
.str();
InMemoryFileSystem->addFile(TestFileName, 0,
llvm::MemoryBuffer::getMemBuffer(Content));
llvm::MemoryBuffer::getMemBuffer(MainCode));
Invocation.run();
Symbols = Factory->Collector->takeSymbols();
return true;
@ -176,6 +176,42 @@ TEST_F(SymbolCollectorTest, CollectSymbols) {
QName("foo::bar::v2"), QName("foo::baz")}));
}
TEST_F(SymbolCollectorTest, Locations) {
// FIXME: the behavior here is bad: chopping tokens, including more than the
// ident range, using half-open ranges. See fixmes in getSymbolLocation().
CollectorOpts.IndexMainFiles = true;
Annotations Header(R"cpp(
// Declared in header, defined in main.
$xdecl[[extern int X]];
$clsdecl[[class C]]ls;
$printdecl[[void print()]];
// Declared in header, defined nowhere.
$zdecl[[extern int Z]];
)cpp");
Annotations Main(R"cpp(
$xdef[[int X = 4]]2;
$clsdef[[class Cls {}]];
$printdef[[void print() {}]]
// Declared/defined in main only.
$y[[int Y]];
)cpp");
runSymbolCollector(Header.code(), Main.code());
EXPECT_THAT(
Symbols,
UnorderedElementsAre(
AllOf(QName("X"), DeclRange(Header.offsetRange("xdecl")),
DefRange(Main.offsetRange("xdef"))),
AllOf(QName("Cls"), DeclRange(Header.offsetRange("clsdecl")),
DefRange(Main.offsetRange("clsdef"))),
AllOf(QName("print"), DeclRange(Header.offsetRange("printdecl")),
DefRange(Main.offsetRange("printdef"))),
AllOf(QName("Z"), DeclRange(Header.offsetRange("zdecl"))),
AllOf(QName("Y"), DeclRange(Main.offsetRange("y")),
DefRange(Main.offsetRange("y")))));
}
TEST_F(SymbolCollectorTest, SymbolRelativeNoFallback) {
CollectorOpts.IndexMainFiles = false;
runSymbolCollector("class Foo {};", /*Main=*/"");
@ -280,10 +316,9 @@ TEST_F(SymbolCollectorTest, SymbolFormedFromMacro) {
EXPECT_THAT(
Symbols,
UnorderedElementsAre(
AllOf(QName("abc_Test"),
LocationOffsets(Header.offsetRange("expansion")),
AllOf(QName("abc_Test"), DeclRange(Header.offsetRange("expansion")),
DeclURI(TestHeaderURI)),
AllOf(QName("Test"), LocationOffsets(Header.offsetRange("spelling")),
AllOf(QName("Test"), DeclRange(Header.offsetRange("spelling")),
DeclURI(TestHeaderURI))));
}
@ -302,13 +337,13 @@ TEST_F(SymbolCollectorTest, SymbolFormedFromMacroInMainFile) {
FF2();
)");
runSymbolCollector(/*Header=*/"", Main.code());
EXPECT_THAT(Symbols, UnorderedElementsAre(
AllOf(QName("abc_Test"),
LocationOffsets(Main.offsetRange("expansion")),
DeclURI(TestFileURI)),
AllOf(QName("Test"),
LocationOffsets(Main.offsetRange("spelling")),
DeclURI(TestFileURI))));
EXPECT_THAT(
Symbols,
UnorderedElementsAre(
AllOf(QName("abc_Test"), DeclRange(Main.offsetRange("expansion")),
DeclURI(TestFileURI)),
AllOf(QName("Test"), DeclRange(Main.offsetRange("spelling")),
DeclURI(TestFileURI))));
}
TEST_F(SymbolCollectorTest, SymbolFormedByCLI) {
@ -322,10 +357,10 @@ TEST_F(SymbolCollectorTest, SymbolFormedByCLI) {
runSymbolCollector(Header.code(), /*Main=*/"",
/*ExtraArgs=*/{"-DNAME=name"});
EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(
QName("name"),
LocationOffsets(Header.offsetRange("expansion")),
DeclURI(TestHeaderURI))));
EXPECT_THAT(Symbols,
UnorderedElementsAre(AllOf(
QName("name"), DeclRange(Header.offsetRange("expansion")),
DeclURI(TestHeaderURI))));
}
TEST_F(SymbolCollectorTest, IgnoreSymbolsInMainFile) {
@ -503,19 +538,23 @@ CompletionSnippetInsertText: 'snippet'
...
)";
auto Symbols1 = SymbolFromYAML(YAML1);
auto Symbols1 = SymbolsFromYAML(YAML1);
EXPECT_THAT(Symbols1,
UnorderedElementsAre(AllOf(
QName("clang::Foo1"), Labeled("Foo1-label"), Doc("Foo doc"),
Detail("int"), DeclURI("file:///path/foo.h"))));
auto Symbols2 = SymbolFromYAML(YAML2);
auto Symbols2 = SymbolsFromYAML(YAML2);
EXPECT_THAT(Symbols2, UnorderedElementsAre(AllOf(
QName("clang::Foo2"), Labeled("Foo2-label"),
Not(HasDetail()), DeclURI("file:///path/bar.h"))));
std::string ConcatenatedYAML =
SymbolsToYAML(Symbols1) + SymbolsToYAML(Symbols2);
auto ConcatenatedSymbols = SymbolFromYAML(ConcatenatedYAML);
std::string ConcatenatedYAML;
{
llvm::raw_string_ostream OS(ConcatenatedYAML);
SymbolsToYAML(Symbols1, OS);
SymbolsToYAML(Symbols2, OS);
}
auto ConcatenatedSymbols = SymbolsFromYAML(ConcatenatedYAML);
EXPECT_THAT(ConcatenatedSymbols,
UnorderedElementsAre(QName("clang::Foo1"),
QName("clang::Foo2")));