-
Notifications
You must be signed in to change notification settings - Fork 13.5k
[Clang][Sema] Use the correct lookup context when building overloaded 'operator->' in the current instantiation #104458
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
Conversation
@llvm/pr-subscribers-clang Author: Krystian Stasiowski (sdkrystian) ChangesFixes #104268. Still need tests. Full diff: https://github.com/llvm/llvm-project/pull/104458.diff 5 Files Affected:
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 25cb6c8fbf6104..b9c5c5e4b92ed9 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -10616,8 +10616,7 @@ class Sema final : public SemaBase {
/// BuildOverloadedArrowExpr - Build a call to an overloaded @c operator->
/// (if one exists), where @c Base is an expression of class type and
/// @c Member is the name of the member we're trying to find.
- ExprResult BuildOverloadedArrowExpr(Scope *S, Expr *Base,
- SourceLocation OpLoc,
+ ExprResult BuildOverloadedArrowExpr(Expr *Base, SourceLocation OpLoc,
bool *NoArrowOperatorFound = nullptr);
ExprResult BuildCXXMemberCallExpr(Expr *Exp, NamedDecl *FoundDecl,
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 124435330ca104..d33d635ff76c5b 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -7876,18 +7876,6 @@ ExprResult Sema::ActOnStartCXXMemberReference(Scope *S, Expr *Base,
QualType BaseType = Base->getType();
MayBePseudoDestructor = false;
- if (BaseType->isDependentType()) {
- // If we have a pointer to a dependent type and are using the -> operator,
- // the object type is the type that the pointer points to. We might still
- // have enough information about that type to do something useful.
- if (OpKind == tok::arrow)
- if (const PointerType *Ptr = BaseType->getAs<PointerType>())
- BaseType = Ptr->getPointeeType();
-
- ObjectType = ParsedType::make(BaseType);
- MayBePseudoDestructor = true;
- return Base;
- }
// C++ [over.match.oper]p8:
// [...] When operator->returns, the operator-> is applied to the value
@@ -7902,7 +7890,7 @@ ExprResult Sema::ActOnStartCXXMemberReference(Scope *S, Expr *Base,
SmallVector<FunctionDecl*, 8> OperatorArrows;
CTypes.insert(Context.getCanonicalType(BaseType));
- while (BaseType->isRecordType()) {
+ while (BaseType->getAsRecordDecl()) {
if (OperatorArrows.size() >= getLangOpts().ArrowDepth) {
Diag(OpLoc, diag::err_operator_arrow_depth_exceeded)
<< StartingType << getLangOpts().ArrowDepth << Base->getSourceRange();
@@ -7913,7 +7901,7 @@ ExprResult Sema::ActOnStartCXXMemberReference(Scope *S, Expr *Base,
}
Result = BuildOverloadedArrowExpr(
- S, Base, OpLoc,
+ Base, OpLoc,
// When in a template specialization and on the first loop iteration,
// potentially give the default diagnostic (with the fixit in a
// separate note) instead of having the error reported back to here
@@ -7939,7 +7927,14 @@ ExprResult Sema::ActOnStartCXXMemberReference(Scope *S, Expr *Base,
}
}
return ExprError();
+ } else if (Result.isUnset()) {
+ // BuildOverloadedArrowExpr returns an empty expression to indicate
+ // that we need to build a dependent overloaded arrow expression.
+ assert(BaseType->isDependentType());
+ BaseType = Context.DependentTy;
+ break;
}
+
Base = Result.get();
if (CXXOperatorCallExpr *OpCall = dyn_cast<CXXOperatorCallExpr>(Base))
OperatorArrows.push_back(OpCall->getDirectCallee());
@@ -7977,7 +7972,7 @@ ExprResult Sema::ActOnStartCXXMemberReference(Scope *S, Expr *Base,
// it's legal for the type to be incomplete if this is a pseudo-destructor
// call. We'll do more incomplete-type checks later in the lookup process,
// so just skip this check for ObjC types.
- if (!BaseType->isRecordType()) {
+ if (!BaseType->getAsRecordDecl()) {
ObjectType = ParsedType::make(BaseType);
MayBePseudoDestructor = true;
return Base;
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 52f640eb96b73b..4340181f316560 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -15864,10 +15864,9 @@ Sema::BuildCallToObjectOfClassType(Scope *S, Expr *Obj,
return CheckForImmediateInvocation(MaybeBindToTemporary(TheCall), Method);
}
-ExprResult
-Sema::BuildOverloadedArrowExpr(Scope *S, Expr *Base, SourceLocation OpLoc,
- bool *NoArrowOperatorFound) {
- assert(Base->getType()->isRecordType() &&
+ExprResult Sema::BuildOverloadedArrowExpr(Expr *Base, SourceLocation OpLoc,
+ bool *NoArrowOperatorFound) {
+ assert(Base->getType()->getAsRecordDecl() &&
"left-hand side must have class type");
if (checkPlaceholderForOverload(*this, Base))
@@ -15890,7 +15889,12 @@ Sema::BuildOverloadedArrowExpr(Scope *S, Expr *Base, SourceLocation OpLoc,
return ExprError();
LookupResult R(*this, OpName, OpLoc, LookupOrdinaryName);
- LookupQualifiedName(R, Base->getType()->castAs<RecordType>()->getDecl());
+ LookupParsedName(R, /*S=*/nullptr, /*SS=*/nullptr, Base->getType());
+
+ if (R.wasNotFoundInCurrentInstantiation() ||
+ (Base->isTypeDependent() && !R.empty()))
+ return ExprEmpty();
+
R.suppressAccessDiagnostics();
for (LookupResult::iterator Oper = R.begin(), OperEnd = R.end();
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 78ec964037dfe9..6a849662015531 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -16542,10 +16542,10 @@ ExprResult TreeTransform<Derived>::RebuildCXXOperatorCallExpr(
} else if (Op == OO_Arrow) {
// It is possible that the type refers to a RecoveryExpr created earlier
// in the tree transformation.
- if (First->getType()->isDependentType())
+ if (First->containsErrors())
return ExprError();
// -> is never a builtin operation.
- return SemaRef.BuildOverloadedArrowExpr(nullptr, First, OpLoc);
+ return SemaRef.BuildOverloadedArrowExpr(First, OpLoc);
} else if (Second == nullptr || isPostIncDec) {
if (!First->getType()->isOverloadableType() ||
(Op == OO_Amp && getSema().isQualifiedMemberAccess(First))) {
diff --git a/clang/test/CXX/temp/temp.res/temp.dep/temp.dep.type/p4.cpp b/clang/test/CXX/temp/temp.res/temp.dep/temp.dep.type/p4.cpp
index f32f49ef4539a5..89c22a0bc137d9 100644
--- a/clang/test/CXX/temp/temp.res/temp.dep/temp.dep.type/p4.cpp
+++ b/clang/test/CXX/temp/temp.res/temp.dep/temp.dep.type/p4.cpp
@@ -484,16 +484,19 @@ namespace N4 {
template<typename T>
struct A {
void not_instantiated(A a, A<T> b, T c) {
- a->x;
- b->x;
+ a->x; // expected-error {{member reference type 'A<T>' is not a pointer; did you mean to use '.'?}}
+ b->x; // expected-error {{member reference type 'A<T>' is not a pointer; did you mean to use '.'?}}
c->x;
}
void instantiated(A a, A<T> b, T c) {
- a->x; // expected-error {{member reference type 'A<int>' is not a pointer; did you mean to use '.'?}}
- // expected-error@-1 {{no member named 'x' in 'N4::A<int>'}}
- b->x; // expected-error {{member reference type 'A<int>' is not a pointer; did you mean to use '.'?}}
- // expected-error@-1 {{no member named 'x' in 'N4::A<int>'}}
+ // FIXME: We should only emit a single diagnostic suggesting to use '.'!
+ a->x; // expected-error {{member reference type 'A<T>' is not a pointer; did you mean to use '.'?}}
+ // expected-error@-1 {{member reference type 'A<int>' is not a pointer; did you mean to use '.'?}}
+ // expected-error@-2 {{no member named 'x' in 'N4::A<int>'}}
+ b->x; // expected-error {{member reference type 'A<T>' is not a pointer; did you mean to use '.'?}}
+ // expected-error@-1 {{member reference type 'A<int>' is not a pointer; did you mean to use '.'?}}
+ // expected-error@-2 {{no member named 'x' in 'N4::A<int>'}}
c->x; // expected-error {{member reference type 'int' is not a pointer}}
}
};
@@ -540,11 +543,10 @@ namespace N4 {
a->T::f();
a->T::g();
- // FIXME: 'U' should be a dependent name, and its lookup context should be 'a.operator->()'!
- a->U::x; // expected-error {{use of undeclared identifier 'U'}}
- a->U::y; // expected-error {{use of undeclared identifier 'U'}}
- a->U::f(); // expected-error {{use of undeclared identifier 'U'}}
- a->U::g(); // expected-error {{use of undeclared identifier 'U'}}
+ a->U::x;
+ a->U::y;
+ a->U::f();
+ a->U::g();
}
void instantiated(D a) {
|
Can you please add more details in your summary about the problem and how the PR will fix the problem. Having detailed summaries for got log is important. Also for the code reviewer as well, I should get a good snapshot of the problem and fix from the summary. |
@sdkrystian do you plan to add more tests? |
@shafik I will once I get back to this PR.
@cor3ntin I do, I just haven't gotten around to it yet :) |
thanks! |
@cor3ntin What's the deadline? |
I think there won't be a rc4 so it's actually probably too late (@tobias) - but presumably there will be a |
You have the wrong handle, on github it's @tru. 😉 Also, RC4 is being considered. |
Thanks! However we can't consider a backport until we have something to backport! |
@cor3ntin After giving it another look, I think the existing tests are sufficient (the tests updated by this PR were written by me to test this exact scenario). WDYT? |
clang/lib/Sema/SemaExprCXX.cpp
Outdated
// BuildOverloadedArrowExpr returns an empty expression to indicate | ||
// that we need to build a dependent overloaded arrow expression. |
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 have 2 concerns with that
1/ It's a bit too clever of an interface
2/ We should make sure this can only happens in Sema, right? I wonder if we need an ActOnOverloadedArrowExpr to wrap that logic such that (it would duplicate the lookup code but i think that's a better outcome)
WDYT?
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 have 2 concerns with that
1/ It's a bit too clever of an interface
Although I agree it's an imperfect interface, this is more of a "transitional" fix. Ideally we will build the full AST for calls to members of the current instantiation in the future. For now, the usage of ExprEmpty
in this patch isn't unfounded so I think it's "good enough". Alternatively I can add a bool& IsDependent
out parameter to implement this functionality.
2/ We should make sure this can only happens in Sema, right? I wonder if we need an ActOnOverloadedArrowExpr to wrap that logic such that (it would duplicate the lookup code but i think that's a better outcome)
I'm not entirely sure what this means. Are you saying that we should expect the result to not be ExprEmpty
during instantiation?
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'm not sure RebuildCXXOperatorCallExpr and its caller would deal with an ExprEmpty when calling
ActOnOverloadedArrowExpr
So yeah, maybe output parameter + assert in treetransform would be better?
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.
@cor3ntin Pushed changes that do what you described
I think the tests you have are fine |
@cor3ntin Excellent, then I think this is good to go |
Can you update the description too? |
e5f3b06
to
4cdb2b4
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.
LGTM, thanks (Please update the description before merging)
If we can backport it, it seems like a reasonable one to backport; the biggest question I have is whether we have any concerns about the changes being risky or not. |
@sdkrystian can you merge soon so that we can cherry pick in 19? thanks! |
a6fedba
to
0b4a403
Compare
/cherry-pick 3cdb30e |
… 'operator->' in the current instantiation (llvm#104458) Currently, clang erroneously rejects the following: ``` struct A { template<typename T> void f(); }; template<typename T> struct B { void g() { (*this)->template f<int>(); // error: no member named 'f' in 'B<T>' } A* operator->(); }; ``` This happens because `Sema::ActOnStartCXXMemberReference` does not adjust the `ObjectType` parameter when `ObjectType` is a dependent type (except when the type is a `PointerType` and the class member access is the `->` form). Since the (possibly adjusted) `ObjectType` parameter (`B<T>` in the above example) is passed to `Parser::ParseOptionalCXXScopeSpecifier`, we end up looking up `f` in `B` rather than `A`. This patch fixes the issue by identifying cases where the type of the object expression `T` is a dependent, non-pointer type and: - `T` is the current instantiation and lookup for `operator->` finds a member of the current instantiation, or - `T` has at least one dependent base case, and `operator->` is not found in the current instantiation and using `ASTContext::DependentTy` as the type of the object expression when the optional _nested-name-specifier_ is parsed. Fixes llvm#104268. (cherry picked from commit 3cdb30e)
/pull-request #107886 |
It looks like this breaks the clang stage 2 build: https://llvm-compile-time-tracker.com/show_error.php?commit=3cdb30ebbc18fa894d3bd67aebcff76ce7c741ac |
I am also seeing assertions triggered when building LLVM |
I have a fix, so I'll reapply this sometime today. |
@sdkrystian sorry to bother, do you have that change to re-land it? |
@porglezomp Sorry, I'm currently at cppcon but I'll try reland it when I have a moment. |
…verloaded 'operator->' in the current instantiation (llvm#104458)"
Currently, clang erroneously rejects the following:
This happens because
Sema::ActOnStartCXXMemberReference
does not adjust theObjectType
parameter whenObjectType
is a dependent type (except when the type is aPointerType
and the class member access is the->
form). Since the (possibly adjusted)ObjectType
parameter (B<T>
in the above example) is passed toParser::ParseOptionalCXXScopeSpecifier
, we end up looking upf
inB
rather thanA
.This patch fixes the issue by identifying cases where the type of the object expression
T
is a dependent, non-pointer type and:T
is the current instantiation and lookup foroperator->
finds a member of the current instantiation, orT
has at least one dependent base case, andoperator->
is not found in the current instantiationand using
ASTContext::DependentTy
as the type of the object expression when the optional nested-name-specifier is parsed.Fixes #104268.