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 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/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 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

>From 98e1167c36c1b4a3b62b779c3647d9ea6e99606a 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            | 34 +++++----
 clang-tools-extra/clangd/XRefs.h              |  3 +-
 .../clangd/test/call-hierarchy.test           |  1 +
 .../clangd/test/type-hierarchy-ext.test       |  3 +
 .../clangd/test/type-hierarchy.test           |  1 +
 .../clangd/unittests/CallHierarchyTests.cpp   | 74 ++++++++++---------
 6 files changed, 63 insertions(+), 53 deletions(-)

diff --git a/clang-tools-extra/clangd/XRefs.cpp 
b/clang-tools-extra/clangd/XRefs.cpp
index d3dacee945ff7..69947c77238f6 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -68,7 +68,6 @@
 #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>
@@ -1756,9 +1755,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 +1777,6 @@ class ParamUsageVisitor : public 
RecursiveASTVisitor<ParamUsageVisitor> {
     }
     return true;
   }
-  
   bool VisitBinaryOperator(BinaryOperator *BO) {
     // Check for assignment to parameter
     if (BO->isAssignmentOp()) {
@@ -1861,8 +1859,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 +2437,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 +2448,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 +2508,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/XRefs.h b/clang-tools-extra/clangd/XRefs.h
index 911cf72d72f03..31e7b0b14440f 100644
--- a/clang-tools-extra/clangd/XRefs.h
+++ b/clang-tools-extra/clangd/XRefs.h
@@ -148,7 +148,8 @@ std::vector<CallHierarchyItem>
 prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath);
 
 std::vector<CallHierarchyIncomingCall>
-incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index, 
ParsedAST &AST);
+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/test/call-hierarchy.test 
b/clang-tools-extra/clangd/test/call-hierarchy.test
index 6548ea0068a8d..44c89d22d9c85 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 8d1a5dc31da0f..983c7538088bf 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 a5f13ab13d0b3..cf52f7af1ec61 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 ad5c7d06a0739..15387b10aad50 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,15 @@ 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, 
ReferenceTag::Read))))));
 }
 
-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 +745,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

Reply via email to