Skip to content

Commit f68238e

Browse files
SuperSodaSeashafikcor3ntinAaronBallman
authored andcommitted
Backport '[clang] static operators should evaluate object argument (reland)' to release/18.x (llvm#80109)
Cherry picked from commit ee01a2c. Closes llvm#80041, backport llvm#80108. Co-authored-by: Shafik Yaghmour <[email protected]> Co-authored-by: cor3ntin <[email protected]> Co-authored-by: Aaron Ballman <[email protected]>
1 parent d12d0c9 commit f68238e

10 files changed

+160
-47
lines changed

clang-tools-extra/clangd/InlayHints.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -651,16 +651,12 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
651651
// implied object argument ([over.call.func]), the list of provided
652652
// arguments is preceded by the implied object argument for the purposes of
653653
// this correspondence...
654-
//
655-
// However, we don't have the implied object argument
656-
// for static operator() per clang::Sema::BuildCallToObjectOfClassType.
657654
llvm::ArrayRef<const Expr *> Args = {E->getArgs(), E->getNumArgs()};
658655
// We don't have the implied object argument through a function pointer
659656
// either.
660657
if (const CXXMethodDecl *Method =
661658
dyn_cast_or_null<CXXMethodDecl>(Callee.Decl))
662-
if (Method->isInstance() &&
663-
(IsFunctor || Method->hasCXXExplicitFunctionObjectParameter()))
659+
if (IsFunctor || Method->hasCXXExplicitFunctionObjectParameter())
664660
Args = Args.drop_front(1);
665661
processCall(Callee, Args);
666662
return true;

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,6 +1054,9 @@ Bug Fixes to C++ Support
10541054
Fixes (`#78830 <https://github.com/llvm/llvm-project/issues/78830>`_)
10551055
Fixes (`#60085 <https://github.com/llvm/llvm-project/issues/60085>`_)
10561056

1057+
- Fix incorrect code generation caused by the object argument of ``static operator()`` and ``static operator[]`` calls not being evaluated.
1058+
Fixes (`#67976 <https://github.com/llvm/llvm-project/issues/67976>`_)
1059+
10571060
Bug Fixes to AST Handling
10581061
^^^^^^^^^^^^^^^^^^^^^^^^^
10591062
- Fixed an import failure of recursive friend class template.

clang/lib/AST/ExprConstant.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7951,7 +7951,8 @@ class ExprEvaluatorBase
79517951
// Overloaded operator calls to member functions are represented as normal
79527952
// calls with '*this' as the first argument.
79537953
const CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(FD);
7954-
if (MD && MD->isImplicitObjectMemberFunction()) {
7954+
if (MD &&
7955+
(MD->isImplicitObjectMemberFunction() || (OCE && MD->isStatic()))) {
79557956
// FIXME: When selecting an implicit conversion for an overloaded
79567957
// operator delete, we sometimes try to evaluate calls to conversion
79577958
// operators without a 'this' parameter!
@@ -7960,7 +7961,11 @@ class ExprEvaluatorBase
79607961

79617962
if (!EvaluateObjectArgument(Info, Args[0], ThisVal))
79627963
return false;
7963-
This = &ThisVal;
7964+
7965+
// If we are calling a static operator, the 'this' argument needs to be
7966+
// ignored after being evaluated.
7967+
if (MD->isInstance())
7968+
This = &ThisVal;
79647969

79657970
// If this is syntactically a simple assignment using a trivial
79667971
// assignment operator, start the lifetimes of union members as needed,

clang/lib/CodeGen/CGExpr.cpp

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5846,6 +5846,7 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType, const CGCallee &OrigCallee
58465846
// destruction order is not necessarily reverse construction order.
58475847
// FIXME: Revisit this based on C++ committee response to unimplementability.
58485848
EvaluationOrder Order = EvaluationOrder::Default;
5849+
bool StaticOperator = false;
58495850
if (auto *OCE = dyn_cast<CXXOperatorCallExpr>(E)) {
58505851
if (OCE->isAssignmentOp())
58515852
Order = EvaluationOrder::ForceRightToLeft;
@@ -5863,10 +5864,22 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType, const CGCallee &OrigCallee
58635864
break;
58645865
}
58655866
}
5867+
5868+
if (const auto *MD =
5869+
dyn_cast_if_present<CXXMethodDecl>(OCE->getCalleeDecl());
5870+
MD && MD->isStatic())
5871+
StaticOperator = true;
58665872
}
58675873

5868-
EmitCallArgs(Args, dyn_cast<FunctionProtoType>(FnType), E->arguments(),
5869-
E->getDirectCallee(), /*ParamsToSkip*/ 0, Order);
5874+
auto Arguments = E->arguments();
5875+
if (StaticOperator) {
5876+
// If we're calling a static operator, we need to emit the object argument
5877+
// and ignore it.
5878+
EmitIgnoredExpr(E->getArg(0));
5879+
Arguments = drop_begin(Arguments, 1);
5880+
}
5881+
EmitCallArgs(Args, dyn_cast<FunctionProtoType>(FnType), Arguments,
5882+
E->getDirectCallee(), /*ParamsToSkip=*/0, Order);
58705883

58715884
const CGFunctionInfo &FnInfo = CGM.getTypes().arrangeFreeFunctionCall(
58725885
Args, FnType, /*ChainCall=*/Chain);

clang/lib/Sema/SemaChecking.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7643,9 +7643,8 @@ bool Sema::CheckFunctionCall(FunctionDecl *FDecl, CallExpr *TheCall,
76437643
unsigned NumArgs = TheCall->getNumArgs();
76447644

76457645
Expr *ImplicitThis = nullptr;
7646-
if (IsMemberOperatorCall && !FDecl->isStatic() &&
7647-
!FDecl->hasCXXExplicitFunctionObjectParameter()) {
7648-
// If this is a call to a non-static member operator, hide the first
7646+
if (IsMemberOperatorCall && !FDecl->hasCXXExplicitFunctionObjectParameter()) {
7647+
// If this is a call to a member operator, hide the first
76497648
// argument from checkCall.
76507649
// FIXME: Our choice of AST representation here is less than ideal.
76517650
ImplicitThis = Args[0];

clang/lib/Sema/SemaOverload.cpp

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5664,10 +5664,15 @@ static ImplicitConversionSequence TryObjectArgumentInitialization(
56645664
assert(FromType->isRecordType());
56655665

56665666
QualType ClassType = S.Context.getTypeDeclType(ActingContext);
5667-
// [class.dtor]p2: A destructor can be invoked for a const, volatile or
5668-
// const volatile object.
5667+
// C++98 [class.dtor]p2:
5668+
// A destructor can be invoked for a const, volatile or const volatile
5669+
// object.
5670+
// C++98 [over.match.funcs]p4:
5671+
// For static member functions, the implicit object parameter is considered
5672+
// to match any object (since if the function is selected, the object is
5673+
// discarded).
56695674
Qualifiers Quals = Method->getMethodQualifiers();
5670-
if (isa<CXXDestructorDecl>(Method)) {
5675+
if (isa<CXXDestructorDecl>(Method) || Method->isStatic()) {
56715676
Quals.addConst();
56725677
Quals.addVolatile();
56735678
}
@@ -15061,15 +15066,15 @@ ExprResult Sema::CreateOverloadedArraySubscriptExpr(SourceLocation LLoc,
1506115066
CXXMethodDecl *Method = cast<CXXMethodDecl>(FnDecl);
1506215067
SmallVector<Expr *, 2> MethodArgs;
1506315068

15064-
// Handle 'this' parameter if the selected function is not static.
15069+
// Initialize the object parameter.
1506515070
if (Method->isExplicitObjectMemberFunction()) {
1506615071
ExprResult Res =
1506715072
InitializeExplicitObjectArgument(*this, Args[0], Method);
1506815073
if (Res.isInvalid())
1506915074
return ExprError();
1507015075
Args[0] = Res.get();
1507115076
ArgExpr = Args;
15072-
} else if (Method->isInstance()) {
15077+
} else {
1507315078
ExprResult Arg0 = PerformImplicitObjectArgumentInitialization(
1507415079
Args[0], /*Qualifier=*/nullptr, Best->FoundDecl, Method);
1507515080
if (Arg0.isInvalid())
@@ -15097,15 +15102,9 @@ ExprResult Sema::CreateOverloadedArraySubscriptExpr(SourceLocation LLoc,
1509715102
ExprValueKind VK = Expr::getValueKindForType(ResultTy);
1509815103
ResultTy = ResultTy.getNonLValueExprType(Context);
1509915104

15100-
CallExpr *TheCall;
15101-
if (Method->isInstance())
15102-
TheCall = CXXOperatorCallExpr::Create(
15103-
Context, OO_Subscript, FnExpr.get(), MethodArgs, ResultTy, VK,
15104-
RLoc, CurFPFeatureOverrides());
15105-
else
15106-
TheCall =
15107-
CallExpr::Create(Context, FnExpr.get(), MethodArgs, ResultTy, VK,
15108-
RLoc, CurFPFeatureOverrides());
15105+
CallExpr *TheCall = CXXOperatorCallExpr::Create(
15106+
Context, OO_Subscript, FnExpr.get(), MethodArgs, ResultTy, VK, RLoc,
15107+
CurFPFeatureOverrides());
1510915108

1511015109
if (CheckCallReturnType(FnDecl->getReturnType(), LLoc, TheCall, FnDecl))
1511115110
return ExprError();
@@ -15733,15 +15732,13 @@ Sema::BuildCallToObjectOfClassType(Scope *S, Expr *Obj,
1573315732

1573415733
bool IsError = false;
1573515734

15736-
// Initialize the implicit object parameter if needed.
15737-
// Since C++23, this could also be a call to a static call operator
15738-
// which we emit as a regular CallExpr.
15735+
// Initialize the object parameter.
1573915736
llvm::SmallVector<Expr *, 8> NewArgs;
1574015737
if (Method->isExplicitObjectMemberFunction()) {
1574115738
// FIXME: we should do that during the definition of the lambda when we can.
1574215739
DiagnoseInvalidExplicitObjectParameterInLambda(Method);
1574315740
PrepareExplicitObjectArgument(*this, Method, Obj, Args, NewArgs);
15744-
} else if (Method->isInstance()) {
15741+
} else {
1574515742
ExprResult ObjRes = PerformImplicitObjectArgumentInitialization(
1574615743
Object.get(), /*Qualifier=*/nullptr, Best->FoundDecl, Method);
1574715744
if (ObjRes.isInvalid())
@@ -15775,14 +15772,9 @@ Sema::BuildCallToObjectOfClassType(Scope *S, Expr *Obj,
1577515772
ExprValueKind VK = Expr::getValueKindForType(ResultTy);
1577615773
ResultTy = ResultTy.getNonLValueExprType(Context);
1577715774

15778-
CallExpr *TheCall;
15779-
if (Method->isInstance())
15780-
TheCall = CXXOperatorCallExpr::Create(Context, OO_Call, NewFn.get(),
15781-
MethodArgs, ResultTy, VK, RParenLoc,
15782-
CurFPFeatureOverrides());
15783-
else
15784-
TheCall = CallExpr::Create(Context, NewFn.get(), MethodArgs, ResultTy, VK,
15785-
RParenLoc, CurFPFeatureOverrides());
15775+
CallExpr *TheCall = CXXOperatorCallExpr::Create(
15776+
Context, OO_Call, NewFn.get(), MethodArgs, ResultTy, VK, RParenLoc,
15777+
CurFPFeatureOverrides());
1578615778

1578715779
if (CheckCallReturnType(Method->getReturnType(), LParenLoc, TheCall, Method))
1578815780
return true;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// RUN: %clang_cc1 -std=c++23 %s -ast-dump -triple x86_64-unknown-unknown -o - | FileCheck -strict-whitespace %s
2+
3+
struct Functor {
4+
static int operator()(int x, int y) {
5+
return x + y;
6+
}
7+
static int operator[](int x, int y) {
8+
return x + y;
9+
}
10+
};
11+
12+
Functor& get_functor() {
13+
static Functor functor;
14+
return functor;
15+
}
16+
17+
void call_static_operators() {
18+
Functor functor;
19+
20+
int z1 = functor(1, 2);
21+
// CHECK: CXXOperatorCallExpr {{.*}} 'int' '()'
22+
// CHECK-NEXT: |-ImplicitCastExpr {{.*}} <col:19, col:24> 'int (*)(int, int)' <FunctionToPointerDecay>
23+
// CHECK-NEXT: | `-DeclRefExpr {{.*}} <col:19, col:24> 'int (int, int)' lvalue CXXMethod {{.*}} 'operator()' 'int (int, int)'
24+
// CHECK-NEXT: |-DeclRefExpr {{.*}} <col:12> 'Functor' lvalue Var {{.*}} 'functor' 'Functor'
25+
// CHECK-NEXT: |-IntegerLiteral {{.*}} <col:20> 'int' 1
26+
// CHECK-NEXT: `-IntegerLiteral {{.*}} <col:23> 'int' 2
27+
28+
int z2 = functor[1, 2];
29+
// CHECK: CXXOperatorCallExpr {{.*}} 'int' '[]'
30+
// CHECK-NEXT: |-ImplicitCastExpr {{.*}} <col:19, col:24> 'int (*)(int, int)' <FunctionToPointerDecay>
31+
// CHECK-NEXT: | `-DeclRefExpr {{.*}} <col:19, col:24> 'int (int, int)' lvalue CXXMethod {{.*}} 'operator[]' 'int (int, int)'
32+
// CHECK-NEXT: |-DeclRefExpr {{.*}} <col:12> 'Functor' lvalue Var {{.*}} 'functor' 'Functor'
33+
// CHECK-NEXT: |-IntegerLiteral {{.*}} <col:20> 'int' 1
34+
// CHECK-NEXT: `-IntegerLiteral {{.*}} <col:23> 'int' 2
35+
36+
int z3 = get_functor()(1, 2);
37+
// CHECK: CXXOperatorCallExpr {{.*}} 'int' '()'
38+
// CHECK-NEXT: |-ImplicitCastExpr {{.*}} <col:25, col:30> 'int (*)(int, int)' <FunctionToPointerDecay>
39+
// CHECK-NEXT: | `-DeclRefExpr {{.*}} <col:25, col:30> 'int (int, int)' lvalue CXXMethod {{.*}} 'operator()' 'int (int, int)'
40+
// CHECK-NEXT: |-CallExpr {{.*}} <col:12, col:24> 'Functor' lvalue
41+
// CHECK-NEXT: | `-ImplicitCastExpr {{.*}} <col:12> 'Functor &(*)()' <FunctionToPointerDecay>
42+
// CHECK-NEXT: | `-DeclRefExpr {{.*}} <col:12> 'Functor &()' lvalue Function {{.*}} 'get_functor' 'Functor &()'
43+
// CHECK-NEXT: |-IntegerLiteral {{.*}} <col:26> 'int' 1
44+
// CHECK-NEXT: `-IntegerLiteral {{.*}} <col:29> 'int' 2
45+
46+
int z4 = get_functor()[1, 2];
47+
// CHECK: CXXOperatorCallExpr {{.*}} 'int' '[]'
48+
// CHECK-NEXT: |-ImplicitCastExpr {{.*}} <col:25, col:30> 'int (*)(int, int)' <FunctionToPointerDecay>
49+
// CHECK-NEXT: | `-DeclRefExpr {{.*}} <col:25, col:30> 'int (int, int)' lvalue CXXMethod {{.*}} 'operator[]' 'int (int, int)'
50+
// CHECK-NEXT: |-CallExpr {{.*}} <col:12, col:24> 'Functor' lvalue
51+
// CHECK-NEXT: | `-ImplicitCastExpr {{.*}} <col:12> 'Functor &(*)()' <FunctionToPointerDecay>
52+
// CHECK-NEXT: | `-DeclRefExpr {{.*}} <col:12> 'Functor &()' lvalue Function {{.*}} 'get_functor' 'Functor &()'
53+
// CHECK-NEXT: |-IntegerLiteral {{.*}} <col:26> 'int' 1
54+
// CHECK-NEXT: `-IntegerLiteral {{.*}} <col:29> 'int' 2
55+
}

clang/test/CodeGenCXX/cxx2b-static-call-operator.cpp

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,22 @@ void CallsTheLambda() {
1919

2020
// CHECK: define {{.*}}CallsTheLambda{{.*}}
2121
// CHECK-NEXT: entry:
22-
// CHECK-NEXT: %call = call noundef i32 {{.*}}(i32 noundef 1, i32 noundef 2)
22+
// CHECK: {{.*}}call {{.*}}GetALambda{{.*}}()
23+
// CHECK-NEXT: {{.*}} = call noundef i32 {{.*}}(i32 noundef 1, i32 noundef 2)
2324
// CHECK-NEXT: ret void
2425
// CHECK-NEXT: }
2526

27+
Functor GetAFunctor() {
28+
return {};
29+
}
30+
2631
void call_static_call_operator() {
2732
Functor f;
2833
f(101, 102);
2934
f.operator()(201, 202);
3035
Functor{}(301, 302);
3136
Functor::operator()(401, 402);
37+
GetAFunctor()(501, 502);
3238
}
3339

3440
// CHECK: define {{.*}}call_static_call_operator{{.*}}
@@ -37,6 +43,8 @@ void call_static_call_operator() {
3743
// CHECK-NEXT: {{.*}} = call noundef i32 {{.*}}Functor{{.*}}(i32 noundef 201, i32 noundef 202)
3844
// CHECK-NEXT: {{.*}} = call noundef i32 {{.*}}Functor{{.*}}(i32 noundef 301, i32 noundef 302)
3945
// CHECK-NEXT: {{.*}} = call noundef i32 {{.*}}Functor{{.*}}(i32 noundef 401, i32 noundef 402)
46+
// CHECK: {{.*}}call {{.*}}GetAFunctor{{.*}}()
47+
// CHECK-NEXT: {{.*}} = call noundef i32 {{.*}}Functor{{.*}}(i32 noundef 501, i32 noundef 502)
4048
// CHECK-NEXT: ret void
4149
// CHECK-NEXT: }
4250

@@ -106,12 +114,16 @@ void test_dep_functors() {
106114

107115
// CHECK: define {{.*}}test_dep_functors{{.*}}
108116
// CHECK-NEXT: entry:
109-
// CHECK: %call = call noundef i32 {{.*}}DepFunctor{{.*}}(float noundef 1.000000e+00)
110-
// CHECK: %call1 = call noundef i32 {{.*}}DepFunctor{{.*}}(i1 noundef zeroext true)
111-
// CHECK: %call2 = call noundef i32 {{.*}}dep_lambda1{{.*}}(float noundef 1.000000e+00)
112-
// CHECK: %call3 = call noundef i32 {{.*}}dep_lambda1{{.*}}(i1 noundef zeroext true)
113-
// CHECK: %call4 = call noundef i32 {{.*}}dep_lambda2{{.*}}(float noundef 1.000000e+00)
114-
// CHECK: %call5 = call noundef i32 {{.*}}dep_lambda2{{.*}}(i1 noundef zeroext true)
117+
// CHECK: {{.*}} = call noundef i32 {{.*}}DepFunctor{{.*}}(float noundef 1.000000e+00)
118+
// CHECK: {{.*}} = call noundef i32 {{.*}}DepFunctor{{.*}}(i1 noundef zeroext true)
119+
// CHECK: {{.*}}call {{.*}}dep_lambda1{{.*}}()
120+
// CHECK: {{.*}} = call noundef i32 {{.*}}dep_lambda1{{.*}}(float noundef 1.000000e+00)
121+
// CHECK: {{.*}}call {{.*}}dep_lambda1{{.*}}()
122+
// CHECK: {{.*}} = call noundef i32 {{.*}}dep_lambda1{{.*}}(i1 noundef zeroext true)
123+
// CHECK: {{.*}}call {{.*}}dep_lambda2{{.*}}()
124+
// CHECK: {{.*}} = call noundef i32 {{.*}}dep_lambda2{{.*}}(float noundef 1.000000e+00)
125+
// CHECK: {{.*}}call {{.*}}dep_lambda2{{.*}}()
126+
// CHECK: {{.*}} = call noundef i32 {{.*}}dep_lambda2{{.*}}(i1 noundef zeroext true)
115127
// CHECK: ret void
116128
// CHECK-NEXT: }
117129

clang/test/CodeGenCXX/cxx2b-static-subscript-operator.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,17 @@ struct Functor {
77
}
88
};
99

10+
Functor GetAFunctor() {
11+
return {};
12+
}
13+
1014
void call_static_subscript_operator() {
1115
Functor f;
1216
f[101, 102];
1317
f.operator[](201, 202);
1418
Functor{}[301, 302];
1519
Functor::operator[](401, 402);
20+
GetAFunctor()[501, 502];
1621
}
1722

1823
// CHECK: define {{.*}}call_static_subscript_operator{{.*}}
@@ -21,6 +26,8 @@ void call_static_subscript_operator() {
2126
// CHECK-NEXT: {{.*}} = call noundef i32 {{.*}}Functor{{.*}}(i32 noundef 201, i32 noundef 202)
2227
// CHECK-NEXT: {{.*}} = call noundef i32 {{.*}}Functor{{.*}}(i32 noundef 301, i32 noundef 302)
2328
// CHECK-NEXT: {{.*}} = call noundef i32 {{.*}}Functor{{.*}}(i32 noundef 401, i32 noundef 402)
29+
// CHECK: {{.*}}call {{.*}}GetAFunctor{{.*}}()
30+
// CHECK-NEXT: {{.*}} = call noundef i32 {{.*}}Functor{{.*}}(i32 noundef 501, i32 noundef 502)
2431
// CHECK-NEXT: ret void
2532
// CHECK-NEXT: }
2633

@@ -60,7 +67,7 @@ void test_dep_functors() {
6067

6168
// CHECK: define {{.*}}test_dep_functors{{.*}}
6269
// CHECK-NEXT: entry:
63-
// CHECK: %call = call noundef i32 {{.*}}DepFunctor{{.*}}(float noundef 1.000000e+00)
64-
// CHECK: %call1 = call noundef i32 {{.*}}DepFunctor{{.*}}(i1 noundef zeroext true)
70+
// CHECK: {{.*}} = call noundef i32 {{.*}}DepFunctor{{.*}}(float noundef 1.000000e+00)
71+
// CHECK: {{.*}} = call noundef i32 {{.*}}DepFunctor{{.*}}(i1 noundef zeroext true)
6572
// CHECK: ret void
6673
// CHECK-NEXT: }
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++23 %s
2+
3+
// expected-no-diagnostics
4+
5+
namespace A {
6+
7+
struct Foo {
8+
static int operator()(int a, int b) { return a + b; }
9+
static int operator[](int a, int b) { return a + b; }
10+
};
11+
12+
void ok() {
13+
// Should pass regardless of const / volatile
14+
Foo foo;
15+
foo(1, 2);
16+
foo[1, 2];
17+
18+
const Foo fooC;
19+
fooC(1, 2);
20+
fooC[1, 2];
21+
22+
const Foo fooV;
23+
fooV(1, 2);
24+
fooV[1, 2];
25+
26+
const volatile Foo fooCV;
27+
fooCV(1, 2);
28+
fooCV[1, 2];
29+
}
30+
31+
}

0 commit comments

Comments
 (0)