https://github.com/isanbard updated 
https://github.com/llvm/llvm-project/pull/235116

>From 66edef27ee3b44a294a8511dbf7525dc80a09bcc Mon Sep 17 00:00:00 2001
From: Bill Wendling <[email protected]>
Date: Fri, 23 Oct 2026 14:40:40 -0700
Subject: [PATCH 1/2] [libclang] Resolve dependent member references in
 clang_getCursorReferenced

Inside a class template, references to members of a dependent base, such
as 'this->size()' where the base is 'A<T>', aren't bound to a declaration
until instantiation, so clang_getCursorReferenced() returned a null cursor
for them.

Fall back to HeuristicResolver, which clangd uses for the same purpose, to
look the name up in the primary template, and return the result if it's a
single declaration. This also resolves 'type_trait<T>::value' in a
requires-clause or concept to the primary template's static member.

'A<T>::size()' (a qualified reference to an instance member) still isn't
resolved, because HeuristicResolver only considers static members for a
DependentScopeDeclRefExpr.

Addresses part of #15370.

Assisted-by: Claude Opus 5.5
---
 .../Index/cursor-ref-dependent-member.cpp     | 33 +++++++++++++++++++
 clang/test/Index/index-concepts.cpp           |  6 ++--
 clang/tools/libclang/CIndex.cpp               | 20 +++++++++++
 3 files changed, 56 insertions(+), 3 deletions(-)
 create mode 100644 clang/test/Index/cursor-ref-dependent-member.cpp

diff --git a/clang/test/Index/cursor-ref-dependent-member.cpp 
b/clang/test/Index/cursor-ref-dependent-member.cpp
new file mode 100644
index 00000000000000..0f2093a2491b26
--- /dev/null
+++ b/clang/test/Index/cursor-ref-dependent-member.cpp
@@ -0,0 +1,33 @@
+// Check that clang_getCursorReferenced() resolves member references through a
+// dependent base class to the member of the primary template.
+
+template <typename T>
+class A {
+public:
+  int size() const { return 1; }
+  int field;
+  void ov(int);
+  void ov(float);
+};
+
+template <typename T>
+class B : public A<T> {
+public:
+  int f1() const { return this->size(); }
+  int f2() const { return this->field; }
+  void f3() { this->ov(1); }
+  int f4() const { return A<T>::size(); }
+};
+
+// RUN: c-index-test -test-load-source local %s | FileCheck %s
+
+// CHECK: 16:27: CallExpr=:7:7 Extent=[16:27 - 16:39]
+// CHECK: 16:27: MemberRefExpr=:7:7 Extent=[16:27 - 16:37]
+// CHECK: 17:27: MemberRefExpr=:8:7 Extent=[17:27 - 17:38]
+
+// An overloaded member can't be resolved to a single declaration.
+// CHECK: 18:15: MemberRefExpr= Extent=[18:15 - 18:23]
+
+// HeuristicResolver only considers static members for a qualified dependent
+// name, so 'A<T>::size()' isn't resolved yet.
+// CHECK: 19:27: DeclRefExpr= Extent=[19:27 - 19:37]
diff --git a/clang/test/Index/index-concepts.cpp 
b/clang/test/Index/index-concepts.cpp
index 17fbd02f34fe8f..04a814ca4894d8 100644
--- a/clang/test/Index/index-concepts.cpp
+++ b/clang/test/Index/index-concepts.cpp
@@ -13,7 +13,7 @@ struct type_trait<int> {
 template <class T>
 requires (type_trait<T>::value)
 // CHECK: index-concepts.cpp:[[@LINE-1]]:10: ParenExpr= Extent=[[[@LINE-1]]:10 
- [[@LINE-1]]:32]
-// CHECK: index-concepts.cpp:[[@LINE-2]]:11: DeclRefExpr= 
Extent=[[[@LINE-2]]:11 - [[@LINE-2]]:31]
+// CHECK: index-concepts.cpp:[[@LINE-2]]:11: DeclRefExpr=:5:23 
Extent=[[[@LINE-2]]:11 - [[@LINE-2]]:31]
 // CHECK: index-concepts.cpp:[[@LINE-3]]:11: TemplateRef=type_trait:4:8 
Extent=[[[@LINE-3]]:11 - [[@LINE-3]]:21]
 // CHECK: index-concepts.cpp:[[@LINE-4]]:22: TypeRef=T:13:17 
Extent=[[[@LINE-4]]:22 - [[@LINE-4]]:23]
 void indexRequiresClause() {
@@ -22,7 +22,7 @@ void indexRequiresClause() {
 template<class T>
 requires (type_trait<T>::value)
 // CHECK: index-concepts.cpp:[[@LINE-1]]:10: ParenExpr= Extent=[[[@LINE-1]]:10 
- [[@LINE-1]]:32]
-// CHECK: index-concepts.cpp:[[@LINE-2]]:11: DeclRefExpr= 
Extent=[[[@LINE-2]]:11 - [[@LINE-2]]:31]
+// CHECK: index-concepts.cpp:[[@LINE-2]]:11: DeclRefExpr=:5:23 
Extent=[[[@LINE-2]]:11 - [[@LINE-2]]:31]
 // CHECK: index-concepts.cpp:[[@LINE-3]]:11: TemplateRef=type_trait:4:8 
Extent=[[[@LINE-3]]:11 - [[@LINE-3]]:21]
 // CHECK: index-concepts.cpp:[[@LINE-4]]:22: TypeRef=T:22:16 
Extent=[[[@LINE-4]]:22 - [[@LINE-4]]:23]
 class IndexRequiresClauseInClass {};
@@ -31,7 +31,7 @@ template <class T>
 concept Con1 = type_trait<T>::value;
 // CHECK: index-concepts.cpp:[[@LINE-1]]:9: ConceptDecl=Con1:[[@LINE-1]]:9 
(Definition) Extent=[[[@LINE-2]]:1 - [[@LINE-1]]:36]
 // CHECK: index-concepts.cpp:[[@LINE-3]]:17: 
TemplateTypeParameter=T:[[@LINE-3]]:17 (Definition) Extent=[[[@LINE-3]]:11 - 
[[@LINE-3]]:18] [access=public]
-// CHECK: index-concepts.cpp:[[@LINE-3]]:16: DeclRefExpr= 
Extent=[[[@LINE-3]]:16 - [[@LINE-3]]:36]
+// CHECK: index-concepts.cpp:[[@LINE-3]]:16: DeclRefExpr=:5:23 
Extent=[[[@LINE-3]]:16 - [[@LINE-3]]:36]
 // CHECK: index-concepts.cpp:[[@LINE-4]]:16: TemplateRef=type_trait:4:8 
Extent=[[[@LINE-4]]:16 - [[@LINE-4]]:26]
 // CHECK: index-concepts.cpp:[[@LINE-5]]:27: TypeRef=T:30:17 
Extent=[[[@LINE-5]]:27 - [[@LINE-5]]:28]
 
diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp
index 8d246d41f31f66..6f4dc06691ca7f 100644
--- a/clang/tools/libclang/CIndex.cpp
+++ b/clang/tools/libclang/CIndex.cpp
@@ -46,6 +46,7 @@
 #include "clang/Lex/Lexer.h"
 #include "clang/Lex/PreprocessingRecord.h"
 #include "clang/Lex/Preprocessor.h"
+#include "clang/Sema/HeuristicResolver.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/StringSwitch.h"
 #include "llvm/Config/llvm-config.h"
@@ -5281,6 +5282,23 @@ static const Decl *getDeclFromExpr(const Stmt *E) {
   return nullptr;
 }
 
+// Resolve a reference through a dependent name, such as 'A<T>::size()' or
+// 'this->size()' in a class template deriving from 'A<T>'. The AST doesn't 
bind
+// these to a declaration until instantiation, so use the same heuristics as
+// clangd to look the name up in the primary template. Return null unless
+// exactly one declaration is found, since the cursor can only name one.
+static const Decl *getDeclFromDependentExpr(const Expr *E, ASTContext &Ctx) {
+  HeuristicResolver Resolver(Ctx);
+  std::vector<const NamedDecl *> Decls;
+  if (const auto *CE = dyn_cast<CallExpr>(E))
+    Decls = Resolver.resolveCalleeOfCallExpr(CE);
+  else if (const auto *ME = dyn_cast<CXXDependentScopeMemberExpr>(E))
+    Decls = Resolver.resolveMemberExpr(ME);
+  else if (const auto *RE = dyn_cast<DependentScopeDeclRefExpr>(E))
+    Decls = Resolver.resolveDeclRefExpr(RE);
+  return Decls.size() == 1 ? Decls.front() : nullptr;
+}
+
 static SourceLocation getLocationFromExpr(const Expr *E) {
   if (const ImplicitCastExpr *CE = dyn_cast<ImplicitCastExpr>(E))
     return getLocationFromExpr(CE->getSubExpr());
@@ -7173,6 +7191,8 @@ CXCursor clang_getCursorReferenced(CXCursor C) {
   if (clang_isExpression(C.kind)) {
     const Expr *E = getCursorExpr(C);
     const Decl *D = getDeclFromExpr(E);
+    if (!D && E)
+      D = getDeclFromDependentExpr(E, getCursorContext(C));
     if (D) {
       CXCursor declCursor = MakeCXCursor(D, tu);
       declCursor = getSelectorIdentifierCursor(getSelectorIdentifierIndex(C),

>From da9371146bf64efa9b2a76fcf8a3704864b2fd5d Mon Sep 17 00:00:00 2001
From: Bill Wendling <[email protected]>
Date: Fri, 23 Oct 2026 15:13:25 -0700
Subject: [PATCH 2/2] [libclang] Only resolve dependent member names, not
 arbitrary callees

HeuristicResolver::resolveCalleeOfCallExpr() does more than resolve
dependent names: for a callee that is itself a call it returns the class the
inner call returns, for a dependent ADL call it returns the single visible
candidate, and it looks through parentheses. That made the cursor for
'm.make()()' refer to the class 'Functor', and changed the result for some
non-dependent calls.

Instead, look through a call to its callee as getDeclFromExpr() does, and
only use HeuristicResolver for CXXDependentScopeMemberExpr and
DependentScopeDeclRefExpr. Add tests for both cases and a release note.

Assisted-by: Claude Opus 5.5
---
 clang/docs/ReleaseNotes.md                    |  4 ++++
 .../Index/cursor-ref-dependent-member.cpp     | 15 +++++++++++++
 clang/tools/libclang/CIndex.cpp               | 21 ++++++++++++-------
 3 files changed, 32 insertions(+), 8 deletions(-)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index adcdd911beb09c..402af16200aed8 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -1371,6 +1371,10 @@ documentation](CIR/index.md) for more information 
regarding ClangIR's status and
 
 - visit identifier initializers in lambda capture as VarDecl instead of 
VariableRef. Warning: this changes behaviour.
 - add unary operator handling to `clang_getCursorSpelling()`.
+- `clang_getCursorReferenced()` now resolves a reference to a member of a
+  dependent base class, such as `this->size()` in a class template deriving
+  from `A<T>`, to the member of the primary template. This also affects
+  `clang_getCursorDefinition()` and `clang_findReferencesInFile()`.
 
 ### Code Completion
 
diff --git a/clang/test/Index/cursor-ref-dependent-member.cpp 
b/clang/test/Index/cursor-ref-dependent-member.cpp
index 0f2093a2491b26..a170037fd04a61 100644
--- a/clang/test/Index/cursor-ref-dependent-member.cpp
+++ b/clang/test/Index/cursor-ref-dependent-member.cpp
@@ -31,3 +31,18 @@ class B : public A<T> {
 // HeuristicResolver only considers static members for a qualified dependent
 // name, so 'A<T>::size()' isn't resolved yet.
 // CHECK: 19:27: DeclRefExpr= Extent=[19:27 - 19:37]
+
+// Only dependent member names are resolved. A call's callee is looked through
+// as for non-dependent calls, so 'm.make()()' refers to 'make', not to the
+// class it returns. A dependent call found by ADL isn't resolved, since other
+// candidates may be found at instantiation.
+template <typename T> struct Functor { void operator()(); };
+template <typename T> struct Maker { Functor<T> make(); };
+void g(int);
+template <typename T> void h1(Maker<T> m) { m.make()(); }
+template <typename T> void h2(T t) { g(t); }
+
+// CHECK: 42:45: CallExpr=:40:49 Extent=[42:45 - 42:55]
+// CHECK: 42:45: CallExpr=:40:49 Extent=[42:45 - 42:53]
+// CHECK: 42:45: MemberRefExpr=:40:49 Extent=[42:45 - 42:51]
+// CHECK: 43:38: CallExpr= Extent=[43:38 - 43:42]
diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp
index 6f4dc06691ca7f..91aaa58a0d0e3d 100644
--- a/clang/tools/libclang/CIndex.cpp
+++ b/clang/tools/libclang/CIndex.cpp
@@ -5282,17 +5282,22 @@ static const Decl *getDeclFromExpr(const Stmt *E) {
   return nullptr;
 }
 
-// Resolve a reference through a dependent name, such as 'A<T>::size()' or
-// 'this->size()' in a class template deriving from 'A<T>'. The AST doesn't 
bind
-// these to a declaration until instantiation, so use the same heuristics as
-// clangd to look the name up in the primary template. Return null unless
-// exactly one declaration is found, since the cursor can only name one.
+// Resolve a reference through a dependent name, such as 'this->size()' in a
+// class template deriving from 'A<T>'. The AST doesn't bind these to a
+// declaration until instantiation, so use the same heuristics as clangd to 
look
+// the name up in the primary template. Return null unless exactly one
+// declaration is found, since the cursor can only name one.
 static const Decl *getDeclFromDependentExpr(const Expr *E, ASTContext &Ctx) {
+  // Look through a call to its callee, as getDeclFromExpr() does. Only the
+  // dependent names themselves are resolved: HeuristicResolver's handling of
+  // other callees (e.g., returning the class a call returns) doesn't name what
+  // the call refers to.
+  if (const auto *CE = dyn_cast<CallExpr>(E))
+    return getDeclFromDependentExpr(CE->getCallee(), Ctx);
+
   HeuristicResolver Resolver(Ctx);
   std::vector<const NamedDecl *> Decls;
-  if (const auto *CE = dyn_cast<CallExpr>(E))
-    Decls = Resolver.resolveCalleeOfCallExpr(CE);
-  else if (const auto *ME = dyn_cast<CXXDependentScopeMemberExpr>(E))
+  if (const auto *ME = dyn_cast<CXXDependentScopeMemberExpr>(E))
     Decls = Resolver.resolveMemberExpr(ME);
   else if (const auto *RE = dyn_cast<DependentScopeDeclRefExpr>(E))
     Decls = Resolver.resolveDeclRefExpr(RE);

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to