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/3] 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 560b8e00ed3778..6027e60609fe24 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 22485720604317..edb28ef33d6a27 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 ef45acf5016122..fd4dfe95213a57 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/3] 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 1518f177b06a0a..22aa6eeb581bd3 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 ac1e9aa5f0ff11..4d8f3f4bd780d7 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 d45d13fa034b50..20ca6bcc221d06 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 6027e60609fe24..7786f62b4347d3 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 fd4dfe95213a57..d3dacee945ff7c 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 247e52314c3f94..911cf72d72f035 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 9859577c7cf7eb..ad5c7d06a0739a 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 >From 96b7a2f537f8cb3ec127e593cc673714b355a7f9 Mon Sep 17 00:00:00 2001 From: Dimitri Ratz <[email protected]> Date: Thu, 29 Jan 2026 19:09:15 +0100 Subject: [PATCH 3/3] Fix Decl check. - Fix lit tests - Fix unit tests. --- clang-tools-extra/clangd/XRefs.cpp | 33 +++++---- .../clangd/test/call-hierarchy.test | 1 + .../clangd/test/type-hierarchy-ext.test | 3 + .../clangd/test/type-hierarchy.test | 1 + .../clangd/unittests/CallHierarchyTests.cpp | 73 ++++++++++--------- 5 files changed, 60 insertions(+), 51 deletions(-) diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp index d3dacee945ff7c..244692324309f4 100644 --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -1756,9 +1756,9 @@ class ParamUsageVisitor : public RecursiveASTVisitor<ParamUsageVisitor> { bool HasWrite = false; bool HasRead = false; const ValueDecl *TargetParam; - + 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) { @@ -1778,7 +1778,6 @@ class ParamUsageVisitor : public RecursiveASTVisitor<ParamUsageVisitor> { } return true; } - bool VisitBinaryOperator(BinaryOperator *BO) { // Check for assignment to parameter if (BO->isAssignmentOp()) { @@ -1861,8 +1860,6 @@ declToHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) { HI.range = HI.selectionRange; } - //determineParameterUsage(ND, HI); - HI.uri = URIForFile::canonicalize(*FilePath, TUPath); return HI; @@ -2441,7 +2438,8 @@ const NamedDecl *getNamedDeclFromSymbol(const Symbol &Sym, } std::vector<CallHierarchyIncomingCall> -incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index, ParsedAST &AST) { +incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index, + ParsedAST &AST) { std::vector<CallHierarchyIncomingCall> Results; if (!Index || Item.data.empty()) return Results; @@ -2451,20 +2449,22 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index, ParsedAST return Results; } - LookupRequest LR; LR.IDs.insert(*ID); - std::optional<const NamedDecl*> PVD; - Index->lookup(LR, [&ID, &AST, &PVD](const Symbol &Sym) { + std::optional<const NamedDecl *> Decl; + Index->lookup(LR, [&ID, &AST, &Decl](const Symbol &Sym) { // This callback is called once per found symbol; here we expect exactly one if (Sym.ID == *ID) { - PVD = getNamedDeclFromSymbol(Sym, AST); + Decl = getNamedDeclFromSymbol(Sym, AST); } }); - if (PVD == nullptr || !PVD.has_value()) { - // Not found in index + // Note: Decl may be nullptr if the symbol is in a header file (not the main + // file). In that case, we still want to continue and use index-based + // resolution. + if (!Decl.has_value()) { + // Symbol not found in index return Results; } @@ -2509,9 +2509,12 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index, ParsedAST 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 + if (Decl.has_value() && Decl.value() != nullptr) { + if (const auto *VD = + llvm::dyn_cast<clang::ValueDecl>(Decl.value())) { + CHI->referenceTags = + analyseParameterUsage(FD, VD); // FD is the caller of var + } } } } else { diff --git a/clang-tools-extra/clangd/test/call-hierarchy.test b/clang-tools-extra/clangd/test/call-hierarchy.test index 6548ea0068a8d0..44c89d22d9c850 100644 --- a/clang-tools-extra/clangd/test/call-hierarchy.test +++ b/clang-tools-extra/clangd/test/call-hierarchy.test @@ -9,6 +9,7 @@ # CHECK-NEXT: "result": [ # CHECK-NEXT: { # CHECK-NEXT: "data": "{{.*}}", +# CHECK-NEXT: "detail": "callee", # CHECK-NEXT: "kind": 12, # CHECK-NEXT: "name": "callee", # CHECK-NEXT: "range": { diff --git a/clang-tools-extra/clangd/test/type-hierarchy-ext.test b/clang-tools-extra/clangd/test/type-hierarchy-ext.test index 8d1a5dc31da0f1..983c7538088bfa 100644 --- a/clang-tools-extra/clangd/test/type-hierarchy-ext.test +++ b/clang-tools-extra/clangd/test/type-hierarchy-ext.test @@ -52,6 +52,7 @@ # CHECK-NEXT: ], # CHECK-NEXT: "symbolID": "8A991335E4E67D08" # CHECK-NEXT: }, +# CHECK-NEXT: "detail": "Child2", # CHECK-NEXT: "kind": 23, # CHECK-NEXT: "name": "Child2", # CHECK-NEXT: "parents": [ @@ -65,6 +66,7 @@ # CHECK-NEXT: ], # CHECK-NEXT: "symbolID": "ECDC0C46D75120F4" # CHECK-NEXT: }, +# CHECK-NEXT: "detail": "Child1", # CHECK-NEXT: "kind": 23, # CHECK-NEXT: "name": "Child1", # CHECK-NEXT: "parents": [ @@ -73,6 +75,7 @@ # CHECK-NEXT: "parents": [], # CHECK-NEXT: "symbolID": "FE546E7B648D69A7" # CHECK-NEXT: }, +# CHECK-NEXT: "detail": "Parent", # CHECK-NEXT: "kind": 23, # CHECK-NEXT: "name": "Parent", # CHECK-NEXT: "parents": [], diff --git a/clang-tools-extra/clangd/test/type-hierarchy.test b/clang-tools-extra/clangd/test/type-hierarchy.test index a5f13ab13d0b3f..cf52f7af1ec615 100644 --- a/clang-tools-extra/clangd/test/type-hierarchy.test +++ b/clang-tools-extra/clangd/test/type-hierarchy.test @@ -22,6 +22,7 @@ # CHECK-NEXT: ], # CHECK-NEXT: "symbolID": "8A991335E4E67D08" # CHECK-NEXT: }, +# CHECK-NEXT: "detail": "Child2", # CHECK-NEXT: "kind": 23, # CHECK-NEXT: "name": "Child2", # CHECK-NEXT: "range": { diff --git a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp index ad5c7d06a0739a..5e0553c3bbfcb4 100644 --- a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp +++ b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp @@ -67,6 +67,12 @@ ::testing::Matcher<CallHierarchyOutgoingCall> oFromRanges(RangeMatchers... M) { UnorderedElementsAre(M...)); } +template <typename... References> +::testing::Matcher<CallHierarchyItem> withReferenceTags(References... refs) { + return Field(&CallHierarchyItem::referenceTags, + UnorderedElementsAre(refs...)); +} + TEST(CallHierarchy, IncomingOneFileCpp) { Annotations Source(R"cpp( void call^ee(int); @@ -402,7 +408,8 @@ TEST(CallHierarchy, MultiFileCpp) { withDetail("nsa::caller1"))), iFromRanges(Caller1C.range())))); - auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST); + auto IncomingLevel2 = + incomingCalls(IncomingLevel1[0].from, Index.get(), AST); ASSERT_THAT( IncomingLevel2, ElementsAre( @@ -411,13 +418,15 @@ TEST(CallHierarchy, MultiFileCpp) { AllOf(from(AllOf(withName("caller3"), withDetail("nsa::caller3"))), iFromRanges(Caller3C.range("Caller1"))))); - auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get(), AST); + 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(), AST); + auto IncomingLevel4 = + incomingCalls(IncomingLevel3[0].from, Index.get(), AST); EXPECT_THAT(IncomingLevel4, IsEmpty()); }; @@ -558,7 +567,8 @@ TEST(CallHierarchy, IncomingMultiFileObjC) { ElementsAre(AllOf(from(withName("caller1")), iFromRanges(Caller1C.range())))); - auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST); + auto IncomingLevel2 = + incomingCalls(IncomingLevel1[0].from, Index.get(), AST); ASSERT_THAT(IncomingLevel2, ElementsAre(AllOf(from(withName("caller2")), iFromRanges(Caller2C.range("A"), @@ -566,12 +576,14 @@ TEST(CallHierarchy, IncomingMultiFileObjC) { AllOf(from(withName("caller3")), iFromRanges(Caller3C.range("Caller1"))))); - auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get(), AST); + 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(), AST); + auto IncomingLevel4 = + incomingCalls(IncomingLevel3[0].from, Index.get(), AST); EXPECT_THAT(IncomingLevel4, IsEmpty()); }; @@ -671,7 +683,8 @@ TEST(CallHierarchy, HierarchyOnVar) { } TEST(CallHierarchy, HierarchyOnVarWithReadReference) { - // Tests that the call hierarchy works on non-local variables and read/write tags are set. + // Tests that the call hierarchy works on non-local variables and a read is + // set. Annotations Source(R"cpp( int v^ar = 1; void caller() { @@ -687,11 +700,14 @@ TEST(CallHierarchy, HierarchyOnVarWithReadReference) { 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); + ASSERT_FALSE(IncomingLevel1.empty()); + EXPECT_THAT( + IncomingLevel1, + ElementsAre(AllOf(from( + AllOf(withName("caller"), withReferenceTags(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() { @@ -706,11 +722,14 @@ TEST(CallHierarchy, HierarchyOnVarWithWriteReference) { 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); + ASSERT_FALSE(IncomingLevel1.empty()); + EXPECT_THAT( + IncomingLevel1, + ElementsAre(AllOf(from( + AllOf(withName("caller"), withReferenceTags(ReferenceTag::Write)))))); } -TEST(CallHierarchy, HierarchyOnVarWithUnaryWriteReference) { - // Tests that the call hierarchy works on non-local variables and read/write tags are set. +TEST(CallHierarchy, HierarchyOnVarWithUnaryReadWriteReference) { Annotations Source(R"cpp( int v^ar = 1; void caller() { @@ -725,32 +744,14 @@ TEST(CallHierarchy, HierarchyOnVarWithUnaryWriteReference) { 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); + ASSERT_FALSE(IncomingLevel1.empty()); + EXPECT_THAT( + IncomingLevel1, + UnorderedElementsAre(AllOf(from( + AllOf(withName("caller"), + withReferenceTags(ReferenceTag::Write, ReferenceTag::Read)))))); } -// 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( _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
