[clang] [clang-tools-extra] [clang] Heuristic resolution for explicit object parameter (PR #155143)
Mythreya Kuricheti via cfe-commits
cfe-commits at lists.llvm.org
Sun Aug 24 00:56:11 PDT 2025
https://github.com/MythreyaK updated https://github.com/llvm/llvm-project/pull/155143
>From c1cdb3909c0bb55ce21d339f087c1e38616fec3f Mon Sep 17 00:00:00 2001
From: Mythreya Kuricheti <git at mythreya.dev>
Date: Sat, 23 Aug 2025 23:57:18 -0700
Subject: [PATCH 1/3] [clang] Heuristic resolution for explicit object
parameter
Assume `self` parameter is of the parent record type
---
.../clangd/unittests/RenameTests.cpp | 43 ++++++++++++++++++-
clang/lib/Sema/HeuristicResolver.cpp | 26 +++++++++++
.../unittests/Sema/HeuristicResolverTest.cpp | 19 +++++++-
3 files changed, 86 insertions(+), 2 deletions(-)
diff --git a/clang-tools-extra/clangd/unittests/RenameTests.cpp b/clang-tools-extra/clangd/unittests/RenameTests.cpp
index 2cb0722f7f285..4701ae07d0d12 100644
--- a/clang-tools-extra/clangd/unittests/RenameTests.cpp
+++ b/clang-tools-extra/clangd/unittests/RenameTests.cpp
@@ -17,10 +17,11 @@
#include "clang/Tooling/Core/Replacement.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/MemoryBuffer.h"
-#include <algorithm>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include <algorithm>
+
namespace clang {
namespace clangd {
namespace {
@@ -2468,6 +2469,46 @@ TEST(CrossFileRenameTests, adjustmentCost) {
}
}
+TEST(RenameTest, RenameWithExplicitObjectPararameter) {
+ Annotations Test = {R"cpp(
+ struct Foo {
+ int [[memb^er]] {};
+ auto&& getter1(this auto&& self) {
+ auto local = [&] {
+ return self.[[memb^er]];
+ }();
+ return local + self.[[memb^er]];
+ }
+ auto&& getter2(this Foo&& self) {
+ return self.[[memb^er]];
+ }
+ int normal() {
+ return [[memb^er]];
+ }
+ };
+ )cpp"};
+
+ auto TU = TestTU::withCode(Test.code());
+ TU.ExtraArgs.push_back("-std=c++23");
+ auto AST = TU.build();
+
+ llvm::StringRef NewName = "m_member";
+ auto Index = TU.index();
+
+ for (const auto &RenamePos : Test.points()) {
+ auto RenameResult = rename({RenamePos, NewName, AST, testPath(TU.Filename),
+ getVFSFromAST(AST), Index.get()});
+
+ ASSERT_TRUE(bool(RenameResult)) << RenameResult.takeError();
+ auto Res = RenameResult.get();
+
+ ASSERT_TRUE(bool(RenameResult)) << RenameResult.takeError();
+ ASSERT_EQ(1u, RenameResult->GlobalChanges.size());
+ EXPECT_EQ(applyEdits(std::move(RenameResult->GlobalChanges)).front().second,
+ expectedResult(Test, NewName));
+ }
+}
+
} // namespace
} // namespace clangd
} // namespace clang
diff --git a/clang/lib/Sema/HeuristicResolver.cpp b/clang/lib/Sema/HeuristicResolver.cpp
index 933841beeac3d..20f7d0fca2066 100644
--- a/clang/lib/Sema/HeuristicResolver.cpp
+++ b/clang/lib/Sema/HeuristicResolver.cpp
@@ -14,6 +14,7 @@
#include "clang/AST/TemplateBase.h"
#include "clang/AST/Type.h"
#include "llvm/ADT/identity.h"
+#include <optional>
namespace clang {
@@ -301,9 +302,34 @@ std::vector<const NamedDecl *> HeuristicResolverImpl::resolveMemberExpr(
return {};
}
+ // check if member expr is in the context of an explicit object method
+ // If so, it's safe to assume the templated arg is of type of the record
+ const auto ExplicitMemberHeuristic =
+ [&](const Expr *Base) -> std::optional<QualType> {
+ if (auto *DeclRef = dyn_cast_if_present<DeclRefExpr>(Base)) {
+ auto *PrDecl = dyn_cast_if_present<ParmVarDecl>(DeclRef->getDecl());
+
+ if (PrDecl && PrDecl->isExplicitObjectParameter()) {
+ auto CxxRecord = dyn_cast_if_present<CXXRecordDecl>(
+ PrDecl->getDeclContext()->getParent());
+
+ if (CxxRecord) {
+ return Ctx.getTypeDeclType(dyn_cast<TypeDecl>(CxxRecord));
+ }
+ }
+ }
+
+ return std::nullopt;
+ };
+
// Try resolving the member inside the expression's base type.
Expr *Base = ME->isImplicitAccess() ? nullptr : ME->getBase();
QualType BaseType = ME->getBaseType();
+
+ if (auto Type = ExplicitMemberHeuristic(Base)) {
+ BaseType = *Type;
+ }
+
BaseType = simplifyType(BaseType, Base, ME->isArrow());
return resolveDependentMember(BaseType, ME->getMember(), NoFilter);
}
diff --git a/clang/unittests/Sema/HeuristicResolverTest.cpp b/clang/unittests/Sema/HeuristicResolverTest.cpp
index 7df25e01e66d4..21aca7a3489b8 100644
--- a/clang/unittests/Sema/HeuristicResolverTest.cpp
+++ b/clang/unittests/Sema/HeuristicResolverTest.cpp
@@ -41,7 +41,7 @@ template <typename InputNode, typename ParamT, typename InputMatcher,
typename... OutputMatchers>
void expectResolution(llvm::StringRef Code, ResolveFnT<ParamT> ResolveFn,
const InputMatcher &IM, const OutputMatchers &...OMS) {
- auto TU = tooling::buildASTFromCodeWithArgs(Code, {"-std=c++20"});
+ auto TU = tooling::buildASTFromCodeWithArgs(Code, {"-std=c++23"});
auto &Ctx = TU->getASTContext();
auto InputMatches = match(IM, Ctx);
ASSERT_EQ(1u, InputMatches.size());
@@ -449,6 +449,23 @@ TEST(HeuristicResolver, MemberExpr_DefaultTemplateArgument_Recursive) {
cxxMethodDecl(hasName("foo")).bind("output"));
}
+TEST(HeuristicResolver, MemberExpr_ExplicitObjectParameter) {
+ std::string Code = R"cpp(
+ struct Foo {
+ int m_int;
+
+ int bar(this auto&& self) {
+ return self.m_int;
+ }
+ };
+ )cpp";
+ // Test resolution of "m_int" in "self.m_int()".
+ expectResolution(
+ Code, &HeuristicResolver::resolveMemberExpr,
+ cxxDependentScopeMemberExpr(hasMemberName("m_int")).bind("input"),
+ fieldDecl(hasName("m_int")).bind("output"));
+}
+
TEST(HeuristicResolver, DeclRefExpr_StaticMethod) {
std::string Code = R"cpp(
template <typename T>
>From 0d482894a4e8bb045351e0c1724a6782caee87da Mon Sep 17 00:00:00 2001
From: Mythreya Kuricheti <git at mythreya.dev>
Date: Sun, 24 Aug 2025 00:46:19 -0700
Subject: [PATCH 2/3] code review
---
clang/lib/Sema/HeuristicResolver.cpp | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/clang/lib/Sema/HeuristicResolver.cpp b/clang/lib/Sema/HeuristicResolver.cpp
index 20f7d0fca2066..ed817ac3691ad 100644
--- a/clang/lib/Sema/HeuristicResolver.cpp
+++ b/clang/lib/Sema/HeuristicResolver.cpp
@@ -305,7 +305,7 @@ std::vector<const NamedDecl *> HeuristicResolverImpl::resolveMemberExpr(
// check if member expr is in the context of an explicit object method
// If so, it's safe to assume the templated arg is of type of the record
const auto ExplicitMemberHeuristic =
- [&](const Expr *Base) -> std::optional<QualType> {
+ [&](const Expr *Base) -> QualType {
if (auto *DeclRef = dyn_cast_if_present<DeclRefExpr>(Base)) {
auto *PrDecl = dyn_cast_if_present<ParmVarDecl>(DeclRef->getDecl());
@@ -319,15 +319,15 @@ std::vector<const NamedDecl *> HeuristicResolverImpl::resolveMemberExpr(
}
}
- return std::nullopt;
+ return {};
};
// Try resolving the member inside the expression's base type.
Expr *Base = ME->isImplicitAccess() ? nullptr : ME->getBase();
QualType BaseType = ME->getBaseType();
- if (auto Type = ExplicitMemberHeuristic(Base)) {
- BaseType = *Type;
+ if (auto Type = ExplicitMemberHeuristic(Base); !Type.isNull()) {
+ BaseType = Type;
}
BaseType = simplifyType(BaseType, Base, ME->isArrow());
>From cd3601aaa400e4926058f3e33cf17c787ff47614 Mon Sep 17 00:00:00 2001
From: Mythreya Kuricheti <git at mythreya.dev>
Date: Sun, 24 Aug 2025 00:55:05 -0700
Subject: [PATCH 3/3] remove cxxdecl check
---
clang/lib/Sema/HeuristicResolver.cpp | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/clang/lib/Sema/HeuristicResolver.cpp b/clang/lib/Sema/HeuristicResolver.cpp
index ed817ac3691ad..4130e9b6686ec 100644
--- a/clang/lib/Sema/HeuristicResolver.cpp
+++ b/clang/lib/Sema/HeuristicResolver.cpp
@@ -304,18 +304,14 @@ std::vector<const NamedDecl *> HeuristicResolverImpl::resolveMemberExpr(
// check if member expr is in the context of an explicit object method
// If so, it's safe to assume the templated arg is of type of the record
- const auto ExplicitMemberHeuristic =
- [&](const Expr *Base) -> QualType {
+ const auto ExplicitMemberHeuristic = [&](const Expr *Base) -> QualType {
if (auto *DeclRef = dyn_cast_if_present<DeclRefExpr>(Base)) {
auto *PrDecl = dyn_cast_if_present<ParmVarDecl>(DeclRef->getDecl());
if (PrDecl && PrDecl->isExplicitObjectParameter()) {
- auto CxxRecord = dyn_cast_if_present<CXXRecordDecl>(
- PrDecl->getDeclContext()->getParent());
-
- if (CxxRecord) {
- return Ctx.getTypeDeclType(dyn_cast<TypeDecl>(CxxRecord));
- }
+ // get the parent, a cxxrecord
+ return Ctx.getTypeDeclType(
+ dyn_cast<TypeDecl>(PrDecl->getDeclContext()->getParent()));
}
}
More information about the cfe-commits
mailing list