34 changes: 29 additions & 5 deletions clang-tools-extra/clang-tidy/misc/UseInternalLinkageCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@

#include "UseInternalLinkageCheck.h"
#include "../utils/FileExtensionsUtils.h"
#include "../utils/LexerUtils.h"
#include "clang/AST/Decl.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchersMacros.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/Specifiers.h"
#include "clang/Basic/TokenKinds.h"
#include "clang/Lex/Token.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"

using namespace clang::ast_matchers;

Expand Down Expand Up @@ -47,6 +47,8 @@ namespace {

AST_MATCHER(Decl, isFirstDecl) { return Node.isFirstDecl(); }

AST_MATCHER(FunctionDecl, hasBody) { return Node.hasBody(); }

static bool isInMainFile(SourceLocation L, SourceManager &SM,
const FileExtensionsSet &HeaderFileExtensions) {
for (;;) {
Expand Down Expand Up @@ -78,6 +80,22 @@ AST_POLYMORPHIC_MATCHER(isExternStorageClass,
return Node.getStorageClass() == SC_Extern;
}

AST_MATCHER(FunctionDecl, isAllocationOrDeallocationOverloadedFunction) {
// [basic.stc.dynamic.allocation]
// An allocation function that is not a class member function shall belong to
// the global scope and not have a name with internal linkage.
// [basic.stc.dynamic.deallocation]
// A deallocation function that is not a class member function shall belong to
// the global scope and not have a name with internal linkage.
static const llvm::DenseSet<OverloadedOperatorKind> OverloadedOperators{
OverloadedOperatorKind::OO_New,
OverloadedOperatorKind::OO_Array_New,
OverloadedOperatorKind::OO_Delete,
OverloadedOperatorKind::OO_Array_Delete,
};
return OverloadedOperators.contains(Node.getOverloadedOperator());
}

} // namespace

UseInternalLinkageCheck::UseInternalLinkageCheck(StringRef Name,
Expand All @@ -100,10 +118,16 @@ void UseInternalLinkageCheck::registerMatchers(MatchFinder *Finder) {
isExternStorageClass(), isExternC(),
// 3. template
isExplicitTemplateSpecialization(),
// 4. friend
hasAncestor(friendDecl()))));
hasAncestor(decl(anyOf(
// 4. friend
friendDecl(),
// 5. module export decl
exportDecl()))))));
Finder->addMatcher(
functionDecl(Common, unless(cxxMethodDecl()), unless(isMain()))
functionDecl(Common, hasBody(),
unless(anyOf(cxxMethodDecl(),
isAllocationOrDeallocationOverloadedFunction(),
isMain())))
.bind("fn"),
this);
Finder->addMatcher(varDecl(Common, hasGlobalStorage()).bind("var"), this);
Expand Down
76 changes: 55 additions & 21 deletions clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,21 @@
using namespace clang::ast_matchers;

namespace clang::tidy::modernize {

static bool isNegativeComparison(const Expr *ComparisonExpr) {
if (const auto *Op = llvm::dyn_cast<BinaryOperator>(ComparisonExpr))
return Op->getOpcode() == BO_NE;

if (const auto *Op = llvm::dyn_cast<CXXOperatorCallExpr>(ComparisonExpr))
return Op->getOperator() == OO_ExclaimEqual;

if (const auto *Op =
llvm::dyn_cast<CXXRewrittenBinaryOperator>(ComparisonExpr))
return Op->getOperator() == BO_NE;

return false;
}

struct NotLengthExprForStringNode {
NotLengthExprForStringNode(std::string ID, DynTypedNode Node,
ASTContext *Context)
Expand Down Expand Up @@ -171,10 +186,26 @@ void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
hasRHS(lengthExprForStringNode("needle")))))
.bind("expr"),
this);

// Case 6: X.substr(0, LEN(Y)) [!=]= Y -> starts_with.
Finder->addMatcher(
binaryOperation(
hasAnyOperatorName("==", "!="),
hasOperands(
expr().bind("needle"),
cxxMemberCallExpr(
argumentCountIs(2), hasArgument(0, ZeroLiteral),
hasArgument(1, lengthExprForStringNode("needle")),
callee(cxxMethodDecl(hasName("substr"),
ofClass(OnClassWithStartsWithFunction))
.bind("find_fun")))
.bind("find_expr")))
.bind("expr"),
this);
}

void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
const auto *ComparisonExpr = Result.Nodes.getNodeAs<BinaryOperator>("expr");
const auto *ComparisonExpr = Result.Nodes.getNodeAs<Expr>("expr");
const auto *FindExpr = Result.Nodes.getNodeAs<CXXMemberCallExpr>("find_expr");
const auto *FindFun = Result.Nodes.getNodeAs<CXXMethodDecl>("find_fun");
const auto *SearchExpr = Result.Nodes.getNodeAs<Expr>("needle");
Expand All @@ -183,39 +214,42 @@ void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
const auto *EndsWithFunction =
Result.Nodes.getNodeAs<CXXMethodDecl>("ends_with_fun");
assert(bool(StartsWithFunction) != bool(EndsWithFunction));

const CXXMethodDecl *ReplacementFunction =
StartsWithFunction ? StartsWithFunction : EndsWithFunction;

if (ComparisonExpr->getBeginLoc().isMacroID())
if (ComparisonExpr->getBeginLoc().isMacroID() ||
FindExpr->getBeginLoc().isMacroID())
return;

const bool Neg = ComparisonExpr->getOpcode() == BO_NE;
// Make sure FindExpr->getArg(0) can be used to make a range in the FitItHint.
if (FindExpr->getNumArgs() == 0)
return;

auto Diagnostic =
diag(FindExpr->getExprLoc(), "use %0 instead of %1() %select{==|!=}2 0")
<< ReplacementFunction->getName() << FindFun->getName() << Neg;
// Retrieve the source text of the search expression.
const auto SearchExprText = Lexer::getSourceText(
CharSourceRange::getTokenRange(SearchExpr->getSourceRange()),
*Result.SourceManager, Result.Context->getLangOpts());

// Remove possible arguments after search expression and ' [!=]= .+' suffix.
Diagnostic << FixItHint::CreateReplacement(
CharSourceRange::getTokenRange(
Lexer::getLocForEndOfToken(SearchExpr->getEndLoc(), 0,
*Result.SourceManager, getLangOpts()),
ComparisonExpr->getEndLoc()),
")");
auto Diagnostic = diag(FindExpr->getExprLoc(), "use %0 instead of %1")
<< ReplacementFunction->getName() << FindFun->getName();

// Remove possible '.+ [!=]= ' prefix.
// Remove everything before the function call.
Diagnostic << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
ComparisonExpr->getBeginLoc(), FindExpr->getBeginLoc()));

// Replace method name by '(starts|ends)_with'.
// Remove possible arguments before search expression.
// Rename the function to `starts_with` or `ends_with`.
Diagnostic << FixItHint::CreateReplacement(FindExpr->getExprLoc(),
ReplacementFunction->getName());

// Replace arguments and everything after the function call.
Diagnostic << FixItHint::CreateReplacement(
CharSourceRange::getCharRange(FindExpr->getExprLoc(),
SearchExpr->getBeginLoc()),
(ReplacementFunction->getName() + "(").str());
CharSourceRange::getTokenRange(FindExpr->getArg(0)->getBeginLoc(),
ComparisonExpr->getEndLoc()),
(SearchExprText + ")").str());

// Add possible negation '!'.
if (Neg)
// Add negation if necessary.
if (isNegativeComparison(ComparisonExpr))
Diagnostic << FixItHint::CreateInsertion(FindExpr->getBeginLoc(), "!");
}

Expand Down
3 changes: 2 additions & 1 deletion clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ AST_MATCHER(StringLiteral, isOrdinary) { return Node.isOrdinary(); }
} // namespace

UseStdPrintCheck::UseStdPrintCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
: ClangTidyCheck(Name, Context), PP(nullptr),
StrictMode(Options.getLocalOrGlobal("StrictMode", false)),
PrintfLikeFunctions(utils::options::parseStringList(
Options.get("PrintfLikeFunctions", ""))),
Expand Down Expand Up @@ -131,6 +131,7 @@ void UseStdPrintCheck::check(const MatchFinder::MatchResult &Result) {
utils::FormatStringConverter::Configuration ConverterConfig;
ConverterConfig.StrictMode = StrictMode;
ConverterConfig.AllowTrailingNewlineRemoval = true;
assert(PP && "Preprocessor should be set by registerPPCallbacks");
utils::FormatStringConverter Converter(
Result.Context, Printf, FormatArgOffset, ConverterConfig, getLangOpts(),
*Result.SourceManager, *PP);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@ namespace clang::tidy::readability {
/// a call to `empty()`.
///
/// The emptiness of a container should be checked using the `empty()` method
/// instead of the `size()` method. It is not guaranteed that `size()` is a
/// constant-time function, and it is generally more efficient and also shows
/// clearer intent to use `empty()`. Furthermore some containers may implement
/// the `empty()` method but not implement the `size()` method. Using `empty()`
/// whenever possible makes it easier to switch to another container in the
/// future.
/// instead of the `size()` method. It shows clearer intent to use `empty()`.
/// Furthermore some containers may implement the `empty()` method but not
/// implement the `size()` method. Using `empty()` whenever possible makes it
/// easier to switch to another container in the future.
class ContainerSizeEmptyCheck : public ClangTidyCheck {
public:
ContainerSizeEmptyCheck(StringRef Name, ClangTidyContext *Context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,10 +299,11 @@ static bool applyDiceHeuristic(StringRef Arg, StringRef Param,

/// Checks if ArgType binds to ParamType regarding reference-ness and
/// cv-qualifiers.
static bool areRefAndQualCompatible(QualType ArgType, QualType ParamType) {
static bool areRefAndQualCompatible(QualType ArgType, QualType ParamType,
const ASTContext &Ctx) {
return !ParamType->isReferenceType() ||
ParamType.getNonReferenceType().isAtLeastAsQualifiedAs(
ArgType.getNonReferenceType());
ArgType.getNonReferenceType(), Ctx);
}

static bool isPointerOrArray(QualType TypeToCheck) {
Expand All @@ -311,12 +312,12 @@ static bool isPointerOrArray(QualType TypeToCheck) {

/// Checks whether ArgType is an array type identical to ParamType's array type.
/// Enforces array elements' qualifier compatibility as well.
static bool isCompatibleWithArrayReference(QualType ArgType,
QualType ParamType) {
static bool isCompatibleWithArrayReference(QualType ArgType, QualType ParamType,
const ASTContext &Ctx) {
if (!ArgType->isArrayType())
return false;
// Here, qualifiers belong to the elements of the arrays.
if (!ParamType.isAtLeastAsQualifiedAs(ArgType))
if (!ParamType.isAtLeastAsQualifiedAs(ArgType, Ctx))
return false;

return ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType();
Expand All @@ -342,12 +343,13 @@ static QualType convertToPointeeOrArrayElementQualType(QualType TypeToConvert) {
/// every * in ParamType to the right of that cv-qualifier, except the last
/// one, must also be const-qualified.
static bool arePointersStillQualCompatible(QualType ArgType, QualType ParamType,
bool &IsParamContinuouslyConst) {
bool &IsParamContinuouslyConst,
const ASTContext &Ctx) {
// The types are compatible, if the parameter is at least as qualified as the
// argument, and if it is more qualified, it has to be const on upper pointer
// levels.
bool AreTypesQualCompatible =
ParamType.isAtLeastAsQualifiedAs(ArgType) &&
ParamType.isAtLeastAsQualifiedAs(ArgType, Ctx) &&
(!ParamType.hasQualifiers() || IsParamContinuouslyConst);
// Check whether the parameter's constness continues at the current pointer
// level.
Expand All @@ -359,9 +361,10 @@ static bool arePointersStillQualCompatible(QualType ArgType, QualType ParamType,
/// Checks whether multilevel pointers are compatible in terms of levels,
/// qualifiers and pointee type.
static bool arePointerTypesCompatible(QualType ArgType, QualType ParamType,
bool IsParamContinuouslyConst) {
bool IsParamContinuouslyConst,
const ASTContext &Ctx) {
if (!arePointersStillQualCompatible(ArgType, ParamType,
IsParamContinuouslyConst))
IsParamContinuouslyConst, Ctx))
return false;

do {
Expand All @@ -372,7 +375,7 @@ static bool arePointerTypesCompatible(QualType ArgType, QualType ParamType,
// Check whether cv-qualifiers permit compatibility on
// current level.
if (!arePointersStillQualCompatible(ArgType, ParamType,
IsParamContinuouslyConst))
IsParamContinuouslyConst, Ctx))
return false;

if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType())
Expand All @@ -396,7 +399,7 @@ static bool areTypesCompatible(QualType ArgType, QualType ParamType,
return true;

// Check for constness and reference compatibility.
if (!areRefAndQualCompatible(ArgType, ParamType))
if (!areRefAndQualCompatible(ArgType, ParamType, Ctx))
return false;

bool IsParamReference = ParamType->isReferenceType();
Expand Down Expand Up @@ -434,7 +437,7 @@ static bool areTypesCompatible(QualType ArgType, QualType ParamType,
// When ParamType is an array reference, ArgType has to be of the same-sized
// array-type with cv-compatible element type.
if (IsParamReference && ParamType->isArrayType())
return isCompatibleWithArrayReference(ArgType, ParamType);
return isCompatibleWithArrayReference(ArgType, ParamType, Ctx);

bool IsParamContinuouslyConst =
!IsParamReference || ParamType.getNonReferenceType().isConstQualified();
Expand All @@ -444,7 +447,7 @@ static bool areTypesCompatible(QualType ArgType, QualType ParamType,
ParamType = convertToPointeeOrArrayElementQualType(ParamType);

// Check qualifier compatibility on the next level.
if (!ParamType.isAtLeastAsQualifiedAs(ArgType))
if (!ParamType.isAtLeastAsQualifiedAs(ArgType, Ctx))
return false;

if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType())
Expand Down Expand Up @@ -472,8 +475,8 @@ static bool areTypesCompatible(QualType ArgType, QualType ParamType,
if (!(ParamType->isAnyPointerType() && ArgType->isAnyPointerType()))
return false;

return arePointerTypesCompatible(ArgType, ParamType,
IsParamContinuouslyConst);
return arePointerTypesCompatible(ArgType, ParamType, IsParamContinuouslyConst,
Ctx);
}

static bool isOverloadedUnaryOrBinarySymbolOperator(const FunctionDecl *FD) {
Expand Down
13 changes: 10 additions & 3 deletions clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,12 @@ bool isQualificationConvertiblePointer(QualType From, QualType To,
} // namespace

static bool canThrow(const FunctionDecl *Func) {
// consteval specifies that every call to the function must produce a
// compile-time constant, which cannot evaluate a throw expression without
// producing a compilation error.
if (Func->isConsteval())
return false;

const auto *FunProto = Func->getType()->getAs<FunctionProtoType>();
if (!FunProto)
return true;
Expand Down Expand Up @@ -418,7 +424,7 @@ ExceptionAnalyzer::ExceptionInfo::filterIgnoredExceptions(
if (TD->getDeclName().isIdentifier()) {
if ((IgnoreBadAlloc &&
(TD->getName() == "bad_alloc" && TD->isInStdNamespace())) ||
(IgnoredTypes.count(TD->getName()) > 0))
(IgnoredTypes.contains(TD->getName())))
TypesToDelete.push_back(T);
}
}
Expand Down Expand Up @@ -449,7 +455,8 @@ void ExceptionAnalyzer::ExceptionInfo::reevaluateBehaviour() {
ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
const FunctionDecl *Func, const ExceptionInfo::Throwables &Caught,
llvm::SmallSet<const FunctionDecl *, 32> &CallStack) {
if (!Func || CallStack.count(Func) || (!CallStack.empty() && !canThrow(Func)))
if (!Func || CallStack.contains(Func) ||
(!CallStack.empty() && !canThrow(Func)))
return ExceptionInfo::createNonThrowing();

if (const Stmt *Body = Func->getBody()) {
Expand Down Expand Up @@ -507,7 +514,7 @@ ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
for (unsigned I = 0; I < Try->getNumHandlers(); ++I) {
const CXXCatchStmt *Catch = Try->getHandler(I);

// Everything is catched through 'catch(...)'.
// Everything is caught through 'catch(...)'.
if (!Catch->getExceptionDecl()) {
ExceptionInfo Rethrown = throwsException(
Catch->getHandlerBlock(), Uncaught.getExceptionTypes(), CallStack);
Expand Down
4 changes: 2 additions & 2 deletions clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ class ExceptionAnalyzer {
/// Recalculate the 'Behaviour' for example after filtering.
void reevaluateBehaviour();

/// Keep track if the entity related to this 'ExceptionInfo' can in princple
/// throw, if it's unknown or if it won't throw.
/// Keep track if the entity related to this 'ExceptionInfo' can in
/// principle throw, if it's unknown or if it won't throw.
State Behaviour;

/// Keep track if the entity contains any unknown elements to keep track
Expand Down
8 changes: 8 additions & 0 deletions clang-tools-extra/clangd/ClangdLSPServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1415,6 +1415,12 @@ void ClangdLSPServer::onInlayHint(const InlayHintsParams &Params,
std::move(Reply));
}

void ClangdLSPServer::onCallHierarchyOutgoingCalls(
const CallHierarchyOutgoingCallsParams &Params,
Callback<std::vector<CallHierarchyOutgoingCall>> Reply) {
Server->outgoingCalls(Params.item, std::move(Reply));
}

void ClangdLSPServer::applyConfiguration(
const ConfigurationSettings &Settings) {
// Per-file update to the compilation database.
Expand Down Expand Up @@ -1693,6 +1699,8 @@ void ClangdLSPServer::bindMethods(LSPBinder &Bind,
Bind.method("typeHierarchy/subtypes", this, &ClangdLSPServer::onSubTypes);
Bind.method("textDocument/prepareCallHierarchy", this, &ClangdLSPServer::onPrepareCallHierarchy);
Bind.method("callHierarchy/incomingCalls", this, &ClangdLSPServer::onCallHierarchyIncomingCalls);
if (Opts.EnableOutgoingCalls)
Bind.method("callHierarchy/outgoingCalls", this, &ClangdLSPServer::onCallHierarchyOutgoingCalls);
Bind.method("textDocument/selectionRange", this, &ClangdLSPServer::onSelectionRange);
Bind.method("textDocument/documentLink", this, &ClangdLSPServer::onDocumentLink);
Bind.method("textDocument/semanticTokens/full", this, &ClangdLSPServer::onSemanticTokens);
Expand Down
3 changes: 3 additions & 0 deletions clang-tools-extra/clangd/ClangdLSPServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
void onCallHierarchyIncomingCalls(
const CallHierarchyIncomingCallsParams &,
Callback<std::vector<CallHierarchyIncomingCall>>);
void onCallHierarchyOutgoingCalls(
const CallHierarchyOutgoingCallsParams &,
Callback<std::vector<CallHierarchyOutgoingCall>>);
void onClangdInlayHints(const InlayHintsParams &,
Callback<llvm::json::Value>);
void onInlayHint(const InlayHintsParams &, Callback<std::vector<InlayHint>>);
Expand Down
14 changes: 13 additions & 1 deletion clang-tools-extra/clangd/ClangdServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,9 @@ ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB,
const ThreadsafeFS &TFS, const Options &Opts,
Callbacks *Callbacks)
: FeatureModules(Opts.FeatureModules), CDB(CDB), TFS(TFS),
DynamicIdx(Opts.BuildDynamicSymbolIndex ? new FileIndex() : nullptr),
DynamicIdx(Opts.BuildDynamicSymbolIndex
? new FileIndex(Opts.EnableOutgoingCalls)
: nullptr),
ModulesManager(Opts.ModulesManager),
ClangTidyProvider(Opts.ClangTidyProvider),
UseDirtyHeaders(Opts.UseDirtyHeaders),
Expand Down Expand Up @@ -256,6 +258,7 @@ ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB,
Callbacks->onBackgroundIndexProgress(S);
};
BGOpts.ContextProvider = Opts.ContextProvider;
BGOpts.SupportContainedRefs = Opts.EnableOutgoingCalls;
BackgroundIdx = std::make_unique<BackgroundIndex>(
TFS, CDB,
BackgroundIndexStorage::createDiskBackedStorageFactory(
Expand Down Expand Up @@ -912,6 +915,15 @@ void ClangdServer::inlayHints(PathRef File, std::optional<Range> RestrictRange,
WorkScheduler->runWithAST("InlayHints", File, std::move(Action), Transient);
}

void ClangdServer::outgoingCalls(
const CallHierarchyItem &Item,
Callback<std::vector<CallHierarchyOutgoingCall>> CB) {
WorkScheduler->run("Outgoing Calls", "",
[CB = std::move(CB), Item, this]() mutable {
CB(clangd::outgoingCalls(Item, Index));
});
}

void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
// FIXME: Do nothing for now. This will be used for indexing and potentially
// invalidating other caches.
Expand Down
9 changes: 9 additions & 0 deletions clang-tools-extra/clangd/ClangdServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ class ClangdServer {
/// Cached preambles are potentially large. If false, store them on disk.
bool StorePreamblesInMemory = true;

/// Call hierarchy's outgoing calls feature requires additional index
/// serving structures which increase memory usage. If false, these are
/// not created and the feature is not enabled.
bool EnableOutgoingCalls = true;

/// This throttler controls which preambles may be built at a given time.
clangd::PreambleThrottler *PreambleThrottler = nullptr;

Expand Down Expand Up @@ -292,6 +297,10 @@ class ClangdServer {
void incomingCalls(const CallHierarchyItem &Item,
Callback<std::vector<CallHierarchyIncomingCall>>);

/// Resolve outgoing calls for a given call hierarchy item.
void outgoingCalls(const CallHierarchyItem &Item,
Callback<std::vector<CallHierarchyOutgoingCall>>);

/// Resolve inlay hints for a given document.
void inlayHints(PathRef File, std::optional<Range> RestrictRange,
Callback<std::vector<InlayHint>>);
Expand Down
6 changes: 3 additions & 3 deletions clang-tools-extra/clangd/Compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ buildCompilerInvocation(const ParseInputs &Inputs, clang::DiagnosticConsumer &D,
CIOpts.VFS = Inputs.TFS->view(Inputs.CompileCommand.Directory);
CIOpts.CC1Args = CC1Args;
CIOpts.RecoverOnError = true;
CIOpts.Diags =
CompilerInstance::createDiagnostics(new DiagnosticOptions, &D, false);
CIOpts.Diags = CompilerInstance::createDiagnostics(
*CIOpts.VFS, new DiagnosticOptions, &D, false);
CIOpts.ProbePrecompiled = false;
std::unique_ptr<CompilerInvocation> CI = createInvocation(ArgStrs, CIOpts);
if (!CI)
Expand Down Expand Up @@ -148,7 +148,7 @@ prepareCompilerInstance(std::unique_ptr<clang::CompilerInvocation> CI,
auto Clang = std::make_unique<CompilerInstance>(
std::make_shared<PCHContainerOperations>());
Clang->setInvocation(std::move(CI));
Clang->createDiagnostics(&DiagsClient, false);
Clang->createDiagnostics(*VFS, &DiagsClient, false);

if (auto VFSWithRemapping = createVFSFromCompilerInvocation(
Clang->getInvocation(), Clang->getDiagnostics(), VFS))
Expand Down
13 changes: 9 additions & 4 deletions clang-tools-extra/clangd/InlayHints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -626,10 +626,15 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {

bool VisitLambdaExpr(LambdaExpr *E) {
FunctionDecl *D = E->getCallOperator();
if (!E->hasExplicitResultType())
addReturnTypeHint(D, E->hasExplicitParameters()
? D->getFunctionTypeLoc().getRParenLoc()
: E->getIntroducerRange().getEnd());
if (!E->hasExplicitResultType()) {
SourceLocation TypeHintLoc;
if (!E->hasExplicitParameters())
TypeHintLoc = E->getIntroducerRange().getEnd();
else if (auto FTL = D->getFunctionTypeLoc())
TypeHintLoc = FTL.getRParenLoc();
if (TypeHintLoc.isValid())
addReturnTypeHint(D, TypeHintLoc);
}
return true;
}

Expand Down
3 changes: 2 additions & 1 deletion clang-tools-extra/clangd/ModulesBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,8 @@ bool IsModuleFileUpToDate(PathRef ModuleFilePath,

clang::clangd::IgnoreDiagnostics IgnoreDiags;
IntrusiveRefCntPtr<DiagnosticsEngine> Diags =
CompilerInstance::createDiagnostics(new DiagnosticOptions, &IgnoreDiags,
CompilerInstance::createDiagnostics(*VFS, new DiagnosticOptions,
&IgnoreDiags,
/*ShouldOwnClient=*/false);

LangOptions LangOpts;
Expand Down
4 changes: 2 additions & 2 deletions clang-tools-extra/clangd/Preamble.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -613,8 +613,9 @@ buildPreamble(PathRef FileName, CompilerInvocation CI,
for (const auto &L : ASTListeners)
L->sawDiagnostic(D, Diag);
});
auto VFS = Inputs.TFS->view(Inputs.CompileCommand.Directory);
llvm::IntrusiveRefCntPtr<DiagnosticsEngine> PreambleDiagsEngine =
CompilerInstance::createDiagnostics(&CI.getDiagnosticOpts(),
CompilerInstance::createDiagnostics(*VFS, &CI.getDiagnosticOpts(),
&PreambleDiagnostics,
/*ShouldOwnClient=*/false);
const Config &Cfg = Config::current();
Expand Down Expand Up @@ -651,7 +652,6 @@ buildPreamble(PathRef FileName, CompilerInvocation CI,
for (const auto &L : ASTListeners)
L->beforeExecute(CI);
});
auto VFS = Inputs.TFS->view(Inputs.CompileCommand.Directory);
llvm::SmallString<32> AbsFileName(FileName);
VFS->makeAbsolute(AbsFileName);
auto StatCache = std::make_shared<PreambleFileStatusCache>(AbsFileName);
Expand Down
29 changes: 29 additions & 0 deletions clang-tools-extra/clangd/Protocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,35 @@ bool fromJSON(const llvm::json::Value &Params, ClientCapabilities &R,
if (auto EditsNearCursor = Completion->getBoolean("editsNearCursor"))
R.CompletionFixes |= *EditsNearCursor;
}
if (auto *References = TextDocument->getObject("references")) {
if (auto ContainerSupport = References->getBoolean("container")) {
R.ReferenceContainer |= *ContainerSupport;
}
}
if (auto *Diagnostics = TextDocument->getObject("publishDiagnostics")) {
if (auto CodeActions = Diagnostics->getBoolean("codeActionsInline")) {
R.DiagnosticFixes |= *CodeActions;
}
}
if (auto *InactiveRegions =
TextDocument->getObject("inactiveRegionsCapabilities")) {
if (auto InactiveRegionsSupport =
InactiveRegions->getBoolean("inactiveRegions")) {
R.InactiveRegions |= *InactiveRegionsSupport;
}
}
}
if (auto *Window = Experimental->getObject("window")) {
if (auto Implicit =
Window->getBoolean("implicitWorkDoneProgressCreate")) {
R.ImplicitProgressCreation |= *Implicit;
}
}
if (auto *OffsetEncoding = Experimental->get("offsetEncoding")) {
R.offsetEncoding.emplace();
if (!fromJSON(*OffsetEncoding, *R.offsetEncoding,
P.field("offsetEncoding")))
return false;
}
}

Expand Down
3 changes: 3 additions & 0 deletions clang-tools-extra/clangd/Protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ struct ClientCapabilities {
std::optional<SymbolKindBitset> WorkspaceSymbolKinds;

/// Whether the client accepts diagnostics with codeActions attached inline.
/// This is a clangd extension.
/// textDocument.publishDiagnostics.codeActionsInline.
bool DiagnosticFixes = false;

Expand All @@ -475,6 +476,7 @@ struct ClientCapabilities {

/// Client supports displaying a container string for results of
/// textDocument/reference (clangd extension)
/// textDocument.references.container
bool ReferenceContainer = false;

/// Client supports hierarchical document symbols.
Expand Down Expand Up @@ -563,6 +565,7 @@ struct ClientCapabilities {

/// Whether the client supports the textDocument/inactiveRegions
/// notification. This is a clangd extension.
/// textDocument.inactiveRegionsCapabilities.inactiveRegions
bool InactiveRegions = false;
};
bool fromJSON(const llvm::json::Value &, ClientCapabilities &,
Expand Down
104 changes: 82 additions & 22 deletions clang-tools-extra/clangd/XRefs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
#include <optional>
Expand Down Expand Up @@ -120,31 +121,17 @@ void logIfOverflow(const SymbolLocation &Loc) {

// Convert a SymbolLocation to LSP's Location.
// TUPath is used to resolve the path of URI.
// FIXME: figure out a good home for it, and share the implementation with
// FindSymbols.
std::optional<Location> toLSPLocation(const SymbolLocation &Loc,
llvm::StringRef TUPath) {
if (!Loc)
return std::nullopt;
auto Uri = URI::parse(Loc.FileURI);
if (!Uri) {
elog("Could not parse URI {0}: {1}", Loc.FileURI, Uri.takeError());
auto LSPLoc = indexToLSPLocation(Loc, TUPath);
if (!LSPLoc) {
elog("{0}", LSPLoc.takeError());
return std::nullopt;
}
auto U = URIForFile::fromURI(*Uri, TUPath);
if (!U) {
elog("Could not resolve URI {0}: {1}", Loc.FileURI, U.takeError());
return std::nullopt;
}

Location LSPLoc;
LSPLoc.uri = std::move(*U);
LSPLoc.range.start.line = Loc.Start.line();
LSPLoc.range.start.character = Loc.Start.column();
LSPLoc.range.end.line = Loc.End.line();
LSPLoc.range.end.character = Loc.End.column();
logIfOverflow(Loc);
return LSPLoc;
return *LSPLoc;
}

SymbolLocation toIndexLocation(const Location &Loc, std::string &URIStorage) {
Expand Down Expand Up @@ -1701,6 +1688,7 @@ declToHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) {

HierarchyItem HI;
HI.name = printName(Ctx, ND);
// FIXME: Populate HI.detail the way we do in symbolToHierarchyItem?
HI.kind = SK;
HI.range = Range{sourceLocToPosition(SM, DeclRange->getBegin()),
sourceLocToPosition(SM, DeclRange->getEnd())};
Expand Down Expand Up @@ -1752,6 +1740,7 @@ static std::optional<HierarchyItem> symbolToHierarchyItem(const Symbol &S,
}
HierarchyItem HI;
HI.name = std::string(S.Name);
HI.detail = (S.Scope + S.Name).str();
HI.kind = indexSymbolKindToSymbolKind(S.SymInfo.Kind);
HI.selectionRange = Loc->range;
// FIXME: Populate 'range' correctly
Expand Down Expand Up @@ -2275,7 +2264,7 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
// Initially store the ranges in a map keyed by SymbolID of the caller.
// This allows us to group different calls with the same caller
// into the same CallHierarchyIncomingCall.
llvm::DenseMap<SymbolID, std::vector<Range>> CallsIn;
llvm::DenseMap<SymbolID, std::vector<Location>> CallsIn;
// We can populate the ranges based on a refs request only. As we do so, we
// also accumulate the container IDs into a lookup request.
LookupRequest ContainerLookup;
Expand All @@ -2285,7 +2274,7 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
elog("incomingCalls failed to convert location: {0}", Loc.takeError());
return;
}
CallsIn[R.Container].push_back(Loc->range);
CallsIn[R.Container].push_back(*Loc);

ContainerLookup.IDs.insert(R.Container);
});
Expand All @@ -2294,9 +2283,21 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
Index->lookup(ContainerLookup, [&](const Symbol &Caller) {
auto It = CallsIn.find(Caller.ID);
assert(It != CallsIn.end());
if (auto CHI = symbolToCallHierarchyItem(Caller, Item.uri.file()))
if (auto CHI = symbolToCallHierarchyItem(Caller, Item.uri.file())) {
std::vector<Range> FromRanges;
for (const Location &L : It->second) {
if (L.uri != CHI->uri) {
// Call location not in same file as caller.
// This can happen in some edge cases. There's not much we can do,
// since the protocol only allows returning ranges interpreted as
// being in the caller's file.
continue;
}
FromRanges.push_back(L.range);
}
Results.push_back(
CallHierarchyIncomingCall{std::move(*CHI), std::move(It->second)});
CallHierarchyIncomingCall{std::move(*CHI), std::move(FromRanges)});
}
});
// Sort results by name of container.
llvm::sort(Results, [](const CallHierarchyIncomingCall &A,
Expand All @@ -2306,6 +2307,65 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
return Results;
}

std::vector<CallHierarchyOutgoingCall>
outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
std::vector<CallHierarchyOutgoingCall> Results;
if (!Index || Item.data.empty())
return Results;
auto ID = SymbolID::fromStr(Item.data);
if (!ID) {
elog("outgoingCalls failed to find symbol: {0}", ID.takeError());
return Results;
}
// In this function, we find outgoing calls based on the index only.
ContainedRefsRequest Request;
Request.ID = *ID;
// Initially store the ranges in a map keyed by SymbolID of the callee.
// This allows us to group different calls to the same function
// into the same CallHierarchyOutgoingCall.
llvm::DenseMap<SymbolID, std::vector<Range>> CallsOut;
// We can populate the ranges based on a refs request only. As we do so, we
// also accumulate the callee IDs into a lookup request.
LookupRequest CallsOutLookup;
Index->containedRefs(Request, [&](const auto &R) {
auto Loc = indexToLSPLocation(R.Location, Item.uri.file());
if (!Loc) {
elog("outgoingCalls failed to convert location: {0}", Loc.takeError());
return;
}
auto It = CallsOut.try_emplace(R.Symbol, std::vector<Range>{}).first;
It->second.push_back(Loc->range);

CallsOutLookup.IDs.insert(R.Symbol);
});
// Perform the lookup request and combine its results with CallsOut to
// get complete CallHierarchyOutgoingCall objects.
Index->lookup(CallsOutLookup, [&](const Symbol &Callee) {
// The containedRefs request should only return symbols which are
// function-like, i.e. symbols for which references to them can be "calls".
using SK = index::SymbolKind;
auto Kind = Callee.SymInfo.Kind;
assert(Kind == SK::Function || Kind == SK::InstanceMethod ||
Kind == SK::ClassMethod || Kind == SK::StaticMethod ||
Kind == SK::Constructor || Kind == SK::Destructor ||
Kind == SK::ConversionFunction);
(void)Kind;
(void)SK::Function;

auto It = CallsOut.find(Callee.ID);
assert(It != CallsOut.end());
if (auto CHI = symbolToCallHierarchyItem(Callee, Item.uri.file()))
Results.push_back(
CallHierarchyOutgoingCall{std::move(*CHI), std::move(It->second)});
});
// Sort results by name of the callee.
llvm::sort(Results, [](const CallHierarchyOutgoingCall &A,
const CallHierarchyOutgoingCall &B) {
return A.to.name < B.to.name;
});
return Results;
}

llvm::DenseSet<const Decl *> getNonLocalDeclRefs(ParsedAST &AST,
const FunctionDecl *FD) {
if (!FD->hasBody())
Expand Down
3 changes: 3 additions & 0 deletions clang-tools-extra/clangd/XRefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath);
std::vector<CallHierarchyIncomingCall>
incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index);

std::vector<CallHierarchyOutgoingCall>
outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index);

/// Returns all decls that are referenced in the \p FD except local symbols.
llvm::DenseSet<const Decl *> getNonLocalDeclRefs(ParsedAST &AST,
const FunctionDecl *FD);
Expand Down
4 changes: 2 additions & 2 deletions clang-tools-extra/clangd/benchmarks/IndexBenchmark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ namespace {

std::unique_ptr<SymbolIndex> buildMem() {
return loadIndex(IndexFilename, clang::clangd::SymbolOrigin::Static,
/*UseDex=*/false);
/*UseDex=*/false, /*SupportContainedRefs=*/true);
}

std::unique_ptr<SymbolIndex> buildDex() {
return loadIndex(IndexFilename, clang::clangd::SymbolOrigin::Static,
/*UseDex=*/true);
/*UseDex=*/true, /*SupportContainedRefs=*/true);
}

// Reads JSON array of serialized FuzzyFindRequest's from user-provided file.
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/index/Background.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ BackgroundIndex::BackgroundIndex(
: SwapIndex(std::make_unique<MemIndex>()), TFS(TFS), CDB(CDB),
IndexingPriority(Opts.IndexingPriority),
ContextProvider(std::move(Opts.ContextProvider)),
IndexedSymbols(IndexContents::All),
IndexedSymbols(IndexContents::All, Opts.SupportContainedRefs),
Rebuilder(this, &IndexedSymbols, Opts.ThreadPoolSize),
IndexStorageFactory(std::move(IndexStorageFactory)),
Queue(std::move(Opts.OnProgress)),
Expand Down
3 changes: 3 additions & 0 deletions clang-tools-extra/clangd/index/Background.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ class BackgroundIndex : public SwapIndex {
// file. Called with the empty string for other tasks.
// (When called, the context from BackgroundIndex construction is active).
std::function<Context(PathRef)> ContextProvider = nullptr;
// Whether the index needs to support the containedRefs() operation.
// May use extra memory.
bool SupportContainedRefs = true;
};

/// Creates a new background index and starts its threads.
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/index/BackgroundRebuild.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//===-- BackgroundRebuild.cpp - when to rebuild thei background index -----===//
//===-- BackgroundRebuild.cpp - when to rebuild the background index ------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
Expand Down
13 changes: 7 additions & 6 deletions clang-tools-extra/clangd/index/FileIndex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,8 @@ SlabTuple indexHeaderSymbols(llvm::StringRef Version, ASTContext &AST,
/*CollectMainFileRefs=*/false);
}

FileSymbols::FileSymbols(IndexContents IdxContents)
: IdxContents(IdxContents) {}
FileSymbols::FileSymbols(IndexContents IdxContents, bool SupportContainedRefs)
: IdxContents(IdxContents), SupportContainedRefs(SupportContainedRefs) {}

void FileSymbols::update(llvm::StringRef Key,
std::unique_ptr<SymbolSlab> Symbols,
Expand Down Expand Up @@ -395,7 +395,7 @@ FileSymbols::buildIndex(IndexType Type, DuplicateHandling DuplicateHandle,
std::move(AllRelations), std::move(Files), IdxContents,
std::make_tuple(std::move(SymbolSlabs), std::move(RefSlabs),
std::move(RefsStorage), std::move(SymsStorage)),
StorageSize);
StorageSize, SupportContainedRefs);
}
llvm_unreachable("Unknown clangd::IndexType");
}
Expand All @@ -419,11 +419,12 @@ void FileSymbols::profile(MemoryTree &MT) const {
}
}

FileIndex::FileIndex()
FileIndex::FileIndex(bool SupportContainedRefs)
: MergedIndex(&MainFileIndex, &PreambleIndex),
PreambleSymbols(IndexContents::Symbols | IndexContents::Relations),
PreambleSymbols(IndexContents::Symbols | IndexContents::Relations,
SupportContainedRefs),
PreambleIndex(std::make_unique<MemIndex>()),
MainFileSymbols(IndexContents::All),
MainFileSymbols(IndexContents::All, SupportContainedRefs),
MainFileIndex(std::make_unique<MemIndex>()) {}

void FileIndex::updatePreamble(IndexFileIn IF) {
Expand Down
5 changes: 3 additions & 2 deletions clang-tools-extra/clangd/index/FileIndex.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ enum class DuplicateHandling {
/// locking when we swap or obtain references to snapshots.
class FileSymbols {
public:
FileSymbols(IndexContents IdxContents);
FileSymbols(IndexContents IdxContents, bool SupportContainedRefs);
/// Updates all slabs associated with the \p Key.
/// If either is nullptr, corresponding data for \p Key will be removed.
/// If CountReferences is true, \p Refs will be used for counting references
Expand All @@ -91,6 +91,7 @@ class FileSymbols {

private:
IndexContents IdxContents;
bool SupportContainedRefs;

struct RefSlabAndCountReferences {
std::shared_ptr<RefSlab> Slab;
Expand All @@ -108,7 +109,7 @@ class FileSymbols {
/// FIXME: Expose an interface to remove files that are closed.
class FileIndex : public MergedIndex {
public:
FileIndex();
FileIndex(bool SupportContainedRefs);

/// Update preamble symbols of file \p Path with all declarations in \p AST
/// and macros in \p PP.
Expand Down
5 changes: 5 additions & 0 deletions clang-tools-extra/clangd/index/Index.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ bool SwapIndex::refs(const RefsRequest &R,
llvm::function_ref<void(const Ref &)> CB) const {
return snapshot()->refs(R, CB);
}
bool SwapIndex::containedRefs(
const ContainedRefsRequest &R,
llvm::function_ref<void(const ContainedRefsResult &)> CB) const {
return snapshot()->containedRefs(R, CB);
}
void SwapIndex::relations(
const RelationsRequest &R,
llvm::function_ref<void(const SymbolID &, const Symbol &)> CB) const {
Expand Down
35 changes: 35 additions & 0 deletions clang-tools-extra/clangd/index/Index.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,34 @@ struct RefsRequest {
bool WantContainer = false;
};

struct ContainedRefsRequest {
/// Note that RefKind::Call just restricts the matched SymbolKind to
/// functions, not the form of the reference (e.g. address-of-function,
/// which can indicate an indirect call, should still be caught).
static const RefKind SupportedRefKinds = RefKind::Call;

SymbolID ID;
/// If set, limit the number of refers returned from the index. The index may
/// choose to return less than this, e.g. it tries to avoid returning stale
/// results.
std::optional<uint32_t> Limit;
};

struct RelationsRequest {
llvm::DenseSet<SymbolID> Subjects;
RelationKind Predicate;
/// If set, limit the number of relations returned from the index.
std::optional<uint32_t> Limit;
};

struct ContainedRefsResult {
/// The source location where the symbol is named.
SymbolLocation Location;
RefKind Kind = RefKind::Unknown;
/// The ID of the symbol which is referred to
SymbolID Symbol;
};

/// Describes what data is covered by an index.
///
/// Indexes may contain symbols but not references from a file, etc.
Expand Down Expand Up @@ -141,6 +162,17 @@ class SymbolIndex {
virtual bool refs(const RefsRequest &Req,
llvm::function_ref<void(const Ref &)> Callback) const = 0;

/// Find all symbols that are referenced by a symbol and apply
/// \p Callback on each result.
///
/// Results should be returned in arbitrary order.
/// The returned result must be deep-copied if it's used outside Callback.
///
/// Returns true if there will be more results (limited by Req.Limit);
virtual bool containedRefs(
const ContainedRefsRequest &Req,
llvm::function_ref<void(const ContainedRefsResult &)> Callback) const = 0;

/// Finds all relations (S, P, O) stored in the index such that S is among
/// Req.Subjects and P is Req.Predicate, and invokes \p Callback for (S, O) in
/// each.
Expand Down Expand Up @@ -175,6 +207,9 @@ class SwapIndex : public SymbolIndex {
llvm::function_ref<void(const Symbol &)>) const override;
bool refs(const RefsRequest &,
llvm::function_ref<void(const Ref &)>) const override;
bool containedRefs(
const ContainedRefsRequest &,
llvm::function_ref<void(const ContainedRefsResult &)>) const override;
void relations(const RelationsRequest &,
llvm::function_ref<void(const SymbolID &, const Symbol &)>)
const override;
Expand Down
20 changes: 20 additions & 0 deletions clang-tools-extra/clangd/index/MemIndex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "MemIndex.h"
#include "FuzzyMatch.h"
#include "Quality.h"
#include "index/Index.h"
#include "support/Trace.h"

namespace clang {
Expand Down Expand Up @@ -85,6 +86,25 @@ bool MemIndex::refs(const RefsRequest &Req,
return false; // We reported all refs.
}

bool MemIndex::containedRefs(
const ContainedRefsRequest &Req,
llvm::function_ref<void(const ContainedRefsResult &)> Callback) const {
trace::Span Tracer("MemIndex refersTo");
uint32_t Remaining = Req.Limit.value_or(std::numeric_limits<uint32_t>::max());
for (const auto &Pair : Refs) {
for (const auto &R : Pair.second) {
if (!static_cast<int>(ContainedRefsRequest::SupportedRefKinds & R.Kind) ||
Req.ID != R.Container)
continue;
if (Remaining == 0)
return true; // More refs were available.
--Remaining;
Callback({R.Location, R.Kind, Pair.first});
}
}
return false; // We reported all refs.
}

void MemIndex::relations(
const RelationsRequest &Req,
llvm::function_ref<void(const SymbolID &, const Symbol &)> Callback) const {
Expand Down
4 changes: 4 additions & 0 deletions clang-tools-extra/clangd/index/MemIndex.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ class MemIndex : public SymbolIndex {
bool refs(const RefsRequest &Req,
llvm::function_ref<void(const Ref &)> Callback) const override;

bool containedRefs(const ContainedRefsRequest &Req,
llvm::function_ref<void(const ContainedRefsResult &)>
Callback) const override;

void relations(const RelationsRequest &Req,
llvm::function_ref<void(const SymbolID &, const Symbol &)>
Callback) const override;
Expand Down
34 changes: 34 additions & 0 deletions clang-tools-extra/clangd/index/Merge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,40 @@ bool MergedIndex::refs(const RefsRequest &Req,
return More || StaticHadMore;
}

bool MergedIndex::containedRefs(
const ContainedRefsRequest &Req,
llvm::function_ref<void(const ContainedRefsResult &)> Callback) const {
trace::Span Tracer("MergedIndex refersTo");
bool More = false;
uint32_t Remaining = Req.Limit.value_or(std::numeric_limits<uint32_t>::max());
// We don't want duplicated refs from the static/dynamic indexes,
// and we can't reliably deduplicate them because offsets may differ slightly.
// We consider the dynamic index authoritative and report all its refs,
// and only report static index refs from other files.
More |= Dynamic->containedRefs(Req, [&](const auto &O) {
Callback(O);
assert(Remaining != 0);
--Remaining;
});
if (Remaining == 0 && More)
return More;
auto DynamicContainsFile = Dynamic->indexedFiles();
// We return less than Req.Limit if static index returns more refs for dirty
// files.
bool StaticHadMore = Static->containedRefs(Req, [&](const auto &O) {
if ((DynamicContainsFile(O.Location.FileURI) & IndexContents::References) !=
IndexContents::None)
return; // ignore refs that have been seen from dynamic index.
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()},
Expand Down
3 changes: 3 additions & 0 deletions clang-tools-extra/clangd/index/Merge.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ class MergedIndex : public SymbolIndex {
llvm::function_ref<void(const Symbol &)>) const override;
bool refs(const RefsRequest &,
llvm::function_ref<void(const Ref &)>) const override;
bool containedRefs(
const ContainedRefsRequest &,
llvm::function_ref<void(const ContainedRefsResult &)>) const override;
void relations(const RelationsRequest &,
llvm::function_ref<void(const SymbolID &, const Symbol &)>)
const override;
Expand Down
13 changes: 13 additions & 0 deletions clang-tools-extra/clangd/index/ProjectAware.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ class ProjectAwareIndex : public SymbolIndex {
/// Query all indexes while prioritizing the associated one (if any).
bool refs(const RefsRequest &Req,
llvm::function_ref<void(const Ref &)> Callback) const override;
/// Query all indexes while prioritizing the associated one (if any).
bool containedRefs(const ContainedRefsRequest &Req,
llvm::function_ref<void(const ContainedRefsResult &)>
Callback) const override;

/// Queries only the associates index when Req.RestrictForCodeCompletion is
/// set, otherwise queries all.
Expand Down Expand Up @@ -94,6 +98,15 @@ bool ProjectAwareIndex::refs(
return false;
}

bool ProjectAwareIndex::containedRefs(
const ContainedRefsRequest &Req,
llvm::function_ref<void(const ContainedRefsResult &)> Callback) const {
trace::Span Tracer("ProjectAwareIndex::refersTo");
if (auto *Idx = getIndex())
return Idx->containedRefs(Req, Callback);
return false;
}

bool ProjectAwareIndex::fuzzyFind(
const FuzzyFindRequest &Req,
llvm::function_ref<void(const Symbol &)> Callback) const {
Expand Down
3 changes: 3 additions & 0 deletions clang-tools-extra/clangd/index/Ref.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ enum class RefKind : uint8_t {
// ^ this references Foo, but does not explicitly spell out its name
// };
Spelled = 1 << 3,
// A reference which is a call. Used as a filter for which references
// to store in data structures used for computing outgoing calls.
Call = 1 << 4,
All = Declaration | Definition | Reference | Spelled,
};

Expand Down
14 changes: 8 additions & 6 deletions clang-tools-extra/clangd/index/Serialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ readCompileCommand(Reader CmdReader, llvm::ArrayRef<llvm::StringRef> Strings) {
// The current versioning scheme is simple - non-current versions are rejected.
// If you make a breaking change, bump this version number to invalidate stored
// data. Later we may want to support some backward compatibility.
constexpr static uint32_t Version = 19;
constexpr static uint32_t Version = 20;

llvm::Expected<IndexFileIn> readRIFF(llvm::StringRef Data,
SymbolOrigin Origin) {
Expand Down Expand Up @@ -704,7 +704,8 @@ llvm::Expected<IndexFileIn> readIndexFile(llvm::StringRef Data,
}

std::unique_ptr<SymbolIndex> loadIndex(llvm::StringRef SymbolFilename,
SymbolOrigin Origin, bool UseDex) {
SymbolOrigin Origin, bool UseDex,
bool SupportContainedRefs) {
trace::Span OverallTracer("LoadIndex");
auto Buffer = llvm::MemoryBuffer::getFile(SymbolFilename);
if (!Buffer) {
Expand Down Expand Up @@ -735,10 +736,11 @@ std::unique_ptr<SymbolIndex> loadIndex(llvm::StringRef SymbolFilename,
size_t NumRelations = Relations.size();

trace::Span Tracer("BuildIndex");
auto Index = UseDex ? dex::Dex::build(std::move(Symbols), std::move(Refs),
std::move(Relations))
: MemIndex::build(std::move(Symbols), std::move(Refs),
std::move(Relations));
auto Index = UseDex
? dex::Dex::build(std::move(Symbols), std::move(Refs),
std::move(Relations), SupportContainedRefs)
: MemIndex::build(std::move(Symbols), std::move(Refs),
std::move(Relations));
vlog("Loaded {0} from {1} with estimated memory usage {2} bytes\n"
" - number of symbols: {3}\n"
" - number of refs: {4}\n"
Expand Down
3 changes: 2 additions & 1 deletion clang-tools-extra/clangd/index/Serialization.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ std::string toYAML(const Ref &);
// Build an in-memory static index from an index file.
// The size should be relatively small, so data can be managed in memory.
std::unique_ptr<SymbolIndex> loadIndex(llvm::StringRef Filename,
SymbolOrigin Origin, bool UseDex = true);
SymbolOrigin Origin, bool UseDex,
bool SupportContainedRefs);

} // namespace clangd
} // namespace clang
Expand Down
20 changes: 17 additions & 3 deletions clang-tools-extra/clangd/index/SymbolCollector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "clang-include-cleaner/Record.h"
#include "clang-include-cleaner/Types.h"
#include "index/CanonicalIncludes.h"
#include "index/Ref.h"
#include "index/Relation.h"
#include "index/Symbol.h"
#include "index/SymbolID.h"
Expand Down Expand Up @@ -660,7 +661,7 @@ bool SymbolCollector::handleDeclOccurrence(
auto FileLoc = SM.getFileLoc(Loc);
auto FID = SM.getFileID(FileLoc);
if (Opts.RefsInHeaders || FID == SM.getMainFileID()) {
addRef(ID, SymbolRef{FileLoc, FID, Roles,
addRef(ID, SymbolRef{FileLoc, FID, Roles, index::getSymbolInfo(ND).Kind,
getRefContainer(ASTNode.Parent, Opts),
isSpelled(FileLoc, *ND)});
}
Expand Down Expand Up @@ -774,8 +775,10 @@ bool SymbolCollector::handleMacroOccurrence(const IdentifierInfo *Name,
// FIXME: Populate container information for macro references.
// FIXME: All MacroRefs are marked as Spelled now, but this should be
// checked.
addRef(ID, SymbolRef{Loc, SM.getFileID(Loc), Roles, /*Container=*/nullptr,
/*Spelled=*/true});
addRef(ID,
SymbolRef{Loc, SM.getFileID(Loc), Roles, index::SymbolKind::Macro,
/*Container=*/nullptr,
/*Spelled=*/true});
}

// Collect symbols.
Expand Down Expand Up @@ -1166,6 +1169,14 @@ bool SymbolCollector::shouldIndexFile(FileID FID) {
return I.first->second;
}

static bool refIsCall(index::SymbolKind Kind) {
using SK = index::SymbolKind;
return Kind == SK::Function || Kind == SK::InstanceMethod ||
Kind == SK::ClassMethod || Kind == SK::StaticMethod ||
Kind == SK::Constructor || Kind == SK::Destructor ||
Kind == SK::ConversionFunction;
}

void SymbolCollector::addRef(SymbolID ID, const SymbolRef &SR) {
const auto &SM = ASTCtx->getSourceManager();
// FIXME: use the result to filter out references.
Expand All @@ -1177,6 +1188,9 @@ void SymbolCollector::addRef(SymbolID ID, const SymbolRef &SR) {
R.Location.End = Range.second;
R.Location.FileURI = HeaderFileURIs->toURI(*FE).c_str();
R.Kind = toRefKind(SR.Roles, SR.Spelled);
if (refIsCall(SR.Kind)) {
R.Kind |= RefKind::Call;
}
R.Container = getSymbolIDCached(SR.Container);
Refs.insert(ID, R);
}
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clangd/index/SymbolCollector.h
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ class SymbolCollector : public index::IndexDataConsumer {
SourceLocation Loc;
FileID FID;
index::SymbolRoleSet Roles;
index::SymbolKind Kind;
const Decl *Container;
bool Spelled;
};
Expand Down
52 changes: 49 additions & 3 deletions clang-tools-extra/clangd/index/dex/Dex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,14 @@ namespace clangd {
namespace dex {

std::unique_ptr<SymbolIndex> Dex::build(SymbolSlab Symbols, RefSlab Refs,
RelationSlab Rels) {
RelationSlab Rels,
bool SupportContainedRefs) {
auto Size = Symbols.bytes() + Refs.bytes();
// There is no need to include "Rels" in Data because the relations are self-
// contained, without references into a backing store.
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);
Size, SupportContainedRefs);
}

namespace {
Expand Down Expand Up @@ -120,7 +121,7 @@ class IndexBuilder {

} // namespace

void Dex::buildIndex() {
void Dex::buildIndex(bool SupportContainedRefs) {
this->Corpus = dex::Corpus(Symbols.size());
std::vector<std::pair<float, const Symbol *>> ScoredSymbols(Symbols.size());

Expand All @@ -147,6 +148,20 @@ void Dex::buildIndex() {
for (DocID SymbolRank = 0; SymbolRank < Symbols.size(); ++SymbolRank)
Builder.add(*Symbols[SymbolRank], SymbolRank);
InvertedIndex = std::move(Builder).build();

// If the containedRefs() operation is supported, build the RevRefs
// data structure used to implement it.
if (!SupportContainedRefs)
return;
for (const auto &[ID, RefList] : Refs)
for (const auto &R : RefList)
if ((R.Kind & ContainedRefsRequest::SupportedRefKinds) !=
RefKind::Unknown)
RevRefs.emplace_back(R, ID);
// Sort by container ID so we can use binary search for lookup.
llvm::sort(RevRefs, [](const RevRef &A, const RevRef &B) {
return A.ref().Container < B.ref().Container;
});
}

std::unique_ptr<Iterator> Dex::iterator(const Token &Tok) const {
Expand Down Expand Up @@ -314,6 +329,36 @@ bool Dex::refs(const RefsRequest &Req,
return false; // We reported all refs.
}

llvm::iterator_range<std::vector<Dex::RevRef>::const_iterator>
Dex::lookupRevRefs(const SymbolID &Container) const {
// equal_range() requires an element of the same type as the elements of the
// range, so construct a dummy RevRef with the container of interest.
Ref QueryRef;
QueryRef.Container = Container;
RevRef Query(QueryRef, SymbolID{});

auto ItPair = std::equal_range(RevRefs.cbegin(), RevRefs.cend(), Query,
[](const RevRef &A, const RevRef &B) {
return A.ref().Container < B.ref().Container;
});
return {ItPair.first, ItPair.second};
}

bool Dex::containedRefs(
const ContainedRefsRequest &Req,
llvm::function_ref<void(const ContainedRefsResult &)> Callback) const {
trace::Span Tracer("Dex reversed refs");
uint32_t Remaining = Req.Limit.value_or(std::numeric_limits<uint32_t>::max());
for (const auto &Rev : lookupRevRefs(Req.ID)) {
// RevRefs are already filtered to ContainedRefsRequest::SupportedRefKinds
if (Remaining == 0)
return true; // More refs were available.
--Remaining;
Callback(Rev.containedRefsResult());
}
return false; // We reported all refs.
}

void Dex::relations(
const RelationsRequest &Req,
llvm::function_ref<void(const SymbolID &, const Symbol &)> Callback) const {
Expand Down Expand Up @@ -350,6 +395,7 @@ size_t Dex::estimateMemoryUsage() const {
for (const auto &TokenToPostingList : InvertedIndex)
Bytes += TokenToPostingList.second.bytes();
Bytes += Refs.getMemorySize();
Bytes += RevRefs.size() * sizeof(RevRef);
Bytes += Relations.getMemorySize();
return Bytes + BackingDataSize;
}
Expand Down
39 changes: 31 additions & 8 deletions clang-tools-extra/clangd/index/dex/Dex.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ class Dex : public SymbolIndex {
public:
// All data must outlive this index.
template <typename SymbolRange, typename RefsRange, typename RelationsRange>
Dex(SymbolRange &&Symbols, RefsRange &&Refs, RelationsRange &&Relations)
Dex(SymbolRange &&Symbols, RefsRange &&Refs, RelationsRange &&Relations,
bool SupportContainedRefs)
: Corpus(0) {
for (auto &&Sym : Symbols)
this->Symbols.push_back(&Sym);
Expand All @@ -46,15 +47,15 @@ class Dex : public SymbolIndex {
this->Relations[std::make_pair(Rel.Subject,
static_cast<uint8_t>(Rel.Predicate))]
.push_back(Rel.Object);
buildIndex();
buildIndex(SupportContainedRefs);
}
// Symbols and Refs are owned by BackingData, Index takes ownership.
template <typename SymbolRange, typename RefsRange, typename RelationsRange,
typename Payload>
Dex(SymbolRange &&Symbols, RefsRange &&Refs, RelationsRange &&Relations,
Payload &&BackingData, size_t BackingDataSize)
Payload &&BackingData, size_t BackingDataSize, bool SupportContainedRefs)
: Dex(std::forward<SymbolRange>(Symbols), std::forward<RefsRange>(Refs),
std::forward<RelationsRange>(Relations)) {
std::forward<RelationsRange>(Relations), SupportContainedRefs) {
KeepAlive = std::shared_ptr<void>(
std::make_shared<Payload>(std::move(BackingData)), nullptr);
this->BackingDataSize = BackingDataSize;
Expand All @@ -64,16 +65,18 @@ class Dex : public SymbolIndex {
typename FileRange, typename Payload>
Dex(SymbolRange &&Symbols, RefsRange &&Refs, RelationsRange &&Relations,
FileRange &&Files, IndexContents IdxContents, Payload &&BackingData,
size_t BackingDataSize)
size_t BackingDataSize, bool SupportContainedRefs)
: Dex(std::forward<SymbolRange>(Symbols), std::forward<RefsRange>(Refs),
std::forward<RelationsRange>(Relations),
std::forward<Payload>(BackingData), BackingDataSize) {
std::forward<Payload>(BackingData), BackingDataSize,
SupportContainedRefs) {
this->Files = std::forward<FileRange>(Files);
this->IdxContents = IdxContents;
}

/// Builds an index from slabs. The index takes ownership of the slab.
static std::unique_ptr<SymbolIndex> build(SymbolSlab, RefSlab, RelationSlab);
static std::unique_ptr<SymbolIndex> build(SymbolSlab, RefSlab, RelationSlab,
bool SupportContainedRefs);

bool
fuzzyFind(const FuzzyFindRequest &Req,
Expand All @@ -85,6 +88,10 @@ class Dex : public SymbolIndex {
bool refs(const RefsRequest &Req,
llvm::function_ref<void(const Ref &)> Callback) const override;

bool containedRefs(const ContainedRefsRequest &Req,
llvm::function_ref<void(const ContainedRefsResult &)>
Callback) const override;

void relations(const RelationsRequest &Req,
llvm::function_ref<void(const SymbolID &, const Symbol &)>
Callback) const override;
Expand All @@ -95,7 +102,22 @@ class Dex : public SymbolIndex {
size_t estimateMemoryUsage() const override;

private:
void buildIndex();
class RevRef {
const Ref *Reference;
SymbolID Target;

public:
RevRef(const Ref &Reference, SymbolID Target)
: Reference(&Reference), Target(Target) {}
const Ref &ref() const { return *Reference; }
ContainedRefsResult containedRefsResult() const {
return {ref().Location, ref().Kind, Target};
}
};

void buildIndex(bool EnableOutgoingCalls);
llvm::iterator_range<std::vector<RevRef>::const_iterator>
lookupRevRefs(const SymbolID &Container) const;
std::unique_ptr<Iterator> iterator(const Token &Tok) const;
std::unique_ptr<Iterator>
createFileProximityIterator(llvm::ArrayRef<std::string> ProximityPaths) const;
Expand All @@ -116,6 +138,7 @@ class Dex : public SymbolIndex {
llvm::DenseMap<Token, PostingList> InvertedIndex;
dex::Corpus Corpus;
llvm::DenseMap<SymbolID, llvm::ArrayRef<Ref>> Refs;
std::vector<RevRef> RevRefs; // sorted by container ID
static_assert(sizeof(RelationKind) == sizeof(uint8_t),
"RelationKind should be of same size as a uint8_t");
llvm::DenseMap<std::pair<SymbolID, uint8_t>, std::vector<SymbolID>> Relations;
Expand Down
3 changes: 2 additions & 1 deletion clang-tools-extra/clangd/index/dex/dexp/Dexp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,8 @@ std::unique_ptr<SymbolIndex> openIndex(llvm::StringRef Index) {
return Index.starts_with("remote:")
? remote::getClient(Index.drop_front(strlen("remote:")),
ProjectRoot)
: loadIndex(Index, SymbolOrigin::Static, /*UseDex=*/true);
: loadIndex(Index, SymbolOrigin::Static, /*UseDex=*/true,
/*SupportContainedRefs=*/true);
}

bool runCommand(std::string Request, const SymbolIndex &Index) {
Expand Down
7 changes: 7 additions & 0 deletions clang-tools-extra/clangd/index/remote/Client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,13 @@ class IndexClient : public clangd::SymbolIndex {
return streamRPC(Request, &remote::v1::SymbolIndex::Stub::Refs, Callback);
}

bool containedRefs(const clangd::ContainedRefsRequest &Request,
llvm::function_ref<void(const ContainedRefsResult &)>
Callback) const override {
return streamRPC(Request, &remote::v1::SymbolIndex::Stub::ContainedRefs,
Callback);
}

void
relations(const clangd::RelationsRequest &Request,
llvm::function_ref<void(const SymbolID &, const clangd::Symbol &)>
Expand Down
18 changes: 18 additions & 0 deletions clang-tools-extra/clangd/index/remote/Index.proto
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,21 @@ message Relation {
optional string subject_id = 1;
optional Symbol object = 2;
}

message ContainedRefsRequest {
optional string id = 1;
optional uint32 limit = 2;
}

message ContainedRefsReply {
oneof kind {
ContainedRef stream_result = 1;
FinalResult final_result = 2;
}
}

message ContainedRef {
optional SymbolLocation location = 1;
optional uint32 kind = 2;
optional string symbol = 3;
}
2 changes: 2 additions & 0 deletions clang-tools-extra/clangd/index/remote/Service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,7 @@ service SymbolIndex {

rpc Refs(RefsRequest) returns (stream RefsReply) {}

rpc ContainedRefs(ContainedRefsRequest) returns (stream ContainedRefsReply) {}

rpc Relations(RelationsRequest) returns (stream RelationsReply) {}
}
56 changes: 56 additions & 0 deletions clang-tools-extra/clangd/index/remote/marshalling/Marshalling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,20 @@ Marshaller::fromProtobuf(const RefsRequest *Message) {
return Req;
}

llvm::Expected<clangd::ContainedRefsRequest>
Marshaller::fromProtobuf(const ContainedRefsRequest *Message) {
clangd::ContainedRefsRequest Req;
if (!Message->has_id())
return error("ContainedRefsRequest requires an id.");
auto ID = SymbolID::fromStr(Message->id());
if (!ID)
return ID.takeError();
Req.ID = *ID;
if (Message->has_limit())
Req.Limit = Message->limit();
return Req;
}

llvm::Expected<clangd::RelationsRequest>
Marshaller::fromProtobuf(const RelationsRequest *Message) {
clangd::RelationsRequest Req;
Expand Down Expand Up @@ -192,6 +206,27 @@ llvm::Expected<clangd::Ref> Marshaller::fromProtobuf(const Ref &Message) {
return Result;
}

llvm::Expected<clangd::ContainedRefsResult>
Marshaller::fromProtobuf(const ContainedRef &Message) {
clangd::ContainedRefsResult Result;
if (!Message.has_location())
return error("ContainedRef must have a location.");
if (!Message.has_kind())
return error("ContainedRef must have a kind.");
if (!Message.has_symbol())
return error("ContainedRef must have a symbol.");
auto Location = fromProtobuf(Message.location());
if (!Location)
return Location.takeError();
Result.Location = *Location;
Result.Kind = static_cast<RefKind>(Message.kind());
auto Symbol = SymbolID::fromStr(Message.symbol());
if (!Symbol)
return Symbol.takeError();
Result.Symbol = *Symbol;
return Result;
}

llvm::Expected<std::pair<clangd::SymbolID, clangd::Symbol>>
Marshaller::fromProtobuf(const Relation &Message) {
auto SubjectID = SymbolID::fromStr(Message.subject_id());
Expand Down Expand Up @@ -244,6 +279,15 @@ RefsRequest Marshaller::toProtobuf(const clangd::RefsRequest &From) {
return RPCRequest;
}

ContainedRefsRequest
Marshaller::toProtobuf(const clangd::ContainedRefsRequest &From) {
ContainedRefsRequest RPCRequest;
RPCRequest.set_id(From.ID.str());
if (From.Limit)
RPCRequest.set_limit(*From.Limit);
return RPCRequest;
}

RelationsRequest Marshaller::toProtobuf(const clangd::RelationsRequest &From) {
RelationsRequest RPCRequest;
for (const auto &ID : From.Subjects)
Expand Down Expand Up @@ -299,6 +343,18 @@ llvm::Expected<Ref> Marshaller::toProtobuf(const clangd::Ref &From) {
return Result;
}

llvm::Expected<ContainedRef>
Marshaller::toProtobuf(const clangd::ContainedRefsResult &From) {
ContainedRef Result;
auto Location = toProtobuf(From.Location);
if (!Location)
return Location.takeError();
*Result.mutable_location() = *Location;
Result.set_kind(static_cast<uint32_t>(From.Kind));
*Result.mutable_symbol() = From.Symbol.str();
return Result;
}

llvm::Expected<Relation> Marshaller::toProtobuf(const clangd::SymbolID &Subject,
const clangd::Symbol &Object) {
Relation Result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ class Marshaller {

llvm::Expected<clangd::Symbol> fromProtobuf(const Symbol &Message);
llvm::Expected<clangd::Ref> fromProtobuf(const Ref &Message);
llvm::Expected<clangd::ContainedRefsResult>
fromProtobuf(const ContainedRef &Message);
llvm::Expected<std::pair<clangd::SymbolID, clangd::Symbol>>
fromProtobuf(const Relation &Message);

Expand All @@ -48,6 +50,8 @@ class Marshaller {
llvm::Expected<clangd::FuzzyFindRequest>
fromProtobuf(const FuzzyFindRequest *Message);
llvm::Expected<clangd::RefsRequest> fromProtobuf(const RefsRequest *Message);
llvm::Expected<clangd::ContainedRefsRequest>
fromProtobuf(const ContainedRefsRequest *Message);
llvm::Expected<clangd::RelationsRequest>
fromProtobuf(const RelationsRequest *Message);

Expand All @@ -58,10 +62,13 @@ class Marshaller {
LookupRequest toProtobuf(const clangd::LookupRequest &From);
FuzzyFindRequest toProtobuf(const clangd::FuzzyFindRequest &From);
RefsRequest toProtobuf(const clangd::RefsRequest &From);
ContainedRefsRequest toProtobuf(const clangd::ContainedRefsRequest &From);
RelationsRequest toProtobuf(const clangd::RelationsRequest &From);

llvm::Expected<Symbol> toProtobuf(const clangd::Symbol &From);
llvm::Expected<Ref> toProtobuf(const clangd::Ref &From);
llvm::Expected<ContainedRef>
toProtobuf(const clangd::ContainedRefsResult &From);
llvm::Expected<Relation> toProtobuf(const clangd::SymbolID &Subject,
const clangd::Symbol &Object);

Expand Down
55 changes: 52 additions & 3 deletions clang-tools-extra/clangd/index/remote/server/Server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,53 @@ class RemoteIndexServer final : public v1::SymbolIndex::Service {
return grpc::Status::OK;
}

grpc::Status
ContainedRefs(grpc::ServerContext *Context,
const ContainedRefsRequest *Request,
grpc::ServerWriter<ContainedRefsReply> *Reply) override {
auto StartTime = stopwatch::now();
WithContextValue WithRequestContext(CurrentRequest, Context);
logRequest(*Request);
trace::Span Tracer("ContainedRefsRequest");
auto Req = ProtobufMarshaller->fromProtobuf(Request);
if (!Req) {
elog("Can not parse ContainedRefsRequest from protobuf: {0}",
Req.takeError());
return grpc::Status::CANCELLED;
}
if (!Req->Limit || *Req->Limit > LimitResults) {
log("[public] Limiting result size for ContainedRefs request from {0} to "
"{1}.",
Req->Limit, LimitResults);
Req->Limit = LimitResults;
}
unsigned Sent = 0;
unsigned FailedToSend = 0;
bool HasMore =
Index.containedRefs(*Req, [&](const clangd::ContainedRefsResult &Item) {
auto SerializedItem = ProtobufMarshaller->toProtobuf(Item);
if (!SerializedItem) {
elog("Unable to convert ContainedRefsResult to protobuf: {0}",
SerializedItem.takeError());
++FailedToSend;
return;
}
ContainedRefsReply NextMessage;
*NextMessage.mutable_stream_result() = *SerializedItem;
logResponse(NextMessage);
Reply->Write(NextMessage);
++Sent;
});
ContainedRefsReply LastMessage;
LastMessage.mutable_final_result()->set_has_more(HasMore);
logResponse(LastMessage);
Reply->Write(LastMessage);
SPAN_ATTACH(Tracer, "Sent", Sent);
SPAN_ATTACH(Tracer, "Failed to send", FailedToSend);
logRequestSummary("v1/ContainedRefs", Sent, StartTime);
return grpc::Status::OK;
}

grpc::Status Relations(grpc::ServerContext *Context,
const RelationsRequest *Request,
grpc::ServerWriter<RelationsReply> *Reply) override {
Expand Down Expand Up @@ -396,7 +443,8 @@ void hotReload(clangd::SwapIndex &Index, llvm::StringRef IndexPath,
LastStatus.getLastModificationTime(), Status->getLastModificationTime());
LastStatus = *Status;
std::unique_ptr<clang::clangd::SymbolIndex> NewIndex =
loadIndex(IndexPath, SymbolOrigin::Static);
loadIndex(IndexPath, SymbolOrigin::Static, /*UseDex=*/true,
/*SupportContainedRefs=*/true);
if (!NewIndex) {
elog("Failed to load new index. Old index will be served.");
return;
Expand Down Expand Up @@ -532,8 +580,9 @@ int main(int argc, char *argv[]) {
return Status.getError().value();
}

auto SymIndex =
clang::clangd::loadIndex(IndexPath, clang::clangd::SymbolOrigin::Static);
auto SymIndex = clang::clangd::loadIndex(
IndexPath, clang::clangd::SymbolOrigin::Static, /*UseDex=*/true,
/*SupportContainedRefs=*/true);
if (!SymIndex) {
llvm::errs() << "Failed to open the index.\n";
return -1;
Expand Down
60 changes: 41 additions & 19 deletions clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,13 @@ findContextForNS(llvm::StringRef TargetNS, const DeclContext *CurContext) {
// afterwards it can be shared with define-inline code action.
llvm::Expected<std::string>
getFunctionSourceAfterReplacements(const FunctionDecl *FD,
const tooling::Replacements &Replacements) {
const tooling::Replacements &Replacements,
bool TargetFileIsHeader) {
const auto &SM = FD->getASTContext().getSourceManager();
auto OrigFuncRange = toHalfOpenFileRange(
SM, FD->getASTContext().getLangOpts(), FD->getSourceRange());
if (!OrigFuncRange)
return error("Couldn't get range for function.");
assert(!FD->getDescribedFunctionTemplate() &&
"Define out-of-line doesn't apply to function templates.");

// Get new begin and end positions for the qualified function definition.
unsigned FuncBegin = SM.getFileOffset(OrigFuncRange->getBegin());
Expand All @@ -129,24 +128,38 @@ getFunctionSourceAfterReplacements(const FunctionDecl *FD,
if (!QualifiedFunc)
return QualifiedFunc.takeError();

auto Source = QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);
std::string TemplatePrefix;
auto AddToTemplatePrefixIfApplicable = [&](const Decl *D) {
const TemplateParameterList *Params = D->getDescribedTemplateParams();
if (!Params)
return;
for (Decl *P : *Params) {
if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(P))
TTP->removeDefaultArgument();
else if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(P))
NTTP->removeDefaultArgument();
else if (auto *TTPD = dyn_cast<TemplateTemplateParmDecl>(P))
TTPD->removeDefaultArgument();
}
std::string S;
llvm::raw_string_ostream Stream(S);
Params->print(Stream, FD->getASTContext());
if (!S.empty())
*S.rbegin() = '\n'; // Replace space with newline
TemplatePrefix.insert(0, S);
};
AddToTemplatePrefixIfApplicable(FD);
if (auto *MD = llvm::dyn_cast<CXXMethodDecl>(FD)) {
for (const CXXRecordDecl *Parent = MD->getParent(); Parent;
Parent =
llvm::dyn_cast_or_null<const CXXRecordDecl>(Parent->getParent())) {
if (const TemplateParameterList *Params =
Parent->getDescribedTemplateParams()) {
std::string S;
llvm::raw_string_ostream Stream(S);
Params->print(Stream, FD->getASTContext());
if (!S.empty())
*S.rbegin() = '\n'; // Replace space with newline
TemplatePrefix.insert(0, S);
}
AddToTemplatePrefixIfApplicable(Parent);
}
}

auto Source = QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);
if (TargetFileIsHeader)
Source.insert(0, "inline ");
if (!TemplatePrefix.empty())
Source.insert(0, TemplatePrefix);
return Source;
Expand Down Expand Up @@ -202,7 +215,8 @@ deleteTokensWithKind(const syntax::TokenBuffer &TokBuf, tok::TokenKind Kind,
llvm::Expected<std::string>
getFunctionSourceCode(const FunctionDecl *FD, const DeclContext *TargetContext,
const syntax::TokenBuffer &TokBuf,
const HeuristicResolver *Resolver) {
const HeuristicResolver *Resolver,
bool TargetFileIsHeader) {
auto &AST = FD->getASTContext();
auto &SM = AST.getSourceManager();

Expand All @@ -225,6 +239,8 @@ getFunctionSourceCode(const FunctionDecl *FD, const DeclContext *TargetContext,
return;

for (const NamedDecl *ND : Ref.Targets) {
if (ND->getKind() == Decl::TemplateTypeParm)
return;
if (ND->getDeclContext() != Ref.Targets.front()->getDeclContext()) {
elog("Targets from multiple contexts: {0}, {1}",
printQualifiedName(*Ref.Targets.front()),
Expand Down Expand Up @@ -337,7 +353,8 @@ getFunctionSourceCode(const FunctionDecl *FD, const DeclContext *TargetContext,

if (Errors)
return std::move(Errors);
return getFunctionSourceAfterReplacements(FD, DeclarationCleanups);
return getFunctionSourceAfterReplacements(FD, DeclarationCleanups,
TargetFileIsHeader);
}

struct InsertionPoint {
Expand Down Expand Up @@ -419,15 +436,15 @@ class DefineOutline : public Tweak {
Source->isOutOfLine())
return false;

// Bail out if this is a function template or specialization, as their
// Bail out if this is a function template specialization, as their
// definitions need to be visible in all including translation units.
if (Source->getDescribedFunctionTemplate())
return false;
if (Source->getTemplateSpecializationInfo())
return false;

auto *MD = llvm::dyn_cast<CXXMethodDecl>(Source);
if (!MD) {
if (Source->getDescribedFunctionTemplate())
return false;
// Can't outline free-standing functions in the same file.
return !SameFile;
}
Expand All @@ -450,6 +467,10 @@ class DefineOutline : public Tweak {
}
}

// Function templates must be defined in the same file.
if (MD->getDescribedTemplate())
SameFile = true;

// The refactoring is meaningless for unnamed classes and namespaces,
// unless we're outlining in the same file
for (const DeclContext *DC = MD->getParent(); DC; DC = DC->getParent()) {
Expand Down Expand Up @@ -485,7 +506,8 @@ class DefineOutline : public Tweak {

auto FuncDef = getFunctionSourceCode(
Source, InsertionPoint->EnclosingNamespace, Sel.AST->getTokens(),
Sel.AST->getHeuristicResolver());
Sel.AST->getHeuristicResolver(),
SameFile && isHeaderFile(Sel.AST->tuPath(), Sel.AST->getLangOpts()));
if (!FuncDef)
return FuncDef.takeError();

Expand Down
Binary file not shown.
2 changes: 2 additions & 0 deletions clang-tools-extra/clangd/test/type-hierarchy-ext.test
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# CHECK-NEXT: "data": {
# CHECK-NEXT: "symbolID": "A6576FE083F2949A"
# CHECK-NEXT: },
# CHECK-NEXT: "detail": "Child3",
# CHECK-NEXT: "kind": 23,
# CHECK-NEXT: "name": "Child3",
# CHECK-NEXT: "range": {
Expand Down Expand Up @@ -153,6 +154,7 @@
# CHECK-NEXT: "data": {
# CHECK-NEXT: "symbolID": "5705B382DFC77CBC"
# CHECK-NEXT: },
# CHECK-NEXT: "detail": "Child4",
# CHECK-NEXT: "kind": 23,
# CHECK-NEXT: "name": "Child4",
# CHECK-NEXT: "range": {
Expand Down
2 changes: 2 additions & 0 deletions clang-tools-extra/clangd/test/type-hierarchy.test
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
# CHECK-NEXT: ],
# CHECK-NEXT: "symbolID": "ECDC0C46D75120F4"
# CHECK-NEXT: },
# CHECK-NEXT: "detail": "Child1",
# CHECK-NEXT: "kind": 23,
# CHECK-NEXT: "name": "Child1",
# CHECK-NEXT: "range": {
Expand Down Expand Up @@ -112,6 +113,7 @@
# CHECK-NEXT: ],
# CHECK-NEXT: "symbolID": "A6576FE083F2949A"
# CHECK-NEXT: },
# CHECK-NEXT: "detail": "Child3",
# CHECK-NEXT: "kind": 23,
# CHECK-NEXT: "name": "Child3",
# CHECK-NEXT: "range": {
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/tool/Check.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ class Checker {
unsigned ErrCount = 0;

Checker(llvm::StringRef File, const ClangdLSPServer::Options &Opts)
: File(File), Opts(Opts) {}
: File(File), Opts(Opts), Index(/*SupportContainedRefs=*/true) {}

// Read compilation database and choose a compile command for the file.
bool buildCommand(const ThreadsafeFS &TFS) {
Expand Down
14 changes: 10 additions & 4 deletions clang-tools-extra/clangd/tool/ClangdMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@ const char TestScheme::TestDir[] = "/clangd-test";

std::unique_ptr<SymbolIndex>
loadExternalIndex(const Config::ExternalIndexSpec &External,
AsyncTaskRunner *Tasks) {
AsyncTaskRunner *Tasks, bool SupportContainedRefs) {
static const trace::Metric RemoteIndexUsed("used_remote_index",
trace::Metric::Value, "address");
switch (External.Kind) {
Expand All @@ -620,8 +620,9 @@ loadExternalIndex(const Config::ExternalIndexSpec &External,
External.Location);
auto NewIndex = std::make_unique<SwapIndex>(std::make_unique<MemIndex>());
auto IndexLoadTask = [File = External.Location,
PlaceHolder = NewIndex.get()] {
if (auto Idx = loadIndex(File, SymbolOrigin::Static, /*UseDex=*/true))
PlaceHolder = NewIndex.get(), SupportContainedRefs] {
if (auto Idx = loadIndex(File, SymbolOrigin::Static, /*UseDex=*/true,
SupportContainedRefs))
PlaceHolder->reset(std::move(Idx));
};
if (Tasks) {
Expand Down Expand Up @@ -909,7 +910,12 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
Opts.BackgroundIndexPriority = BackgroundIndexPriority;
Opts.ReferencesLimit = ReferencesLimit;
Opts.Rename.LimitFiles = RenameFileLimit;
auto PAI = createProjectAwareIndex(loadExternalIndex, Sync);
auto PAI = createProjectAwareIndex(
[SupportContainedRefs = Opts.EnableOutgoingCalls](
const Config::ExternalIndexSpec &External, AsyncTaskRunner *Tasks) {
return loadExternalIndex(External, Tasks, SupportContainedRefs);
},
Sync);
Opts.StaticIndex = PAI.get();
Opts.AsyncThreadsCount = WorkerThreadsCount;
Opts.MemoryCleanup = getMemoryCleanupFunction();
Expand Down
3 changes: 2 additions & 1 deletion clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -685,7 +685,8 @@ TEST_F(BackgroundIndexTest, Reindex) {
class BackgroundIndexRebuilderTest : public testing::Test {
protected:
BackgroundIndexRebuilderTest()
: Source(IndexContents::All), Target(std::make_unique<MemIndex>()),
: Source(IndexContents::All, /*SupportContainedRefs=*/true),
Target(std::make_unique<MemIndex>()),
Rebuilder(&Target, &Source, /*Threads=*/10) {
// Prepare FileSymbols with TestSymbol in it, for checkRebuild.
TestSymbol.ID = SymbolID("foo");
Expand Down
304 changes: 232 additions & 72 deletions clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1703,6 +1703,12 @@ class IndexRequestCollector : public SymbolIndex {
return false;
}

bool containedRefs(
const ContainedRefsRequest &,
llvm::function_ref<void(const ContainedRefsResult &)>) const override {
return false;
}

void relations(const RelationsRequest &,
llvm::function_ref<void(const SymbolID &, const Symbol &)>)
const override {}
Expand Down
46 changes: 24 additions & 22 deletions clang-tools-extra/clangd/unittests/DexTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ TEST(DexSearchTokens, SymbolPath) {

TEST(Dex, Lookup) {
auto I = Dex::build(generateSymbols({"ns::abc", "ns::xyz"}), RefSlab(),
RelationSlab());
RelationSlab(), true);
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"));
Expand All @@ -489,7 +489,7 @@ TEST(Dex, FuzzyFind) {
auto Index =
Dex::build(generateSymbols({"ns::ABC", "ns::BCD", "::ABC",
"ns::nested::ABC", "other::ABC", "other::A"}),
RefSlab(), RelationSlab());
RefSlab(), RelationSlab(), true);
FuzzyFindRequest Req;
Req.Query = "ABC";
Req.Scopes = {"ns::"};
Expand All @@ -511,7 +511,8 @@ TEST(Dex, FuzzyFind) {
}

TEST(DexTest, DexLimitedNumMatches) {
auto I = Dex::build(generateNumSymbols(0, 100), RefSlab(), RelationSlab());
auto I =
Dex::build(generateNumSymbols(0, 100), RefSlab(), RelationSlab(), true);
FuzzyFindRequest Req;
Req.Query = "5";
Req.AnyScope = true;
Expand All @@ -526,7 +527,7 @@ TEST(DexTest, DexLimitedNumMatches) {
TEST(DexTest, FuzzyMatch) {
auto I = Dex::build(
generateSymbols({"LaughingOutLoud", "LionPopulation", "LittleOldLady"}),
RefSlab(), RelationSlab());
RefSlab(), RelationSlab(), true);
FuzzyFindRequest Req;
Req.Query = "lol";
Req.AnyScope = true;
Expand All @@ -537,7 +538,7 @@ TEST(DexTest, FuzzyMatch) {

TEST(DexTest, ShortQuery) {
auto I = Dex::build(generateSymbols({"_OneTwoFourSix"}), RefSlab(),
RelationSlab());
RelationSlab(), true);
FuzzyFindRequest Req;
Req.AnyScope = true;
bool Incomplete;
Expand Down Expand Up @@ -580,7 +581,7 @@ TEST(DexTest, ShortQuery) {

TEST(DexTest, MatchQualifiedNamesWithoutSpecificScope) {
auto I = Dex::build(generateSymbols({"a::y1", "b::y2", "y3"}), RefSlab(),
RelationSlab());
RelationSlab(), true);
FuzzyFindRequest Req;
Req.AnyScope = true;
Req.Query = "y";
Expand All @@ -589,7 +590,7 @@ TEST(DexTest, MatchQualifiedNamesWithoutSpecificScope) {

TEST(DexTest, MatchQualifiedNamesWithGlobalScope) {
auto I = Dex::build(generateSymbols({"a::y1", "b::y2", "y3"}), RefSlab(),
RelationSlab());
RelationSlab(), true);
FuzzyFindRequest Req;
Req.Query = "y";
Req.Scopes = {""};
Expand All @@ -599,7 +600,7 @@ TEST(DexTest, MatchQualifiedNamesWithGlobalScope) {
TEST(DexTest, MatchQualifiedNamesWithOneScope) {
auto I =
Dex::build(generateSymbols({"a::y1", "a::y2", "a::x", "b::y2", "y3"}),
RefSlab(), RelationSlab());
RefSlab(), RelationSlab(), true);
FuzzyFindRequest Req;
Req.Query = "y";
Req.Scopes = {"a::"};
Expand All @@ -609,7 +610,7 @@ TEST(DexTest, MatchQualifiedNamesWithOneScope) {
TEST(DexTest, MatchQualifiedNamesWithMultipleScopes) {
auto I =
Dex::build(generateSymbols({"a::y1", "a::y2", "a::x", "b::y3", "y3"}),
RefSlab(), RelationSlab());
RefSlab(), RelationSlab(), true);
FuzzyFindRequest Req;
Req.Query = "y";
Req.Scopes = {"a::", "b::"};
Expand All @@ -618,7 +619,7 @@ TEST(DexTest, MatchQualifiedNamesWithMultipleScopes) {

TEST(DexTest, NoMatchNestedScopes) {
auto I = Dex::build(generateSymbols({"a::y1", "a::b::y2"}), RefSlab(),
RelationSlab());
RelationSlab(), true);
FuzzyFindRequest Req;
Req.Query = "y";
Req.Scopes = {"a::"};
Expand All @@ -627,7 +628,7 @@ TEST(DexTest, NoMatchNestedScopes) {

TEST(DexTest, WildcardScope) {
auto I = Dex::build(generateSymbols({"a::y1", "a::b::y2", "c::y3"}),
RefSlab(), RelationSlab());
RefSlab(), RelationSlab(), true);
FuzzyFindRequest Req;
Req.AnyScope = true;
Req.Query = "y";
Expand All @@ -638,7 +639,7 @@ TEST(DexTest, WildcardScope) {

TEST(DexTest, IgnoreCases) {
auto I = Dex::build(generateSymbols({"ns::ABC", "ns::abc"}), RefSlab(),
RelationSlab());
RelationSlab(), true);
FuzzyFindRequest Req;
Req.Query = "AB";
Req.Scopes = {"ns::"};
Expand All @@ -648,15 +649,15 @@ TEST(DexTest, IgnoreCases) {
TEST(DexTest, UnknownPostingList) {
// Regression test: we used to ignore unknown scopes and accept any symbol.
auto I = Dex::build(generateSymbols({"ns::ABC", "ns::abc"}), RefSlab(),
RelationSlab());
RelationSlab(), true);
FuzzyFindRequest Req;
Req.Scopes = {"ns2::"};
EXPECT_THAT(match(*I, Req), UnorderedElementsAre());
}

TEST(DexTest, Lookup) {
auto I = Dex::build(generateSymbols({"ns::abc", "ns::xyz"}), RefSlab(),
RelationSlab());
RelationSlab(), true);
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"));
Expand All @@ -671,7 +672,7 @@ TEST(DexTest, SymbolIndexOptionsFilter) {
CodeCompletionSymbol.Flags = Symbol::SymbolFlag::IndexedForCodeCompletion;
NonCodeCompletionSymbol.Flags = Symbol::SymbolFlag::None;
std::vector<Symbol> Symbols{CodeCompletionSymbol, NonCodeCompletionSymbol};
Dex I(Symbols, RefSlab(), RelationSlab());
Dex I(Symbols, RefSlab(), RelationSlab(), true);
FuzzyFindRequest Req;
Req.AnyScope = true;
Req.RestrictForCodeCompletion = false;
Expand All @@ -687,7 +688,7 @@ TEST(DexTest, ProximityPathsBoosting) {
CloseSymbol.CanonicalDeclaration.FileURI = "unittest:///a/b/c/d/e/f/file.h";

std::vector<Symbol> Symbols{CloseSymbol, RootSymbol};
Dex I(Symbols, RefSlab(), RelationSlab());
Dex I(Symbols, RefSlab(), RelationSlab(), true);

FuzzyFindRequest Req;
Req.AnyScope = true;
Expand Down Expand Up @@ -726,15 +727,15 @@ TEST(DexTests, Refs) {
Req.Filter = RefKind::Declaration | RefKind::Definition;

std::vector<std::string> Files;
EXPECT_FALSE(Dex(std::vector<Symbol>{Foo, Bar}, Refs, RelationSlab())
EXPECT_FALSE(Dex(std::vector<Symbol>{Foo, Bar}, Refs, RelationSlab(), true)
.refs(Req, [&](const Ref &R) {
Files.push_back(R.Location.FileURI);
}));
EXPECT_THAT(Files, UnorderedElementsAre("foo.h", "foo.cc"));

Req.Limit = 1;
Files.clear();
EXPECT_TRUE(Dex(std::vector<Symbol>{Foo, Bar}, Refs, RelationSlab())
EXPECT_TRUE(Dex(std::vector<Symbol>{Foo, Bar}, Refs, RelationSlab(), true)
.refs(Req, [&](const Ref &R) {
Files.push_back(R.Location.FileURI);
}));
Expand All @@ -751,7 +752,7 @@ TEST(DexTests, Relations) {
std::vector<Relation> Relations{{Parent.ID, RelationKind::BaseOf, Child1.ID},
{Parent.ID, RelationKind::BaseOf, Child2.ID}};

Dex I{Symbols, RefSlab(), Relations};
Dex I{Symbols, RefSlab(), Relations, true};

std::vector<SymbolID> Results;
RelationsRequest Req;
Expand All @@ -770,7 +771,7 @@ TEST(DexIndex, IndexedFiles) {
auto Data = std::make_pair(std::move(Symbols), std::move(Refs));
llvm::StringSet<> Files = {"unittest:///foo.cc", "unittest:///bar.cc"};
Dex I(std::move(Data.first), std::move(Data.second), RelationSlab(),
std::move(Files), IndexContents::All, std::move(Data), Size);
std::move(Files), IndexContents::All, std::move(Data), Size, true);
auto ContainsFile = I.indexedFiles();
EXPECT_EQ(ContainsFile("unittest:///foo.cc"), IndexContents::All);
EXPECT_EQ(ContainsFile("unittest:///bar.cc"), IndexContents::All);
Expand All @@ -784,7 +785,7 @@ TEST(DexTest, PreferredTypesBoosting) {
Sym2.Type = "T2";

std::vector<Symbol> Symbols{Sym1, Sym2};
Dex I(Symbols, RefSlab(), RelationSlab());
Dex I(Symbols, RefSlab(), RelationSlab(), true);

FuzzyFindRequest Req;
Req.AnyScope = true;
Expand Down Expand Up @@ -820,7 +821,8 @@ TEST(DexTest, TemplateSpecialization) {
index::SymbolProperty::TemplatePartialSpecialization);
B.insert(S);

auto I = dex::Dex::build(std::move(B).build(), RefSlab(), RelationSlab());
auto I =
dex::Dex::build(std::move(B).build(), RefSlab(), RelationSlab(), true);
FuzzyFindRequest Req;
Req.AnyScope = true;

Expand Down
52 changes: 26 additions & 26 deletions clang-tools-extra/clangd/unittests/FileIndexTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ std::unique_ptr<RelationSlab> relSlab(llvm::ArrayRef<const Relation> Rels) {
}

TEST(FileSymbolsTest, UpdateAndGet) {
FileSymbols FS(IndexContents::All);
FileSymbols FS(IndexContents::All, true);
EXPECT_THAT(runFuzzyFind(*FS.buildIndex(IndexType::Light), ""), IsEmpty());

FS.update("f1", numSlab(1, 3), refSlab(SymbolID("1"), "f1.cc"), nullptr,
Expand All @@ -116,7 +116,7 @@ TEST(FileSymbolsTest, UpdateAndGet) {
}

TEST(FileSymbolsTest, Overlap) {
FileSymbols FS(IndexContents::All);
FileSymbols FS(IndexContents::All, true);
FS.update("f1", numSlab(1, 3), nullptr, nullptr, false);
FS.update("f2", numSlab(3, 5), nullptr, nullptr, false);
for (auto Type : {IndexType::Light, IndexType::Heavy})
Expand All @@ -126,7 +126,7 @@ TEST(FileSymbolsTest, Overlap) {
}

TEST(FileSymbolsTest, MergeOverlap) {
FileSymbols FS(IndexContents::All);
FileSymbols FS(IndexContents::All, true);
auto OneSymboSlab = [](Symbol Sym) {
SymbolSlab::Builder S;
S.insert(Sym);
Expand All @@ -147,7 +147,7 @@ TEST(FileSymbolsTest, MergeOverlap) {
}

TEST(FileSymbolsTest, SnapshotAliveAfterRemove) {
FileSymbols FS(IndexContents::All);
FileSymbols FS(IndexContents::All, true);

SymbolID ID("1");
FS.update("f1", numSlab(1, 3), refSlab(ID, "f1.cc"), nullptr, false);
Expand Down Expand Up @@ -180,14 +180,14 @@ void update(FileIndex &M, llvm::StringRef Basename, llvm::StringRef Code) {
}

TEST(FileIndexTest, CustomizedURIScheme) {
FileIndex M;
FileIndex M(true);
update(M, "f", "class string {};");

EXPECT_THAT(runFuzzyFind(M, ""), ElementsAre(declURI("unittest:///f.h")));
}

TEST(FileIndexTest, IndexAST) {
FileIndex M;
FileIndex M(true);
update(M, "f1", "namespace ns { void f() {} class X {}; }");

FuzzyFindRequest Req;
Expand All @@ -198,7 +198,7 @@ TEST(FileIndexTest, IndexAST) {
}

TEST(FileIndexTest, NoLocal) {
FileIndex M;
FileIndex M(true);
update(M, "f1", "namespace ns { void f() { int local = 0; } class X {}; }");

EXPECT_THAT(
Expand All @@ -207,7 +207,7 @@ TEST(FileIndexTest, NoLocal) {
}

TEST(FileIndexTest, IndexMultiASTAndDeduplicate) {
FileIndex M;
FileIndex M(true);
update(M, "f1", "namespace ns { void f() {} class X {}; }");
update(M, "f2", "namespace ns { void ff() {} class X {}; }");

Expand All @@ -219,7 +219,7 @@ TEST(FileIndexTest, IndexMultiASTAndDeduplicate) {
}

TEST(FileIndexTest, ClassMembers) {
FileIndex M;
FileIndex M(true);
update(M, "f1", "class X { static int m1; int m2; static void f(); };");

EXPECT_THAT(runFuzzyFind(M, ""),
Expand All @@ -228,7 +228,7 @@ TEST(FileIndexTest, ClassMembers) {
}

TEST(FileIndexTest, IncludeCollected) {
FileIndex M;
FileIndex M(true);
update(
M, "f",
"// IWYU pragma: private, include <the/good/header.h>\nclass string {};");
Expand All @@ -240,7 +240,7 @@ TEST(FileIndexTest, IncludeCollected) {
}

TEST(FileIndexTest, IWYUPragmaExport) {
FileIndex M;
FileIndex M(true);

TestTU File;
File.Code = R"cpp(#pragma once
Expand Down Expand Up @@ -286,7 +286,7 @@ template <class Ty, class Arg>
vector<Ty> make_vector(Arg A) {}
)cpp";

FileIndex M;
FileIndex M(true);
update(M, "f", Source);

auto Symbols = runFuzzyFind(M, "");
Expand Down Expand Up @@ -334,7 +334,7 @@ TEST(FileIndexTest, RebuildWithPreamble) {
IgnoreDiagnostics IgnoreDiags;
auto CI = buildCompilerInvocation(PI, IgnoreDiags);

FileIndex Index;
FileIndex Index(true);
bool IndexUpdated = false;
buildPreamble(
FooCpp, *CI, PI,
Expand Down Expand Up @@ -374,7 +374,7 @@ TEST(FileIndexTest, Refs) {
RefsRequest Request;
Request.IDs = {Foo.ID};

FileIndex Index;
FileIndex Index(true);
// Add test.cc
TestTU Test;
Test.HeaderCode = HeaderCode;
Expand Down Expand Up @@ -409,7 +409,7 @@ TEST(FileIndexTest, MacroRefs) {
}
)cpp");

FileIndex Index;
FileIndex Index(true);
// Add test.cc
TestTU Test;
Test.HeaderCode = std::string(HeaderCode.code());
Expand All @@ -432,7 +432,7 @@ TEST(FileIndexTest, MacroRefs) {
}

TEST(FileIndexTest, CollectMacros) {
FileIndex M;
FileIndex M(true);
update(M, "f", "#define CLANGD 1");
EXPECT_THAT(runFuzzyFind(M, ""), Contains(qName("CLANGD")));
}
Expand All @@ -443,7 +443,7 @@ TEST(FileIndexTest, Relations) {
TU.HeaderFilename = "f.h";
TU.HeaderCode = "class A {}; class B : public A {};";
auto AST = TU.build();
FileIndex Index;
FileIndex Index(true);
Index.updatePreamble(testPath(TU.Filename), /*Version=*/"null",
AST.getASTContext(), AST.getPreprocessor(),
AST.getPragmaIncludes());
Expand Down Expand Up @@ -493,7 +493,7 @@ TEST(FileIndexTest, ReferencesInMainFileWithPreamble) {
)cpp");
TU.Code = std::string(Main.code());
auto AST = TU.build();
FileIndex Index;
FileIndex Index(true);
Index.updateMain(testPath(TU.Filename), AST);

// Expect to see references in main file, references in headers are excluded
Expand All @@ -510,7 +510,7 @@ TEST(FileIndexTest, MergeMainFileSymbols) {
Cpp.HeaderFilename = "foo.h";
Cpp.HeaderCode = CommonHeader;

FileIndex Index;
FileIndex Index(true);
auto HeaderAST = Header.build();
auto CppAST = Cpp.build();
Index.updateMain(testPath("foo.h"), HeaderAST);
Expand All @@ -524,7 +524,7 @@ TEST(FileIndexTest, MergeMainFileSymbols) {
}

TEST(FileSymbolsTest, CountReferencesNoRefSlabs) {
FileSymbols FS(IndexContents::All);
FileSymbols FS(IndexContents::All, true);
FS.update("f1", numSlab(1, 3), nullptr, nullptr, true);
FS.update("f2", numSlab(1, 3), nullptr, nullptr, false);
EXPECT_THAT(
Expand All @@ -536,7 +536,7 @@ TEST(FileSymbolsTest, CountReferencesNoRefSlabs) {
}

TEST(FileSymbolsTest, CountReferencesWithRefSlabs) {
FileSymbols FS(IndexContents::All);
FileSymbols FS(IndexContents::All, true);
FS.update("f1cpp", numSlab(1, 3), refSlab(SymbolID("1"), "f1.cpp"), nullptr,
true);
FS.update("f1h", numSlab(1, 3), refSlab(SymbolID("1"), "f1.h"), nullptr,
Expand All @@ -558,7 +558,7 @@ TEST(FileSymbolsTest, CountReferencesWithRefSlabs) {
}

TEST(FileIndexTest, StalePreambleSymbolsDeleted) {
FileIndex M;
FileIndex M(true);
TestTU File;
File.HeaderFilename = "a.h";

Expand All @@ -581,7 +581,7 @@ TEST(FileIndexTest, StalePreambleSymbolsDeleted) {

// Verifies that concurrent calls to updateMain don't "lose" any updates.
TEST(FileIndexTest, Threadsafety) {
FileIndex M;
FileIndex M(true);
Notification Go;

constexpr int Count = 10;
Expand Down Expand Up @@ -714,7 +714,7 @@ TEST(FileShardedIndexTest, Sharding) {
}

TEST(FileIndexTest, Profile) {
FileIndex FI;
FileIndex FI(true);

auto FileName = testPath("foo.cpp");
auto AST = TestTU::withHeaderCode("int a;").build();
Expand All @@ -738,7 +738,7 @@ TEST(FileIndexTest, Profile) {
}

TEST(FileSymbolsTest, Profile) {
FileSymbols FS(IndexContents::All);
FileSymbols FS(IndexContents::All, true);
FS.update("f1", numSlab(1, 2), nullptr, nullptr, false);
FS.update("f2", nullptr, refSlab(SymbolID("1"), "f1"), nullptr, false);
FS.update("f3", nullptr, nullptr,
Expand All @@ -758,7 +758,7 @@ TEST(FileSymbolsTest, Profile) {
}

TEST(FileIndexTest, MacrosFromMainFile) {
FileIndex Idx;
FileIndex Idx(true);
TestTU TU;
TU.Code = "#pragma once\n#define FOO";
TU.Filename = "foo.h";
Expand Down
8 changes: 4 additions & 4 deletions clang-tools-extra/clangd/unittests/IndexTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ TEST(MergeIndexTest, Lookup) {
}

TEST(MergeIndexTest, LookupRemovedDefinition) {
FileIndex DynamicIndex, StaticIndex;
FileIndex DynamicIndex(true), StaticIndex(true);
MergedIndex Merge(&DynamicIndex, &StaticIndex);

const char *HeaderCode = "class Foo;";
Expand Down Expand Up @@ -349,7 +349,7 @@ TEST(MergeIndexTest, FuzzyFind) {
}

TEST(MergeIndexTest, FuzzyFindRemovedSymbol) {
FileIndex DynamicIndex, StaticIndex;
FileIndex DynamicIndex(true), StaticIndex(true);
MergedIndex Merge(&DynamicIndex, &StaticIndex);

const char *HeaderCode = "class Foo;";
Expand Down Expand Up @@ -446,8 +446,8 @@ TEST(MergeTest, PreferSymbolLocationInCodegenFile) {
}

TEST(MergeIndexTest, Refs) {
FileIndex Dyn;
FileIndex StaticIndex;
FileIndex Dyn(true);
FileIndex StaticIndex(true);
MergedIndex Merge(&Dyn, &StaticIndex);

const char *HeaderCode = "class Foo;";
Expand Down
16 changes: 16 additions & 0 deletions clang-tools-extra/clangd/unittests/InlayHintTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1576,6 +1576,22 @@ TEST(TypeHints, Aliased) {
EXPECT_THAT(hintsOfKind(AST, InlayHintKind::Type), IsEmpty());
}

TEST(TypeHints, CallingConvention) {
// Check that we don't crash for lambdas without a FunctionTypeLoc
// https://github.com/clangd/clangd/issues/2223
std::string Code = R"cpp(
void test() {
[]() __cdecl {};
}
)cpp";
TestTU TU = TestTU::withCode(Code);
TU.ExtraArgs.push_back("--target=x86_64-w64-mingw32");
TU.PredefineMacros = true; // for the __cdecl
auto AST = TU.build();

EXPECT_THAT(hintsOfKind(AST, InlayHintKind::Type), IsEmpty());
}

TEST(TypeHints, Decltype) {
assertTypeHints(R"cpp(
$a[[decltype(0)]] a;
Expand Down
14 changes: 13 additions & 1 deletion clang-tools-extra/clangd/unittests/RenameTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1548,7 +1548,7 @@ TEST(CrossFileRenameTests, DirtyBuffer) {
std::string BarPath = testPath("bar.cc");
// Build the index, the index has "Foo" references from foo.cc and "Bar"
// references from bar.cc.
FileSymbols FSymbols(IndexContents::All);
FileSymbols FSymbols(IndexContents::All, true);
FSymbols.update(FooPath, nullptr, buildRefSlab(FooCode, "Foo", FooPath),
nullptr, false);
FSymbols.update(BarPath, nullptr, buildRefSlab(BarCode, "Bar", BarPath),
Expand Down Expand Up @@ -1601,6 +1601,12 @@ TEST(CrossFileRenameTests, DirtyBuffer) {
return true; // has more references
}

bool containedRefs(const ContainedRefsRequest &Req,
llvm::function_ref<void(const ContainedRefsResult &)>
Callback) const override {
return false;
}

bool fuzzyFind(
const FuzzyFindRequest &Req,
llvm::function_ref<void(const Symbol &)> Callback) const override {
Expand Down Expand Up @@ -1652,6 +1658,12 @@ TEST(CrossFileRenameTests, DeduplicateRefsFromIndex) {
return false;
}

bool containedRefs(const ContainedRefsRequest &Req,
llvm::function_ref<void(const ContainedRefsResult &)>
Callback) const override {
return false;
}

bool fuzzyFind(const FuzzyFindRequest &,
llvm::function_ref<void(const Symbol &)>) const override {
return false;
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/unittests/TestTU.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ RefSlab TestTU::headerRefs() const {

std::unique_ptr<SymbolIndex> TestTU::index() const {
auto AST = build();
auto Idx = std::make_unique<FileIndex>();
auto Idx = std::make_unique<FileIndex>(/*SupportContainedRefs=*/true);
Idx->updatePreamble(testPath(Filename), /*Version=*/"null",
AST.getASTContext(), AST.getPreprocessor(),
AST.getPragmaIncludes());
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/unittests/TestWorkspace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace clang {
namespace clangd {

std::unique_ptr<SymbolIndex> TestWorkspace::index() {
auto Index = std::make_unique<FileIndex>();
auto Index = std::make_unique<FileIndex>(/*SupportContainedRefs=*/true);
for (const auto &Input : Inputs) {
if (!Input.second.IsMainFile)
continue;
Expand Down
Loading