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
