#include "Dex.h"
#include "FileDistance.h"
#include "FuzzyMatch.h"
#include "Quality.h"
#include "URI.h"
#include "index/Index.h"
#include "index/dex/Iterator.h"
#include "index/dex/Token.h"
#include "index/dex/Trigram.h"
#include "support/Logger.h"
#include "support/Trace.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/ScopedPrinter.h"
#include <algorithm>
#include <optional>
#include <queue>
#include <utility>
#include <vector>
namespace clang {
namespace clangd {
namespace dex {
std::unique_ptr<SymbolIndex> Dex::build(SymbolSlab Symbols, RefSlab Refs,
RelationSlab Rels) {
auto Size = Symbols.bytes() + Refs.bytes();
auto Data = std::make_pair(std::move(Symbols), std::move(Refs));
return std::make_unique<Dex>(Data.first, Data.second, Rels, std::move(Data),
Size);
}
namespace {
const Token RestrictedForCodeCompletion =
Token(Token::Kind::Sentinel, "Restricted For Code Completion");
class IndexBuilder {
llvm::DenseMap<Trigram, std::vector<DocID>> TrigramDocs;
std::vector<DocID> RestrictedCCDocs;
llvm::StringMap<std::vector<DocID>> TypeDocs;
llvm::StringMap<std::vector<DocID>> ScopeDocs;
llvm::StringMap<std::vector<DocID>> ProximityDocs;
std::vector<Trigram> TrigramScratch;
public:
void add(const Symbol &Sym, DocID D) {
generateIdentifierTrigrams(Sym.Name, TrigramScratch);
for (Trigram T : TrigramScratch)
TrigramDocs[T].push_back(D);
ScopeDocs[Sym.Scope].push_back(D);
if (!llvm::StringRef(Sym.CanonicalDeclaration.FileURI).empty())
for (const auto &ProximityURI :
generateProximityURIs(Sym.CanonicalDeclaration.FileURI))
ProximityDocs[ProximityURI].push_back(D);
if (Sym.Flags & Symbol::IndexedForCodeCompletion)
RestrictedCCDocs.push_back(D);
if (!Sym.Type.empty())
TypeDocs[Sym.Type].push_back(D);
}
llvm::DenseMap<Token, PostingList> build() && {
llvm::DenseMap<Token, PostingList> Result(
TrigramDocs.size() +
RestrictedCCDocs.size() +
TypeDocs.size() +
ScopeDocs.size() +
ProximityDocs.size());
auto CreatePostingList =
[&Result](Token::Kind TK, llvm::StringMap<std::vector<DocID>> &Docs) {
for (auto &E : Docs) {
Result.try_emplace(Token(TK, E.first()), E.second);
E.second = {};
}
Docs = {};
};
CreatePostingList(Token::Kind::Type, TypeDocs);
CreatePostingList(Token::Kind::Scope, ScopeDocs);
CreatePostingList(Token::Kind::ProximityURI, ProximityDocs);
for (auto &E : TrigramDocs) {
Result.try_emplace(Token(Token::Kind::Trigram, E.first.str()), E.second);
E.second = {};
}
TrigramDocs = llvm::DenseMap<Trigram, std::vector<DocID>>{};
if (!RestrictedCCDocs.empty())
Result.try_emplace(RestrictedForCodeCompletion,
std::move(RestrictedCCDocs));
return Result;
}
};
}
void Dex::buildIndex() {
this->Corpus = dex::Corpus(Symbols.size());
std::vector<std::pair<float, const Symbol *>> ScoredSymbols(Symbols.size());
for (size_t I = 0; I < Symbols.size(); ++I) {
const Symbol *Sym = Symbols[I];
LookupTable[Sym->ID] = Sym;
ScoredSymbols[I] = {quality(*Sym), Sym};
}
llvm::sort(ScoredSymbols, std::greater<std::pair<float, const Symbol *>>());
SymbolQuality.resize(Symbols.size());
for (size_t I = 0; I < ScoredSymbols.size(); ++I) {
SymbolQuality[I] = ScoredSymbols[I].first;
Symbols[I] = ScoredSymbols[I].second;
}
IndexBuilder Builder;
for (DocID SymbolRank = 0; SymbolRank < Symbols.size(); ++SymbolRank)
Builder.add(*Symbols[SymbolRank], SymbolRank);
InvertedIndex = std::move(Builder).build();
}
std::unique_ptr<Iterator> Dex::iterator(const Token &Tok) const {
auto It = InvertedIndex.find(Tok);
return It == InvertedIndex.end() ? Corpus.none()
: It->second.iterator(&It->first);
}
std::unique_ptr<Iterator> Dex::createFileProximityIterator(
llvm::ArrayRef<std::string> ProximityPaths) const {
std::vector<std::unique_ptr<Iterator>> BoostingIterators;
llvm::StringSet<> ParentURIs;
llvm::StringMap<SourceParams> Sources;
for (const auto &Path : ProximityPaths) {
Sources[Path] = SourceParams();
auto PathURI = URI::create(Path).toString();
const auto PathProximityURIs = generateProximityURIs(PathURI.c_str());
for (const auto &ProximityURI : PathProximityURIs)
ParentURIs.insert(ProximityURI);
}
SymbolRelevanceSignals PathProximitySignals;
URIDistance DistanceCalculator(Sources);
PathProximitySignals.FileProximityMatch = &DistanceCalculator;
for (const auto &ParentURI : ParentURIs.keys()) {
auto It = iterator(Token(Token::Kind::ProximityURI, ParentURI));
if (It->kind() != Iterator::Kind::False) {
PathProximitySignals.SymbolURI = ParentURI;
BoostingIterators.push_back(Corpus.boost(
std::move(It), PathProximitySignals.evaluateHeuristics()));
}
}
BoostingIterators.push_back(Corpus.all());
return Corpus.unionOf(std::move(BoostingIterators));
}
std::unique_ptr<Iterator>
Dex::createTypeBoostingIterator(llvm::ArrayRef<std::string> Types) const {
std::vector<std::unique_ptr<Iterator>> BoostingIterators;
SymbolRelevanceSignals PreferredTypeSignals;
PreferredTypeSignals.TypeMatchesPreferred = true;
auto Boost = PreferredTypeSignals.evaluateHeuristics();
for (const auto &T : Types)
BoostingIterators.push_back(
Corpus.boost(iterator(Token(Token::Kind::Type, T)), Boost));
BoostingIterators.push_back(Corpus.all());
return Corpus.unionOf(std::move(BoostingIterators));
}
bool Dex::fuzzyFind(const FuzzyFindRequest &Req,
llvm::function_ref<void(const Symbol &)> Callback) const {
assert(!StringRef(Req.Query).contains("::") &&
"There must be no :: in query.");
trace::Span Tracer("Dex fuzzyFind");
FuzzyMatcher Filter(Req.Query);
bool More = !Req.Query.empty() && Req.Query.size() < 3;
std::vector<std::unique_ptr<Iterator>> Criteria;
const auto TrigramTokens = generateQueryTrigrams(Req.Query);
std::vector<std::unique_ptr<Iterator>> TrigramIterators;
for (const auto &Trigram : TrigramTokens)
TrigramIterators.push_back(iterator(Trigram));
Criteria.push_back(Corpus.intersect(std::move(TrigramIterators)));
std::vector<std::unique_ptr<Iterator>> ScopeIterators;
for (const auto &Scope : Req.Scopes)
ScopeIterators.push_back(iterator(Token(Token::Kind::Scope, Scope)));
if (Req.AnyScope)
ScopeIterators.push_back(
Corpus.boost(Corpus.all(), ScopeIterators.empty() ? 1.0 : 0.2));
Criteria.push_back(Corpus.unionOf(std::move(ScopeIterators)));
Criteria.push_back(createFileProximityIterator(Req.ProximityPaths));
Criteria.push_back(createTypeBoostingIterator(Req.PreferredTypes));
if (Req.RestrictForCodeCompletion)
Criteria.push_back(iterator(RestrictedForCodeCompletion));
auto Root = Corpus.intersect(std::move(Criteria));
if (Req.Limit)
Root = Corpus.limit(std::move(Root), *Req.Limit * 100);
SPAN_ATTACH(Tracer, "query", llvm::to_string(*Root));
vlog("Dex query tree: {0}", *Root);
using IDAndScore = std::pair<DocID, float>;
std::vector<IDAndScore> IDAndScores = consume(*Root);
auto Compare = [](const IDAndScore &LHS, const IDAndScore &RHS) {
return LHS.second > RHS.second;
};
TopN<IDAndScore, decltype(Compare)> Top(
Req.Limit ? *Req.Limit : std::numeric_limits<size_t>::max(), Compare);
for (const auto &IDAndScore : IDAndScores) {
const DocID SymbolDocID = IDAndScore.first;
const auto *Sym = Symbols[SymbolDocID];
const std::optional<float> Score = Filter.match(Sym->Name);
if (!Score)
continue;
const float FinalScore =
(*Score) * SymbolQuality[SymbolDocID] * IDAndScore.second;
if (Top.push({SymbolDocID, FinalScore}))
More = true;
}
for (const auto &Item : std::move(Top).items())
Callback(*Symbols[Item.first]);
return More;
}
void Dex::lookup(const LookupRequest &Req,
llvm::function_ref<void(const Symbol &)> Callback) const {
trace::Span Tracer("Dex lookup");
for (const auto &ID : Req.IDs) {
auto I = LookupTable.find(ID);
if (I != LookupTable.end())
Callback(*I->second);
}
}
bool Dex::refs(const RefsRequest &Req,
llvm::function_ref<void(const Ref &)> Callback) const {
trace::Span Tracer("Dex refs");
uint32_t Remaining = Req.Limit.value_or(std::numeric_limits<uint32_t>::max());
for (const auto &ID : Req.IDs)
for (const auto &Ref : Refs.lookup(ID)) {
if (!static_cast<int>(Req.Filter & Ref.Kind))
continue;
if (Remaining == 0)
return true;
--Remaining;
Callback(Ref);
}
return false;
}
void Dex::relations(
const RelationsRequest &Req,
llvm::function_ref<void(const SymbolID &, const Symbol &)> Callback) const {
trace::Span Tracer("Dex relations");
uint32_t Remaining = Req.Limit.value_or(std::numeric_limits<uint32_t>::max());
for (const SymbolID &Subject : Req.Subjects) {
LookupRequest LookupReq;
auto It = Relations.find(
std::make_pair(Subject, static_cast<uint8_t>(Req.Predicate)));
if (It != Relations.end()) {
for (const auto &Object : It->second) {
if (Remaining > 0) {
--Remaining;
LookupReq.IDs.insert(Object);
}
}
}
lookup(LookupReq, [&](const Symbol &Object) { Callback(Subject, Object); });
}
}
llvm::unique_function<IndexContents(llvm::StringRef) const>
Dex::indexedFiles() const {
return [this](llvm::StringRef FileURI) {
return Files.contains(FileURI) ? IdxContents : IndexContents::None;
};
}
size_t Dex::estimateMemoryUsage() const {
size_t Bytes = Symbols.size() * sizeof(const Symbol *);
Bytes += SymbolQuality.size() * sizeof(float);
Bytes += LookupTable.getMemorySize();
Bytes += InvertedIndex.getMemorySize();
for (const auto &TokenToPostingList : InvertedIndex)
Bytes += TokenToPostingList.second.bytes();
Bytes += Refs.getMemorySize();
Bytes += Relations.getMemorySize();
return Bytes + BackingDataSize;
}
llvm::StringRef findPathInURI(llvm::StringRef S) {
S = S.split(':').second;
if (S.consume_front("//"))
S = S.drop_until([](char C) { return C == '/'; });
return S;
}
constexpr unsigned ProximityURILimit = 5;
llvm::SmallVector<llvm::StringRef, ProximityURILimit>
generateProximityURIs(llvm::StringRef URI) {
llvm::StringRef Path = findPathInURI(URI);
if (Path.empty())
return {};
assert(Path.begin() >= URI.begin() && Path.begin() < URI.end() &&
Path.end() == URI.end());
llvm::SmallVector<llvm::StringRef, ProximityURILimit> Result = {URI};
for (auto Slash = Path.rfind('/'); Slash > 0 && Slash != StringRef::npos;
Slash = Path.rfind('/')) {
Path = Path.substr(0, Slash);
Result.push_back(URI.substr(0, Path.end() - URI.data()));
if (Result.size() == ProximityURILimit)
return Result;
}
if (Path.starts_with("/"))
Result.push_back(URI.substr(0, Path.begin() + 1 - URI.data()));
return Result;
}
}
}
}