[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:
parent
991af7a558
commit
6003951c66
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")));
|
||||
|
|
Loading…
Reference in New Issue