#include "Merge.h"
#include "index/Symbol.h"
#include "index/SymbolLocation.h"
#include "index/SymbolOrigin.h"
#include "support/Trace.h"
#include "llvm/ADT/StringRef.h"
#include <algorithm>
#include <iterator>
namespace clang {
namespace clangd {
namespace {
bool isIndexAuthoritative(const SymbolIndex::IndexedFiles &Index,
const Symbol &S) {
const char *OwningFile =
S.Definition ? S.Definition.FileURI : S.CanonicalDeclaration.FileURI;
return (Index(OwningFile) & IndexContents::Symbols) != IndexContents::None;
}
}
bool MergedIndex::fuzzyFind(
const FuzzyFindRequest &Req,
llvm::function_ref<void(const Symbol &)> Callback) const {
trace::Span Tracer("MergedIndex fuzzyFind");
bool More = false;
SymbolSlab::Builder DynB;
unsigned DynamicCount = 0;
unsigned StaticCount = 0;
unsigned MergedCount = 0;
unsigned StaticDropped = 0;
More |= Dynamic->fuzzyFind(Req, [&](const Symbol &S) {
++DynamicCount;
DynB.insert(S);
});
SymbolSlab Dyn = std::move(DynB).build();
llvm::DenseSet<SymbolID> ReportedDynSymbols;
{
auto DynamicContainsFile = Dynamic->indexedFiles();
More |= Static->fuzzyFind(Req, [&](const Symbol &S) {
++StaticCount;
auto DynS = Dyn.find(S.ID);
if (DynS != Dyn.end()) {
++MergedCount;
ReportedDynSymbols.insert(S.ID);
return Callback(mergeSymbol(*DynS, S));
}
if (isIndexAuthoritative(DynamicContainsFile, S)) {
++StaticDropped;
return;
}
return Callback(S);
});
}
SPAN_ATTACH(Tracer, "dynamic", DynamicCount);
SPAN_ATTACH(Tracer, "static", StaticCount);
SPAN_ATTACH(Tracer, "static_dropped", StaticDropped);
SPAN_ATTACH(Tracer, "merged", MergedCount);
for (const Symbol &S : Dyn)
if (!ReportedDynSymbols.count(S.ID))
Callback(S);
return More;
}
void MergedIndex::lookup(
const LookupRequest &Req,
llvm::function_ref<void(const Symbol &)> Callback) const {
trace::Span Tracer("MergedIndex lookup");
SymbolSlab::Builder B;
Dynamic->lookup(Req, [&](const Symbol &S) { B.insert(S); });
auto RemainingIDs = Req.IDs;
{
auto DynamicContainsFile = Dynamic->indexedFiles();
Static->lookup(Req, [&](const Symbol &S) {
if (const Symbol *Sym = B.find(S.ID)) {
RemainingIDs.erase(S.ID);
return Callback(mergeSymbol(*Sym, S));
}
if (isIndexAuthoritative(DynamicContainsFile, S))
return;
RemainingIDs.erase(S.ID);
Callback(S);
});
}
for (const auto &ID : RemainingIDs)
if (const Symbol *Sym = B.find(ID))
Callback(*Sym);
}
bool MergedIndex::refs(const RefsRequest &Req,
llvm::function_ref<void(const Ref &)> Callback) const {
trace::Span Tracer("MergedIndex refs");
bool More = false;
uint32_t Remaining = Req.Limit.value_or(std::numeric_limits<uint32_t>::max());
More |= Dynamic->refs(Req, [&](const Ref &O) {
Callback(O);
assert(Remaining != 0);
--Remaining;
});
if (Remaining == 0 && More)
return More;
auto DynamicContainsFile = Dynamic->indexedFiles();
bool StaticHadMore = Static->refs(Req, [&](const Ref &O) {
if ((DynamicContainsFile(O.Location.FileURI) & IndexContents::References) !=
IndexContents::None)
return;
if (Remaining == 0) {
More = true;
return;
}
--Remaining;
Callback(O);
});
return More || StaticHadMore;
}
llvm::unique_function<IndexContents(llvm::StringRef) const>
MergedIndex::indexedFiles() const {
return [DynamicContainsFile{Dynamic->indexedFiles()},
StaticContainsFile{Static->indexedFiles()}](llvm::StringRef FileURI) {
return DynamicContainsFile(FileURI) | StaticContainsFile(FileURI);
};
}
void MergedIndex::relations(
const RelationsRequest &Req,
llvm::function_ref<void(const SymbolID &, const Symbol &)> Callback) const {
uint32_t Remaining = Req.Limit.value_or(std::numeric_limits<uint32_t>::max());
llvm::DenseSet<std::pair<SymbolID, SymbolID>> SeenRelations;
Dynamic->relations(Req, [&](const SymbolID &Subject, const Symbol &Object) {
Callback(Subject, Object);
SeenRelations.insert(std::make_pair(Subject, Object.ID));
--Remaining;
});
if (Remaining == 0)
return;
Static->relations(Req, [&](const SymbolID &Subject, const Symbol &Object) {
if (Remaining > 0 &&
!SeenRelations.count(std::make_pair(Subject, Object.ID))) {
--Remaining;
Callback(Subject, Object);
}
});
}
static bool prefer(const SymbolLocation &L, const SymbolLocation &R) {
if (!L)
return false;
if (!R)
return true;
auto HasCodeGenSuffix = [](const SymbolLocation &Loc) {
constexpr static const char *CodegenSuffixes[] = {".proto"};
return llvm::any_of(CodegenSuffixes, [&](llvm::StringRef Suffix) {
return llvm::StringRef(Loc.FileURI).ends_with(Suffix);
});
};
return HasCodeGenSuffix(L) && !HasCodeGenSuffix(R);
}
Symbol mergeSymbol(const Symbol &L, const Symbol &R) {
assert(L.ID == R.ID);
bool PreferR = R.Definition && !L.Definition;
assert(L.Definition.FileURI && R.Definition.FileURI);
bool MergeIncludes =
bool(*L.Definition.FileURI) == bool(*R.Definition.FileURI);
Symbol S = PreferR ? R : L;
const Symbol &O = PreferR ? L : R;
if (prefer(O.CanonicalDeclaration, S.CanonicalDeclaration))
S.CanonicalDeclaration = O.CanonicalDeclaration;
if (prefer(O.Definition, S.Definition))
S.Definition = O.Definition;
S.References += O.References;
if (S.Signature == "")
S.Signature = O.Signature;
if (S.CompletionSnippetSuffix == "")
S.CompletionSnippetSuffix = O.CompletionSnippetSuffix;
if (S.Documentation == "") {
bool IsClass = S.SymInfo.Kind == index::SymbolKind::Class ||
S.SymInfo.Kind == index::SymbolKind::Struct ||
S.SymInfo.Kind == index::SymbolKind::Union;
if (!IsClass || !S.Definition)
S.Documentation = O.Documentation;
}
if (S.ReturnType == "")
S.ReturnType = O.ReturnType;
if (S.Type == "")
S.Type = O.Type;
for (const auto &OI : O.IncludeHeaders) {
bool Found = false;
for (auto &SI : S.IncludeHeaders) {
if (SI.IncludeHeader == OI.IncludeHeader) {
Found = true;
SI.References += OI.References;
SI.SupportedDirectives |= OI.SupportedDirectives;
break;
}
}
if (!Found && MergeIncludes)
S.IncludeHeaders.emplace_back(OI.IncludeHeader, OI.References,
OI.supportedDirectives());
}
S.Origin |= O.Origin | SymbolOrigin::Merge;
S.Flags |= O.Flags;
return S;
}
}
}