https://github.com/ratzdi updated https://github.com/llvm/llvm-project/pull/172462
>From f4f37212f33182f0f65a0f9ae0f4db58f94dedf4 Mon Sep 17 00:00:00 2001 From: Dimitri Ratz <[email protected]> Date: Wed, 10 Dec 2025 16:20:56 +0100 Subject: [PATCH 1/2] Determine the access method to a referenced symbol. The access method helps to know in a quick way, what is happening with the referenced symbol somewhere in the code, i.e. is the referenced symbol used as a rvalue object to read a value from or as lvalue object to write some value into. The basic access methods to a referenced symbol can be write and/or read. --- clang-tools-extra/clangd/Protocol.cpp | 9 +- clang-tools-extra/clangd/Protocol.h | 13 +++ clang-tools-extra/clangd/XRefs.cpp | 147 ++++++++++++++++++++++++-- 3 files changed, 158 insertions(+), 11 deletions(-) diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp index 560b8e00ed377..6027e60609fe2 100644 --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -209,7 +209,7 @@ bool fromJSON(const llvm::json::Value &Params, ChangeAnnotation &R, O.map("needsConfirmation", R.needsConfirmation) && O.mapOptional("description", R.description); } -llvm::json::Value toJSON(const ChangeAnnotation & CA) { +llvm::json::Value toJSON(const ChangeAnnotation &CA) { llvm::json::Object Result{{"label", CA.label}}; if (CA.needsConfirmation) Result["needsConfirmation"] = *CA.needsConfirmation; @@ -1478,6 +1478,10 @@ llvm::json::Value toJSON(SymbolTag Tag) { return llvm::json::Value(static_cast<int>(Tag)); } +llvm::json::Value toJSON(ReferenceTag Tag) { + return llvm::json::Value(static_cast<int>(Tag)); +} + llvm::json::Value toJSON(const CallHierarchyItem &I) { llvm::json::Object Result{{"name", I.name}, {"kind", static_cast<int>(I.kind)}, @@ -1490,6 +1494,9 @@ llvm::json::Value toJSON(const CallHierarchyItem &I) { Result["detail"] = I.detail; if (!I.data.empty()) Result["data"] = I.data; + if (!I.referenceTags.empty()) + Result["referenceTags"] = I.referenceTags; + return std::move(Result); } diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h index 2248572060431..edb28ef33d6a2 100644 --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -405,6 +405,13 @@ enum class SymbolKind { Operator = 25, TypeParameter = 26 }; + +/// Tags describing the reference kind. +enum class ReferenceTag { + Read = 1, + Write = 2, +}; + bool fromJSON(const llvm::json::Value &, SymbolKind &, llvm::json::Path); constexpr auto SymbolKindMin = static_cast<size_t>(SymbolKind::File); constexpr auto SymbolKindMax = static_cast<size_t>(SymbolKind::TypeParameter); @@ -1513,6 +1520,9 @@ struct TypeHierarchyItem { /// The kind of this item. SymbolKind kind; + /// The symbol tags for this item. + std::vector<ReferenceTag> referenceTags; + /// More detail for this item, e.g. the signature of a function. std::optional<std::string> detail; @@ -1590,6 +1600,9 @@ struct CallHierarchyItem { /// Tags for this item. std::vector<SymbolTag> tags; + /// The tags describing reference kinds of this item. + std::vector<ReferenceTag> referenceTags; + /// More detaill for this item, e.g. the signature of a function. std::string detail; diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp index ef45acf501612..fd4dfe95213a5 100644 --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -68,6 +68,7 @@ #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" +#include "support/Logger.h" #include <algorithm> #include <optional> #include <string> @@ -1749,6 +1750,130 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, return OS; } +// This would require a visitor pattern: +class ParamUsageVisitor : public RecursiveASTVisitor<ParamUsageVisitor> { +public: + bool HasWrite = false; + bool HasRead = false; + const ParmVarDecl *TargetParam; + + ParamUsageVisitor(const ParmVarDecl *P) : TargetParam(P) {} + + bool VisitUnaryOperator(UnaryOperator *UO) { + // Check for increment/decrement on parameter + if (UO->isIncrementDecrementOp()) { + if (auto *DRE = dyn_cast<DeclRefExpr>(UO->getSubExpr())) { + if (DRE->getDecl() == TargetParam) { + HasWrite = true; + } + } + } + return true; + } + + bool VisitBinaryOperator(BinaryOperator *BO) { + // Check for assignment to parameter + if (BO->isAssignmentOp()) { + if (auto *DRE = dyn_cast<DeclRefExpr>(BO->getLHS())) { + if (DRE->getDecl() == TargetParam) { + HasWrite = true; + } + } + } + // Any other use is at least a read + if (auto *DRE = dyn_cast<DeclRefExpr>(BO->getRHS())) { + if (DRE->getDecl() == TargetParam) { + HasRead = true; + } + } + return true; + } +}; + +static std::vector<ReferenceTag> analyseParameterUsage(const FunctionDecl *FD) { + std::vector<ReferenceTag> Result; + // This requires more sophisticated analysis - checking if param is modified + const Stmt *Body = FD->getBody(); + if (!Body) + return Result; // No definition available + + for (unsigned I = 0; I < FD->getNumParams(); ++I) { + const ParmVarDecl *Param = FD->getParamDecl(I); + + // Check const qualifier + // QualType ParamType = Param->getType(); + // bool IsReadOnly = ParamType.isConstQualified(); + + // For deeper analysis, you'd need to: + // 1. Walk the AST of the function body + // 2. Find all references to the parameter + // 3. Check if they appear on the left side of assignments (write) + // or only on the right side (read) + + ParamUsageVisitor Visitor(Param); + Visitor.TraverseStmt(const_cast<Stmt *>(Body)); + if (Visitor.HasWrite) + Result.push_back(ReferenceTag::Write); + if (Visitor.HasRead) + Result.push_back(ReferenceTag::Read); + } + return Result; +} + +template <typename HierarchyItem> +static void determineParameterUsage(const NamedDecl &ND, HierarchyItem &HI) { + // Get parent context and check if it's a function parameter + const DeclContext *DC = ND.getDeclContext(); + elog("determineParameterUsage: called for ND={0}", ND.getNameAsString()); + if (const auto *TD = llvm::dyn_cast<TagDecl>(DC)) { + elog("determineParameterUsage: ND is inside a TagDecl: {0}", TD->getNameAsString()); + // No parameter analysis for TagDecl parent contexts. + } else if (const auto *FD = llvm::dyn_cast<FunctionDecl>(DC)) { + elog("determineParameterUsage: ND is inside a FunctionDecl"); + for (unsigned I = 0; I < FD->getNumParams(); ++I) { + if (FD->getParamDecl(I) == &ND) { + elog("determineParameterUsage: ND is the {0}-th parameter of function {1}", I, FD->getNameAsString()); + + const ParmVarDecl *Param = FD->getParamDecl(I); + QualType ParamType = Param->getType(); + + bool IsConst = false; + bool IsConstRef = false; + bool IsConstPtr = false; + + // Check if const (read-only) + IsConst = ParamType.isConstQualified(); + elog("determineParameterUsage: ParamType.isConstQualified() = {0}", IsConst); + + // Check if it's a const reference + if (const auto *RT = ParamType->getAs<ReferenceType>()) { + IsConstRef = RT->getPointeeType().isConstQualified(); + elog("determineParameterUsage: ParamType is ReferenceType, isConstQualified = {0}", IsConstRef); + } + + // Check if it's a const pointer + if (const auto *PT = ParamType->getAs<PointerType>()) { + IsConstPtr = PT->getPointeeType().isConstQualified(); + elog("determineParameterUsage: ParamType is PointerType, isConstQualified = {0}", IsConstPtr); + } + + if (IsConst && IsConstRef && IsConstPtr) { + elog("determineParameterUsage: All const checks passed, marking as Read"); + HI.referenceTags.push_back(ReferenceTag::Read); + } else { + elog("determineParameterUsage: Performing analyseParameterUsage"); + HI.referenceTags = analyseParameterUsage(FD); + } + + break; + } + elog("determineParameterUsage: ND is not parameter {0} of function {1}", I, FD->getNameAsString()); + } + } else { + elog("determineParameterUsage: ND is not inside a FunctionDecl or TagDecl"); + } +} + template <typename HierarchyItem> static std::optional<HierarchyItem> declToHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) { @@ -1790,6 +1915,8 @@ declToHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) { HI.range = HI.selectionRange; } + determineParameterUsage(ND, HI); + HI.uri = URIForFile::canonicalize(*FilePath, TUPath); return HI; @@ -2097,15 +2224,15 @@ static QualType typeForNode(const ASTContext &Ctx, const HeuristicResolver *H, return QualType(); } -// Given a type targeted by the cursor, return one or more types that are more interesting -// to target. -static void unwrapFindType( - QualType T, const HeuristicResolver* H, llvm::SmallVector<QualType>& Out) { +// Given a type targeted by the cursor, return one or more types that are more +// interesting to target. +static void unwrapFindType(QualType T, const HeuristicResolver *H, + llvm::SmallVector<QualType> &Out) { if (T.isNull()) return; // If there's a specific type alias, point at that rather than unwrapping. - if (const auto* TDT = T->getAs<TypedefType>()) + if (const auto *TDT = T->getAs<TypedefType>()) return Out.push_back(QualType(TDT, 0)); // Pointers etc => pointee type. @@ -2139,8 +2266,8 @@ static void unwrapFindType( } // Convenience overload, to allow calling this without the out-parameter -static llvm::SmallVector<QualType> unwrapFindType( - QualType T, const HeuristicResolver* H) { +static llvm::SmallVector<QualType> unwrapFindType(QualType T, + const HeuristicResolver *H) { llvm::SmallVector<QualType> Result; unwrapFindType(T, H, Result); return Result; @@ -2162,9 +2289,9 @@ std::vector<LocatedSymbol> findType(ParsedAST &AST, Position Pos, std::vector<LocatedSymbol> LocatedSymbols; // NOTE: unwrapFindType might return duplicates for something like - // unique_ptr<unique_ptr<T>>. Let's *not* remove them, because it gives you some - // information about the type you may have not known before - // (since unique_ptr<unique_ptr<T>> != unique_ptr<T>). + // unique_ptr<unique_ptr<T>>. Let's *not* remove them, because it gives you + // some information about the type you may have not known before (since + // unique_ptr<unique_ptr<T>> != unique_ptr<T>). for (const QualType &Type : unwrapFindType( typeForNode(AST.getASTContext(), AST.getHeuristicResolver(), N), AST.getHeuristicResolver())) >From 2c2a42c3c95cedcde8bd06ff23c68efc753ea5ab Mon Sep 17 00:00:00 2001 From: Dimitri Ratz <[email protected]> Date: Fri, 2 Jan 2026 14:02:50 +0100 Subject: [PATCH 2/2] Added Visitor to traverse AST to detemine access type of expressions. --- clang-tools-extra/clangd/ClangdLSPServer.cpp | 2 +- clang-tools-extra/clangd/ClangdServer.cpp | 13 +- clang-tools-extra/clangd/ClangdServer.h | 2 +- clang-tools-extra/clangd/Protocol.cpp | 2 +- clang-tools-extra/clangd/XRefs.cpp | 174 +++++++++--------- clang-tools-extra/clangd/XRefs.h | 2 +- .../clangd/unittests/CallHierarchyTests.cpp | 137 +++++++++++--- 7 files changed, 212 insertions(+), 120 deletions(-) diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp index 1518f177b06a0..22aa6eeb581bd 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -1389,7 +1389,7 @@ void ClangdLSPServer::onPrepareCallHierarchy( void ClangdLSPServer::onCallHierarchyIncomingCalls( const CallHierarchyIncomingCallsParams &Params, Callback<std::vector<CallHierarchyIncomingCall>> Reply) { - Server->incomingCalls(Params.item, std::move(Reply)); + Server->incomingCalls(Params.item.uri.file(), Params.item, std::move(Reply)); } void ClangdLSPServer::onClangdInlayHints(const InlayHintsParams &Params, diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp index ac1e9aa5f0ff1..4d8f3f4bd780d 100644 --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -908,12 +908,15 @@ void ClangdServer::prepareCallHierarchy( } void ClangdServer::incomingCalls( - const CallHierarchyItem &Item, + PathRef File, const CallHierarchyItem &Item, Callback<std::vector<CallHierarchyIncomingCall>> CB) { - WorkScheduler->run("Incoming Calls", "", - [CB = std::move(CB), Item, this]() mutable { - CB(clangd::incomingCalls(Item, Index)); - }); + auto Action = [Item, CB = std::move(CB), + this](llvm::Expected<InputsAndAST> InpAST) mutable { + if (!InpAST) + return CB(InpAST.takeError()); + CB(clangd::incomingCalls(Item, Index, InpAST->AST)); + }; + WorkScheduler->runWithAST("Incoming Calls", File, std::move(Action)); } void ClangdServer::inlayHints(PathRef File, std::optional<Range> RestrictRange, diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h index d45d13fa034b5..20ca6bcc221d0 100644 --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -299,7 +299,7 @@ class ClangdServer { Callback<std::vector<CallHierarchyItem>> CB); /// Resolve incoming calls for a given call hierarchy item. - void incomingCalls(const CallHierarchyItem &Item, + void incomingCalls(PathRef File, const CallHierarchyItem &Item, Callback<std::vector<CallHierarchyIncomingCall>>); /// Resolve outgoing calls for a given call hierarchy item. diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp index 6027e60609fe2..7786f62b4347d 100644 --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -1496,7 +1496,7 @@ llvm::json::Value toJSON(const CallHierarchyItem &I) { Result["data"] = I.data; if (!I.referenceTags.empty()) Result["referenceTags"] = I.referenceTags; - + return std::move(Result); } diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp index fd4dfe95213a5..d3dacee945ff7 100644 --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -1755,10 +1755,18 @@ class ParamUsageVisitor : public RecursiveASTVisitor<ParamUsageVisitor> { public: bool HasWrite = false; bool HasRead = false; - const ParmVarDecl *TargetParam; + const ValueDecl *TargetParam; - ParamUsageVisitor(const ParmVarDecl *P) : TargetParam(P) {} + ParamUsageVisitor(const ValueDecl *P) : TargetParam(P) {} + // Any reference to the target is at least a read, unless we later + // identify it specifically as a write (e.g. assignment/inc/dec on LHS). + bool VisitDeclRefExpr(DeclRefExpr *DRE) { + if (DRE->getDecl() == TargetParam) + HasRead = true; + return true; + } + bool VisitUnaryOperator(UnaryOperator *UO) { // Check for increment/decrement on parameter if (UO->isIncrementDecrementOp()) { @@ -1774,14 +1782,17 @@ class ParamUsageVisitor : public RecursiveASTVisitor<ParamUsageVisitor> { bool VisitBinaryOperator(BinaryOperator *BO) { // Check for assignment to parameter if (BO->isAssignmentOp()) { - if (auto *DRE = dyn_cast<DeclRefExpr>(BO->getLHS())) { + if (isa<DeclRefExpr>(BO->getLHS())) { + auto *DRE = dyn_cast<DeclRefExpr>(BO->getLHS()); if (DRE->getDecl() == TargetParam) { HasWrite = true; } } } + // Any other use is at least a read - if (auto *DRE = dyn_cast<DeclRefExpr>(BO->getRHS())) { + if (isa<DeclRefExpr>(BO->getRHS())) { + auto *DRE = dyn_cast<DeclRefExpr>(BO->getRHS()); if (DRE->getDecl() == TargetParam) { HasRead = true; } @@ -1790,90 +1801,25 @@ class ParamUsageVisitor : public RecursiveASTVisitor<ParamUsageVisitor> { } }; -static std::vector<ReferenceTag> analyseParameterUsage(const FunctionDecl *FD) { +static std::vector<ReferenceTag> analyseParameterUsage(const FunctionDecl *FD, + const ValueDecl *PVD) { std::vector<ReferenceTag> Result; - // This requires more sophisticated analysis - checking if param is modified const Stmt *Body = FD->getBody(); if (!Body) return Result; // No definition available - for (unsigned I = 0; I < FD->getNumParams(); ++I) { - const ParmVarDecl *Param = FD->getParamDecl(I); + // Walk the body and determine read/write usage of the referenced variable + // within this function. + ParamUsageVisitor Visitor(PVD); + Visitor.TraverseStmt(const_cast<Stmt *>(Body)); + if (Visitor.HasWrite) + Result.push_back(ReferenceTag::Write); + if (Visitor.HasRead) + Result.push_back(ReferenceTag::Read); - // Check const qualifier - // QualType ParamType = Param->getType(); - // bool IsReadOnly = ParamType.isConstQualified(); - - // For deeper analysis, you'd need to: - // 1. Walk the AST of the function body - // 2. Find all references to the parameter - // 3. Check if they appear on the left side of assignments (write) - // or only on the right side (read) - - ParamUsageVisitor Visitor(Param); - Visitor.TraverseStmt(const_cast<Stmt *>(Body)); - if (Visitor.HasWrite) - Result.push_back(ReferenceTag::Write); - if (Visitor.HasRead) - Result.push_back(ReferenceTag::Read); - } return Result; } -template <typename HierarchyItem> -static void determineParameterUsage(const NamedDecl &ND, HierarchyItem &HI) { - // Get parent context and check if it's a function parameter - const DeclContext *DC = ND.getDeclContext(); - elog("determineParameterUsage: called for ND={0}", ND.getNameAsString()); - if (const auto *TD = llvm::dyn_cast<TagDecl>(DC)) { - elog("determineParameterUsage: ND is inside a TagDecl: {0}", TD->getNameAsString()); - // No parameter analysis for TagDecl parent contexts. - } else if (const auto *FD = llvm::dyn_cast<FunctionDecl>(DC)) { - elog("determineParameterUsage: ND is inside a FunctionDecl"); - for (unsigned I = 0; I < FD->getNumParams(); ++I) { - if (FD->getParamDecl(I) == &ND) { - elog("determineParameterUsage: ND is the {0}-th parameter of function {1}", I, FD->getNameAsString()); - - const ParmVarDecl *Param = FD->getParamDecl(I); - QualType ParamType = Param->getType(); - - bool IsConst = false; - bool IsConstRef = false; - bool IsConstPtr = false; - - // Check if const (read-only) - IsConst = ParamType.isConstQualified(); - elog("determineParameterUsage: ParamType.isConstQualified() = {0}", IsConst); - - // Check if it's a const reference - if (const auto *RT = ParamType->getAs<ReferenceType>()) { - IsConstRef = RT->getPointeeType().isConstQualified(); - elog("determineParameterUsage: ParamType is ReferenceType, isConstQualified = {0}", IsConstRef); - } - - // Check if it's a const pointer - if (const auto *PT = ParamType->getAs<PointerType>()) { - IsConstPtr = PT->getPointeeType().isConstQualified(); - elog("determineParameterUsage: ParamType is PointerType, isConstQualified = {0}", IsConstPtr); - } - - if (IsConst && IsConstRef && IsConstPtr) { - elog("determineParameterUsage: All const checks passed, marking as Read"); - HI.referenceTags.push_back(ReferenceTag::Read); - } else { - elog("determineParameterUsage: Performing analyseParameterUsage"); - HI.referenceTags = analyseParameterUsage(FD); - } - - break; - } - elog("determineParameterUsage: ND is not parameter {0} of function {1}", I, FD->getNameAsString()); - } - } else { - elog("determineParameterUsage: ND is not inside a FunctionDecl or TagDecl"); - } -} - template <typename HierarchyItem> static std::optional<HierarchyItem> declToHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) { @@ -1904,7 +1850,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.detail = printQualifiedName(ND); HI.kind = SK; HI.range = Range{sourceLocToPosition(SM, DeclRange->getBegin()), sourceLocToPosition(SM, DeclRange->getEnd())}; @@ -1915,7 +1861,7 @@ declToHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) { HI.range = HI.selectionRange; } - determineParameterUsage(ND, HI); + //determineParameterUsage(ND, HI); HI.uri = URIForFile::canonicalize(*FilePath, TUPath); @@ -2464,8 +2410,38 @@ prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath) { return Result; } +// Tries to find a NamedDecl in the AST that matches the given Symbol. +// Returns nullptr if the symbol is not found in the current AST. +const NamedDecl *getNamedDeclFromSymbol(const Symbol &Sym, + const ParsedAST &AST) { + // Try to convert the symbol to a location and find the decl at that location + auto SymLoc = symbolToLocation(Sym, AST.tuPath()); + if (!SymLoc) + return nullptr; + + // Check if the symbol location is in the main file + if (SymLoc->uri.file() != AST.tuPath()) + return nullptr; + + // Convert LSP position to source location + const auto &SM = AST.getSourceManager(); + auto CurLoc = sourceLocationInMainFile(SM, SymLoc->range.start); + if (!CurLoc) { + llvm::consumeError(CurLoc.takeError()); + return nullptr; + } + + // Get all decls at this location + auto Decls = getDeclAtPosition(const_cast<ParsedAST &>(AST), *CurLoc, {}); + if (Decls.empty()) + return nullptr; + + // Return the first decl (usually the most specific one) + return Decls[0]; +} + std::vector<CallHierarchyIncomingCall> -incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) { +incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index, ParsedAST &AST) { std::vector<CallHierarchyIncomingCall> Results; if (!Index || Item.data.empty()) return Results; @@ -2474,6 +2450,24 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) { elog("incomingCalls failed to find symbol: {0}", ID.takeError()); return Results; } + + + LookupRequest LR; + LR.IDs.insert(*ID); + + std::optional<const NamedDecl*> PVD; + Index->lookup(LR, [&ID, &AST, &PVD](const Symbol &Sym) { + // This callback is called once per found symbol; here we expect exactly one + if (Sym.ID == *ID) { + PVD = getNamedDeclFromSymbol(Sym, AST); + } + }); + + if (PVD == nullptr || !PVD.has_value()) { + // Not found in index + return Results; + } + // In this function, we find incoming calls based on the index only. // In principle, the AST could have more up-to-date information about // occurrences within the current file. However, going from a SymbolID @@ -2510,7 +2504,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())) { + + std::optional<CallHierarchyItem> CHI; + if (auto *ND = getNamedDeclFromSymbol(Caller, AST)) { + CHI = declToCallHierarchyItem(*ND, AST.tuPath()); + if (const auto *FD = llvm::dyn_cast<clang::FunctionDecl>(ND)) { + if (isa<ValueDecl>(PVD.value())) { + const auto *VD = llvm::dyn_cast<clang::ValueDecl>(PVD.value()); + CHI->referenceTags = analyseParameterUsage(FD, VD); // FD is the caller of var + } + } + } else { + CHI = symbolToCallHierarchyItem(Caller, Item.uri.file()); + } + + if (CHI) { std::vector<Range> FromRanges; for (const Location &L : It->second) { if (L.uri != CHI->uri) { diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h index 247e52314c3f9..911cf72d72f03 100644 --- a/clang-tools-extra/clangd/XRefs.h +++ b/clang-tools-extra/clangd/XRefs.h @@ -148,7 +148,7 @@ std::vector<CallHierarchyItem> prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath); std::vector<CallHierarchyIncomingCall> -incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index); +incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index, ParsedAST &AST); std::vector<CallHierarchyOutgoingCall> outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index); diff --git a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp index 9859577c7cf7e..ad5c7d06a0739 100644 --- a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp +++ b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp @@ -89,12 +89,12 @@ TEST(CallHierarchy, IncomingOneFileCpp) { std::vector<CallHierarchyItem> Items = prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); ASSERT_THAT(Items, ElementsAre(withName("callee"))); - auto IncomingLevel1 = incomingCalls(Items[0], Index.get()); + auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST); ASSERT_THAT( IncomingLevel1, ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("caller1"))), iFromRanges(Source.range("Callee"))))); - auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get()); + auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST); ASSERT_THAT( IncomingLevel2, ElementsAre(AllOf(from(AllOf(withName("caller2"), withDetail("caller2"))), @@ -103,13 +103,13 @@ TEST(CallHierarchy, IncomingOneFileCpp) { AllOf(from(AllOf(withName("caller3"), withDetail("caller3"))), iFromRanges(Source.range("Caller1C"))))); - auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get()); + auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get(), AST); ASSERT_THAT( IncomingLevel3, ElementsAre(AllOf(from(AllOf(withName("caller3"), withDetail("caller3"))), iFromRanges(Source.range("Caller2"))))); - auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get()); + auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get(), AST); EXPECT_THAT(IncomingLevel4, IsEmpty()); } @@ -137,12 +137,12 @@ TEST(CallHierarchy, IncomingOneFileObjC) { std::vector<CallHierarchyItem> Items = prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); ASSERT_THAT(Items, ElementsAre(withName("callee"))); - auto IncomingLevel1 = incomingCalls(Items[0], Index.get()); + auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST); ASSERT_THAT(IncomingLevel1, ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("MyClass::caller1"))), iFromRanges(Source.range("Callee"))))); - auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get()); + auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST); ASSERT_THAT(IncomingLevel2, ElementsAre(AllOf(from(AllOf(withName("caller2"), withDetail("MyClass::caller2"))), @@ -152,13 +152,13 @@ TEST(CallHierarchy, IncomingOneFileObjC) { withDetail("MyClass::caller3"))), iFromRanges(Source.range("Caller1C"))))); - auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get()); + auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get(), AST); ASSERT_THAT(IncomingLevel3, ElementsAre(AllOf(from(AllOf(withName("caller3"), withDetail("MyClass::caller3"))), iFromRanges(Source.range("Caller2"))))); - auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get()); + auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get(), AST); EXPECT_THAT(IncomingLevel4, IsEmpty()); } @@ -184,18 +184,18 @@ TEST(CallHierarchy, IncomingIncludeOverrides) { std::vector<CallHierarchyItem> Items = prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); ASSERT_THAT(Items, ElementsAre(withName("callee"))); - auto IncomingLevel1 = incomingCalls(Items[0], Index.get()); + auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST); ASSERT_THAT(IncomingLevel1, ElementsAre(AllOf(from(AllOf(withName("Func"), withDetail("Implementation::Func"))), iFromRanges(Source.range("Callee"))))); - auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get()); + auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST); ASSERT_THAT( IncomingLevel2, ElementsAre(AllOf(from(AllOf(withName("Test"), withDetail("Test"))), iFromRanges(Source.range("FuncCall"))))); - auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get()); + auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get(), AST); EXPECT_THAT(IncomingLevel3, IsEmpty()); } @@ -221,13 +221,13 @@ TEST(CallHierarchy, MainFileOnlyRef) { std::vector<CallHierarchyItem> Items = prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); ASSERT_THAT(Items, ElementsAre(withName("callee"))); - auto IncomingLevel1 = incomingCalls(Items[0], Index.get()); + auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST); ASSERT_THAT( IncomingLevel1, ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("caller1"))), iFromRanges(Source.range("Callee"))))); - auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get()); + auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST); EXPECT_THAT( IncomingLevel2, ElementsAre(AllOf(from(AllOf(withName("caller2"), withDetail("caller2"))), @@ -256,7 +256,7 @@ TEST(CallHierarchy, IncomingQualified) { std::vector<CallHierarchyItem> Items = prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); ASSERT_THAT(Items, ElementsAre(withName("Waldo::find"))); - auto Incoming = incomingCalls(Items[0], Index.get()); + auto Incoming = incomingCalls(Items[0], Index.get(), AST); EXPECT_THAT( Incoming, ElementsAre( @@ -396,13 +396,13 @@ TEST(CallHierarchy, MultiFileCpp) { std::vector<CallHierarchyItem> Items = prepareCallHierarchy(AST, Pos, TUPath); ASSERT_THAT(Items, ElementsAre(withName("callee"))); - auto IncomingLevel1 = incomingCalls(Items[0], Index.get()); + auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST); ASSERT_THAT(IncomingLevel1, ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("nsa::caller1"))), iFromRanges(Caller1C.range())))); - auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get()); + auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST); ASSERT_THAT( IncomingLevel2, ElementsAre( @@ -411,13 +411,13 @@ TEST(CallHierarchy, MultiFileCpp) { AllOf(from(AllOf(withName("caller3"), withDetail("nsa::caller3"))), iFromRanges(Caller3C.range("Caller1"))))); - auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get()); + auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get(), AST); ASSERT_THAT(IncomingLevel3, ElementsAre(AllOf(from(AllOf(withName("caller3"), withDetail("nsa::caller3"))), iFromRanges(Caller3C.range("Caller2"))))); - auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get()); + auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get(), AST); EXPECT_THAT(IncomingLevel4, IsEmpty()); }; @@ -553,12 +553,12 @@ TEST(CallHierarchy, IncomingMultiFileObjC) { std::vector<CallHierarchyItem> Items = prepareCallHierarchy(AST, Pos, TUPath); ASSERT_THAT(Items, ElementsAre(withName("callee"))); - auto IncomingLevel1 = incomingCalls(Items[0], Index.get()); + auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST); ASSERT_THAT(IncomingLevel1, ElementsAre(AllOf(from(withName("caller1")), iFromRanges(Caller1C.range())))); - auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get()); + auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST); ASSERT_THAT(IncomingLevel2, ElementsAre(AllOf(from(withName("caller2")), iFromRanges(Caller2C.range("A"), @@ -566,12 +566,12 @@ TEST(CallHierarchy, IncomingMultiFileObjC) { AllOf(from(withName("caller3")), iFromRanges(Caller3C.range("Caller1"))))); - auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get()); + auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get(), AST); ASSERT_THAT(IncomingLevel3, ElementsAre(AllOf(from(withName("caller3")), iFromRanges(Caller3C.range("Caller2"))))); - auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get()); + auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get(), AST); EXPECT_THAT(IncomingLevel4, IsEmpty()); }; @@ -616,7 +616,7 @@ TEST(CallHierarchy, CallInLocalVarDecl) { prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); ASSERT_THAT(Items, ElementsAre(withName("callee"))); - auto Incoming = incomingCalls(Items[0], Index.get()); + auto Incoming = incomingCalls(Items[0], Index.get(), AST); ASSERT_THAT(Incoming, ElementsAre(AllOf(from(withName("caller1")), iFromRanges(Source.range("call1"))), AllOf(from(withName("caller2")), @@ -643,7 +643,7 @@ TEST(CallHierarchy, HierarchyOnField) { std::vector<CallHierarchyItem> Items = prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); ASSERT_THAT(Items, ElementsAre(withName("var1"))); - auto IncomingLevel1 = incomingCalls(Items[0], Index.get()); + auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST); ASSERT_THAT(IncomingLevel1, ElementsAre(AllOf(from(withName("caller")), iFromRanges(Source.range("Callee"))))); @@ -664,12 +664,93 @@ TEST(CallHierarchy, HierarchyOnVar) { std::vector<CallHierarchyItem> Items = prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); ASSERT_THAT(Items, ElementsAre(withName("var"))); - auto IncomingLevel1 = incomingCalls(Items[0], Index.get()); + auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST); ASSERT_THAT(IncomingLevel1, ElementsAre(AllOf(from(withName("caller")), iFromRanges(Source.range("Callee"))))); } +TEST(CallHierarchy, HierarchyOnVarWithReadReference) { + // Tests that the call hierarchy works on non-local variables and read/write tags are set. + Annotations Source(R"cpp( + int v^ar = 1; + void caller() { + int x = 0; + x = var; + } + )cpp"); + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + auto Index = TU.index(); + + std::vector<CallHierarchyItem> Items = + prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); + ASSERT_THAT(Items, ElementsAre(withName("var"))); + auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST); + EXPECT_TRUE(IncomingLevel1.front().from.referenceTags.at(0) == ReferenceTag::Read); +} + +TEST(CallHierarchy, HierarchyOnVarWithWriteReference) { + // Tests that the call hierarchy works on non-local variables and read/write tags are set. + Annotations Source(R"cpp( + int v^ar = 1; + void caller() { + var = 2; + } + )cpp"); + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + auto Index = TU.index(); + + std::vector<CallHierarchyItem> Items = + prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); + ASSERT_THAT(Items, ElementsAre(withName("var"))); + auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST); + EXPECT_TRUE(IncomingLevel1.front().from.referenceTags.at(0) == ReferenceTag::Write); +} + +TEST(CallHierarchy, HierarchyOnVarWithUnaryWriteReference) { + // Tests that the call hierarchy works on non-local variables and read/write tags are set. + Annotations Source(R"cpp( + int v^ar = 1; + void caller() { + var++; + } + )cpp"); + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + auto Index = TU.index(); + + std::vector<CallHierarchyItem> Items = + prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); + ASSERT_THAT(Items, ElementsAre(withName("var"))); + auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST); + EXPECT_TRUE(IncomingLevel1.front().from.referenceTags.at(0) == ReferenceTag::Write); +} + +// TEST(CallHierarchy, HierarchyOnFieldWithReadWriteReference) { +// // Tests that the call hierarchy works on non-local variables and read/write tags are set. +// Annotations Source(R"cpp( +// struct Vars { +// int v^ar1 = 1; +// }; +// void caller() { +// Vars values; +// values.var1 = 2; +// } +// )cpp"); +// TestTU TU = TestTU::withCode(Source.code()); +// auto AST = TU.build(); +// auto Index = TU.index(); +// +// std::vector<CallHierarchyItem> Items = +// prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); +// ASSERT_THAT(Items, ElementsAre(withName("var1"))); +// auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST); +// EXPECT_TRUE(IncomingLevel1.front().from.referenceTags.at(0) == ReferenceTag::Read); +// //EXPECT_TRUE(IncomingLevel1.front().from.referenceTags.at(1) == ReferenceTag::Read); +// } + TEST(CallHierarchy, HierarchyOnEnumConstant) { // Tests that the call hierarchy works on enum constants. Annotations Source(R"cpp( @@ -686,14 +767,14 @@ TEST(CallHierarchy, HierarchyOnEnumConstant) { std::vector<CallHierarchyItem> Items = prepareCallHierarchy(AST, Source.point("Heads"), testPath(TU.Filename)); ASSERT_THAT(Items, ElementsAre(withName("heads"))); - auto IncomingLevel1 = incomingCalls(Items[0], Index.get()); + auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST); ASSERT_THAT(IncomingLevel1, ElementsAre(AllOf(from(withName("caller")), iFromRanges(Source.range("CallerH"))))); Items = prepareCallHierarchy(AST, Source.point("Tails"), testPath(TU.Filename)); ASSERT_THAT(Items, ElementsAre(withName("tails"))); - IncomingLevel1 = incomingCalls(Items[0], Index.get()); + IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST); ASSERT_THAT(IncomingLevel1, ElementsAre(AllOf(from(withName("caller")), iFromRanges(Source.range("CallerT"))))); @@ -718,7 +799,7 @@ TEST(CallHierarchy, CallInDifferentFileThanCaller) { prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename)); ASSERT_THAT(Items, ElementsAre(withName("callee"))); - auto Incoming = incomingCalls(Items[0], Index.get()); + auto Incoming = incomingCalls(Items[0], Index.get(), AST); // The only call site is in the source file, which is a different file from // the declaration of the function containing the call, which is in the _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
