-
Notifications
You must be signed in to change notification settings - Fork 14.9k
[clang] Heuristic resolution for explicit object parameter #155143
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[clang] Heuristic resolution for explicit object parameter #155143
Conversation
Assume `self` parameter is of the parent record type
69ff01b
to
c1cdb39
Compare
@llvm/pr-subscribers-clangd @llvm/pr-subscribers-clang-tools-extra Author: Mythreya Kuricheti (MythreyaK) ChangesFixes clangd/clangd#2323. Assumes struct Foo {
int member {};
auto&& getter1(this auto&& self) { // assume `self` is is `Foo`
return self.member;
}; Full diff: https://github.com/llvm/llvm-project/pull/155143.diff 3 Files Affected:
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>
|
@llvm/pr-subscribers-clang Author: Mythreya Kuricheti (MythreyaK) ChangesFixes clangd/clangd#2323. Assumes struct Foo {
int member {};
auto&& getter1(this auto&& self) { // assume `self` is is `Foo`
return self.member;
}; Full diff: https://github.com/llvm/llvm-project/pull/155143.diff 3 Files Affected:
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>
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
clang/lib/Sema/HeuristicResolver.cpp
Outdated
auto *PrDecl = dyn_cast_if_present<ParmVarDecl>(DeclRef->getDecl()); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When is that null?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PrDecl
is null here, for example.
DeclRefExpr 0x2e63d268 'auto' lvalue Var 0x2e63d150 'b' 'auto'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the question is rather, when is DeclRef->getDecl()
null, such that you need to use dyn_cast_if_present
rather than plain dyn_cast
.
Looking at other usages of DeclRefExpr::getDecl()
, it seems callers assume it's never null.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to use dyn_cast
instead 👍
clang/lib/Sema/HeuristicResolver.cpp
Outdated
// 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider things like
struct S {
template <typename T>
void f(this Foo<T>);
};
It might be prudent to
1/ call simplifyType
first
2/ only apply the heuristic to undeduced auto and TemplateTypeParmType
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to address this, but not sure if this is what you meant (I'm new to clang and still finding my way 😄). Please let me know what you think!
template <typename T>
void f(this Foo<T>);
Can a this
parameter be something other than a record not related to S
(Foo<T>
, in this example)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! This looks generally good to me.
@@ -2468,6 +2469,46 @@ TEST(CrossFileRenameTests, adjustmentCost) { | |||
} | |||
} | |||
|
|||
TEST(RenameTest, RenameWithExplicitObjectPararameter) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of adding a new top-level test case (which involves repeating some boilerplate), could you add an entry to the array in RenameTest.WithinFileRename
instead (and adjust the flags of that test to use -std=c++23
as necessary)?
clang/lib/Sema/HeuristicResolver.cpp
Outdated
// Try resolving the member inside the expression's base type. | ||
Expr *Base = ME->isImplicitAccess() ? nullptr : ME->getBase(); | ||
QualType BaseType = ME->getBaseType(); | ||
BaseType = simplifyType(BaseType, Base, ME->isArrow()); | ||
|
||
if (!BaseType.isNull() && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we move this into simplifyType
itself? Specifically, inside SimplifyOneStep
, what are BaseType
and Base
here are available as T.Type
and T.Expr
respectively.
That would make this heuristic a bit more general. (For example, in combination with this patch which adds a simplifyType
call site in SemaCodeComplete
, it gets code completion to work after self.^
in such a function.)
clang/lib/Sema/HeuristicResolver.cpp
Outdated
@@ -256,6 +256,21 @@ QualType HeuristicResolverImpl::simplifyType(QualType Type, const Expr *E, | |||
} | |||
} | |||
} | |||
// check if member expr is in the context of an explicit object method |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would reword / expand on this as:
// Check if the expression refers to an explicit object parameter of templated type.
// If so, heuristically treat it as having the type of the enclosing class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
clang/lib/Sema/HeuristicResolver.cpp
Outdated
(T.Type->isUndeducedAutoType() || T.Type->isTemplateTypeParmType())) { | ||
if (auto *DRE = dyn_cast_if_present<DeclRefExpr>(T.E)) { | ||
auto *PrDecl = dyn_cast_if_present<ParmVarDecl>(DRE->getDecl()); | ||
// Then the type of 'this' should be type of the record the method is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(with the above change, this comment can be dropped)
clang/lib/Sema/HeuristicResolver.cpp
Outdated
BaseType = Type; | ||
} | ||
} | ||
// fflush(stdout); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some debugging code snuck in here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh whoops, my bad 😅, removed, thanks!
c76a451
to
b48fa0e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updates look good to me, just one outstanding issue.
@@ -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++23"}); | |||
auto TU = tooling::buildASTFromCodeWithArgs(Code, {"-std=c++20"}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change (from c++20 to c++23) should be kept.
(It looks like the this auto
test case manages to pass even without it, but that looks like a bug in the test suite; code with error diagnostics should cause the test to fail.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh whoops, done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(It looks like the
this auto
test case manages to pass even without it, but that looks like a bug in the test suite; code with error diagnostics should cause the test to fail.)
(Filed #155545 for this.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks!
Fixes clangd/clangd#2323.
Assumes
self
is of the record type in the declaration.