Skip to content
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] static operators should evaluate object argument #68485

Merged
merged 33 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
0327626
[clang] static operators should evaluate object argument
SuperSodaSea Oct 7, 2023
63a3627
Deal with static operator in EmitCall
SuperSodaSea Oct 8, 2023
1269bc3
Apply suggestions from cor3ntin
SuperSodaSea Oct 10, 2023
755bcaa
Minor changes
SuperSodaSea Oct 10, 2023
12d3ea2
Apply suggestions from shafik
SuperSodaSea Oct 11, 2023
e725a8f
Ignore `this` in constexpr evaluation
SuperSodaSea Oct 11, 2023
441ca98
Merge remote-tracking branch 'upstream/main' into fix/static-operator
SuperSodaSea Jan 4, 2024
5accc5d
Update clang/docs/ReleaseNotes.rst
SuperSodaSea Jan 4, 2024
389d6d4
Merge remote-tracking branch 'upstream/main' into fix/static-operator
SuperSodaSea Jan 4, 2024
6bcc590
Update ast-dump-static-operators.cpp
SuperSodaSea Jan 4, 2024
ab1dc2c
Merge branch 'main' into fix/static-operator
SuperSodaSea Jan 5, 2024
eb42407
Should work with const / volatile
SuperSodaSea Jan 5, 2024
fa85d87
Format code
SuperSodaSea Jan 5, 2024
3e1e4cd
Merge remote-tracking branch 'upstream/main' into fix/static-operator
SuperSodaSea Jan 5, 2024
9a724c9
Merge branch 'main' into fix/static-operator
SuperSodaSea Jan 6, 2024
c679700
Update clang/docs/ReleaseNotes.rst
SuperSodaSea Jan 6, 2024
d3edbd1
Update clang/docs/ReleaseNotes.rst
SuperSodaSea Jan 11, 2024
490fd0f
Apply suggestions from @cor3ntin
SuperSodaSea Jan 11, 2024
93e647c
Merge remote-tracking branch 'upstream/main' into fix/static-operator
SuperSodaSea Jan 11, 2024
94195a2
Update wording
SuperSodaSea Jan 11, 2024
a141494
Make CI happy
SuperSodaSea Jan 11, 2024
fdfa350
Merge remote-tracking branch 'upstream/main' into fix/static-operator
SuperSodaSea Jan 11, 2024
08208f3
Merge branch 'main' into fix/static-operator
SuperSodaSea Jan 17, 2024
62b5040
Merge branch 'main' into fix/static-operator
SuperSodaSea Jan 18, 2024
497738f
Merge branch 'main' into fix/static-operator
SuperSodaSea Jan 22, 2024
b389560
Merge branch 'main' into fix/static-operator
SuperSodaSea Jan 26, 2024
6aafb82
Merge branch 'main' into fix/static-operator
SuperSodaSea Jan 26, 2024
ca4c70c
Merge branch 'main' into fix/static-operator
SuperSodaSea Jan 26, 2024
08b43d2
Merge branch 'main' into fix/static-operator
SuperSodaSea Jan 30, 2024
22be6a2
Merge branch 'main' into fix/static-operator
SuperSodaSea Jan 30, 2024
5c3dfb3
Apply suggestion from @AaronBallman
SuperSodaSea Jan 30, 2024
c560b38
Apply suggestion from @AaronBallman
SuperSodaSea Jan 30, 2024
1ceaae4
Use upper case
SuperSodaSea Jan 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ Bug Fixes to C++ Support
- Fix for crash when using a erroneous type in a return statement.
Fixes (`#63244 <https://github.com/llvm/llvm-project/issues/63244>`_)
and (`#79745 <https://github.com/llvm/llvm-project/issues/79745>`_)
- Fix incorrect code generation caused by the object argument of ``static operator()`` and ``static operator[]`` calls not being evaluated.
Fixes (`#67976 <https://github.com/llvm/llvm-project/issues/67976>`_)

Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
9 changes: 7 additions & 2 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7951,7 +7951,8 @@ class ExprEvaluatorBase
// Overloaded operator calls to member functions are represented as normal
// calls with '*this' as the first argument.
const CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(FD);
if (MD && MD->isImplicitObjectMemberFunction()) {
if (MD &&
(MD->isImplicitObjectMemberFunction() || (OCE && MD->isStatic()))) {
SuperSodaSea marked this conversation as resolved.
Show resolved Hide resolved
// FIXME: When selecting an implicit conversion for an overloaded
// operator delete, we sometimes try to evaluate calls to conversion
// operators without a 'this' parameter!
Expand All @@ -7960,7 +7961,11 @@ class ExprEvaluatorBase

if (!EvaluateObjectArgument(Info, Args[0], ThisVal))
return false;
This = &ThisVal;

// If we are calling a static operator, the 'this' argument needs to be
// ignored after being evaluated.
if (MD->isInstance())
This = &ThisVal;

// If this is syntactically a simple assignment using a trivial
// assignment operator, start the lifetimes of union members as needed,
Expand Down
17 changes: 15 additions & 2 deletions clang/lib/CodeGen/CGExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5848,6 +5848,7 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType, const CGCallee &OrigCallee
// destruction order is not necessarily reverse construction order.
// FIXME: Revisit this based on C++ committee response to unimplementability.
EvaluationOrder Order = EvaluationOrder::Default;
bool StaticOperator = false;
if (auto *OCE = dyn_cast<CXXOperatorCallExpr>(E)) {
if (OCE->isAssignmentOp())
Order = EvaluationOrder::ForceRightToLeft;
Expand All @@ -5865,10 +5866,22 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType, const CGCallee &OrigCallee
break;
}
}

if (const auto *MD =
dyn_cast_if_present<CXXMethodDecl>(OCE->getCalleeDecl());
MD && MD->isStatic())
StaticOperator = true;
}

EmitCallArgs(Args, dyn_cast<FunctionProtoType>(FnType), E->arguments(),
E->getDirectCallee(), /*ParamsToSkip*/ 0, Order);
auto Arguments = E->arguments();
if (StaticOperator) {
// If we're calling a static operator, we need to emit the object argument
// and ignore it.
EmitIgnoredExpr(E->getArg(0));
Arguments = drop_begin(Arguments, 1);
}
EmitCallArgs(Args, dyn_cast<FunctionProtoType>(FnType), Arguments,
E->getDirectCallee(), /*ParamsToSkip=*/0, Order);

const CGFunctionInfo &FnInfo = CGM.getTypes().arrangeFreeFunctionCall(
Args, FnType, /*ChainCall=*/Chain);
Expand Down
5 changes: 2 additions & 3 deletions clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7611,9 +7611,8 @@ bool Sema::CheckFunctionCall(FunctionDecl *FDecl, CallExpr *TheCall,
unsigned NumArgs = TheCall->getNumArgs();

Expr *ImplicitThis = nullptr;
if (IsMemberOperatorCall && !FDecl->isStatic() &&
!FDecl->hasCXXExplicitFunctionObjectParameter()) {
// If this is a call to a non-static member operator, hide the first
if (IsMemberOperatorCall && !FDecl->hasCXXExplicitFunctionObjectParameter()) {
// If this is a call to a member operator, hide the first
// argument from checkCall.
// FIXME: Our choice of AST representation here is less than ideal.
ImplicitThis = Args[0];
Expand Down
44 changes: 18 additions & 26 deletions clang/lib/Sema/SemaOverload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5664,10 +5664,15 @@ static ImplicitConversionSequence TryObjectArgumentInitialization(
assert(FromType->isRecordType());

QualType ClassType = S.Context.getTypeDeclType(ActingContext);
// [class.dtor]p2: A destructor can be invoked for a const, volatile or
// const volatile object.
// C++98 [class.dtor]p2:
// A destructor can be invoked for a const, volatile or const volatile
// object.
// C++98 [over.match.funcs]p4:
// For static member functions, the implicit object parameter is considered
// to match any object (since if the function is selected, the object is
// discarded).
Qualifiers Quals = Method->getMethodQualifiers();
if (isa<CXXDestructorDecl>(Method)) {
if (isa<CXXDestructorDecl>(Method) || Method->isStatic()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this change have a functional change wrt this change or is this just a correctness fix?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following code won't compile without this change:

struct Foo {
    static int operator()(int a, int b) { return a + b; }
};

void f() {
    const Foo foo;
    foo(1, 2); // 'this' argument to member function 'operator()' has type 'const Foo', but function is not marked const
}

This issue was founded in libc++ test suite, which calls static operator() on a const object. Test for this situation is added in clang/test/SemaCXX/cxx2b-static-operator.cpp.

Quals.addConst();
Quals.addVolatile();
}
Expand Down Expand Up @@ -15061,15 +15066,15 @@ ExprResult Sema::CreateOverloadedArraySubscriptExpr(SourceLocation LLoc,
CXXMethodDecl *Method = cast<CXXMethodDecl>(FnDecl);
SmallVector<Expr *, 2> MethodArgs;

// Handle 'this' parameter if the selected function is not static.
// Initialize the object parameter.
if (Method->isExplicitObjectMemberFunction()) {
ExprResult Res =
InitializeExplicitObjectArgument(*this, Args[0], Method);
if (Res.isInvalid())
return ExprError();
Args[0] = Res.get();
ArgExpr = Args;
} else if (Method->isInstance()) {
} else {
ExprResult Arg0 = PerformImplicitObjectArgumentInitialization(
Args[0], /*Qualifier=*/nullptr, Best->FoundDecl, Method);
if (Arg0.isInvalid())
Expand Down Expand Up @@ -15097,15 +15102,9 @@ ExprResult Sema::CreateOverloadedArraySubscriptExpr(SourceLocation LLoc,
ExprValueKind VK = Expr::getValueKindForType(ResultTy);
ResultTy = ResultTy.getNonLValueExprType(Context);

CallExpr *TheCall;
if (Method->isInstance())
TheCall = CXXOperatorCallExpr::Create(
Context, OO_Subscript, FnExpr.get(), MethodArgs, ResultTy, VK,
RLoc, CurFPFeatureOverrides());
else
TheCall =
CallExpr::Create(Context, FnExpr.get(), MethodArgs, ResultTy, VK,
RLoc, CurFPFeatureOverrides());
CallExpr *TheCall = CXXOperatorCallExpr::Create(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I haven’t had time to go through this PR, these lines appear to be the reason for the test failure. We had been relying on these different CallExprs to tell whether we should drop the explicit object parameter at the clangd site (IsFunctor). And this seemingly breaks that expectation as well as changes the AST?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, with this change the CXXOperatorCall will always have an operator argument, even if the operator is static, this is intended in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In short, for the following code

// struct Foo { static int operator()(int a, int b); };
// Foo foo;
foo(1, 2);

, the AST is changed from

CallExpr
|- operator()
|- a
\- b

to

CXXOperatorCallExpr
|- operator()
|- foo
|- a
\- b

And other situations are not affected.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation! I think this is reasonable and makes it much more intuitive for static operator call expressions. So, if I understand the change correctly, I think we can simplify the condition testing at the clangd side: we can avoid the check isInstance on expressions. That means, can we just reduce the condition to IsFunctor && hasCXXExplicitFunctionObjectParameter?

I’m on my phone now, so I couldn't validate my thoughts. Could you please help me for it? I’m willing to help you reland the patch if that works!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, hasCXXExplicitFunctionObjectParameter() should imply isInstance(), so isInstance() is no longer needed. Passed check-clangd on my own build.

      if (const CXXMethodDecl *Method =
              dyn_cast_or_null<CXXMethodDecl>(Callee.Decl))
-       if (Method->isInstance() &&
-           (IsFunctor || Method->hasCXXExplicitFunctionObjectParameter()))
+       if (IsFunctor || Method->hasCXXExplicitFunctionObjectParameter())
          Args = Args.drop_front(1);

Diff: 1ceaae4...910ae40

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Given that the commit has been reverted, can you please submit a new relanding PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure!

Context, OO_Subscript, FnExpr.get(), MethodArgs, ResultTy, VK, RLoc,
CurFPFeatureOverrides());

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

bool IsError = false;

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

CallExpr *TheCall;
if (Method->isInstance())
TheCall = CXXOperatorCallExpr::Create(Context, OO_Call, NewFn.get(),
MethodArgs, ResultTy, VK, RParenLoc,
CurFPFeatureOverrides());
else
TheCall = CallExpr::Create(Context, NewFn.get(), MethodArgs, ResultTy, VK,
RParenLoc, CurFPFeatureOverrides());
CallExpr *TheCall = CXXOperatorCallExpr::Create(
Context, OO_Call, NewFn.get(), MethodArgs, ResultTy, VK, RParenLoc,
CurFPFeatureOverrides());

if (CheckCallReturnType(Method->getReturnType(), LParenLoc, TheCall, Method))
return true;
Expand Down
55 changes: 55 additions & 0 deletions clang/test/AST/ast-dump-static-operators.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// RUN: %clang_cc1 -std=c++23 %s -ast-dump -triple x86_64-unknown-unknown -o - | FileCheck -strict-whitespace %s

struct Functor {
static int operator()(int x, int y) {
return x + y;
}
static int operator[](int x, int y) {
return x + y;
}
};

Functor& get_functor() {
static Functor functor;
return functor;
}

void call_static_operators() {
Functor functor;

int z1 = functor(1, 2);
// CHECK: CXXOperatorCallExpr {{.*}} 'int' '()'
// CHECK-NEXT: |-ImplicitCastExpr {{.*}} <col:19, col:24> 'int (*)(int, int)' <FunctionToPointerDecay>
// CHECK-NEXT: | `-DeclRefExpr {{.*}} <col:19, col:24> 'int (int, int)' lvalue CXXMethod {{.*}} 'operator()' 'int (int, int)'
// CHECK-NEXT: |-DeclRefExpr {{.*}} <col:12> 'Functor' lvalue Var {{.*}} 'functor' 'Functor'
// CHECK-NEXT: |-IntegerLiteral {{.*}} <col:20> 'int' 1
// CHECK-NEXT: `-IntegerLiteral {{.*}} <col:23> 'int' 2

int z2 = functor[1, 2];
// CHECK: CXXOperatorCallExpr {{.*}} 'int' '[]'
// CHECK-NEXT: |-ImplicitCastExpr {{.*}} <col:19, col:24> 'int (*)(int, int)' <FunctionToPointerDecay>
// CHECK-NEXT: | `-DeclRefExpr {{.*}} <col:19, col:24> 'int (int, int)' lvalue CXXMethod {{.*}} 'operator[]' 'int (int, int)'
// CHECK-NEXT: |-DeclRefExpr {{.*}} <col:12> 'Functor' lvalue Var {{.*}} 'functor' 'Functor'
// CHECK-NEXT: |-IntegerLiteral {{.*}} <col:20> 'int' 1
// CHECK-NEXT: `-IntegerLiteral {{.*}} <col:23> 'int' 2

int z3 = get_functor()(1, 2);
// CHECK: CXXOperatorCallExpr {{.*}} 'int' '()'
// CHECK-NEXT: |-ImplicitCastExpr {{.*}} <col:25, col:30> 'int (*)(int, int)' <FunctionToPointerDecay>
// CHECK-NEXT: | `-DeclRefExpr {{.*}} <col:25, col:30> 'int (int, int)' lvalue CXXMethod {{.*}} 'operator()' 'int (int, int)'
// CHECK-NEXT: |-CallExpr {{.*}} <col:12, col:24> 'Functor' lvalue
// CHECK-NEXT: | `-ImplicitCastExpr {{.*}} <col:12> 'Functor &(*)()' <FunctionToPointerDecay>
// CHECK-NEXT: | `-DeclRefExpr {{.*}} <col:12> 'Functor &()' lvalue Function {{.*}} 'get_functor' 'Functor &()'
// CHECK-NEXT: |-IntegerLiteral {{.*}} <col:26> 'int' 1
// CHECK-NEXT: `-IntegerLiteral {{.*}} <col:29> 'int' 2

int z4 = get_functor()[1, 2];
// CHECK: CXXOperatorCallExpr {{.*}} 'int' '[]'
// CHECK-NEXT: |-ImplicitCastExpr {{.*}} <col:25, col:30> 'int (*)(int, int)' <FunctionToPointerDecay>
// CHECK-NEXT: | `-DeclRefExpr {{.*}} <col:25, col:30> 'int (int, int)' lvalue CXXMethod {{.*}} 'operator[]' 'int (int, int)'
// CHECK-NEXT: |-CallExpr {{.*}} <col:12, col:24> 'Functor' lvalue
// CHECK-NEXT: | `-ImplicitCastExpr {{.*}} <col:12> 'Functor &(*)()' <FunctionToPointerDecay>
// CHECK-NEXT: | `-DeclRefExpr {{.*}} <col:12> 'Functor &()' lvalue Function {{.*}} 'get_functor' 'Functor &()'
// CHECK-NEXT: |-IntegerLiteral {{.*}} <col:26> 'int' 1
// CHECK-NEXT: `-IntegerLiteral {{.*}} <col:29> 'int' 2
}
26 changes: 19 additions & 7 deletions clang/test/CodeGenCXX/cxx2b-static-call-operator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,22 @@ void CallsTheLambda() {

// CHECK: define {{.*}}CallsTheLambda{{.*}}
// CHECK-NEXT: entry:
// CHECK-NEXT: %call = call noundef i32 {{.*}}(i32 noundef 1, i32 noundef 2)
// CHECK: {{.*}}call {{.*}}GetALambda{{.*}}()
// CHECK-NEXT: {{.*}} = call noundef i32 {{.*}}(i32 noundef 1, i32 noundef 2)
// CHECK-NEXT: ret void
// CHECK-NEXT: }

Functor GetAFunctor() {
return {};
}

void call_static_call_operator() {
Functor f;
f(101, 102);
f.operator()(201, 202);
Functor{}(301, 302);
Functor::operator()(401, 402);
GetAFunctor()(501, 502);
}

// CHECK: define {{.*}}call_static_call_operator{{.*}}
Expand All @@ -37,6 +43,8 @@ void call_static_call_operator() {
// CHECK-NEXT: {{.*}} = call noundef i32 {{.*}}Functor{{.*}}(i32 noundef 201, i32 noundef 202)
// CHECK-NEXT: {{.*}} = call noundef i32 {{.*}}Functor{{.*}}(i32 noundef 301, i32 noundef 302)
// CHECK-NEXT: {{.*}} = call noundef i32 {{.*}}Functor{{.*}}(i32 noundef 401, i32 noundef 402)
// CHECK: {{.*}}call {{.*}}GetAFunctor{{.*}}()
// CHECK-NEXT: {{.*}} = call noundef i32 {{.*}}Functor{{.*}}(i32 noundef 501, i32 noundef 502)
// CHECK-NEXT: ret void
// CHECK-NEXT: }

Expand Down Expand Up @@ -106,12 +114,16 @@ void test_dep_functors() {

// CHECK: define {{.*}}test_dep_functors{{.*}}
// CHECK-NEXT: entry:
// CHECK: %call = call noundef i32 {{.*}}DepFunctor{{.*}}(float noundef 1.000000e+00)
// CHECK: %call1 = call noundef i32 {{.*}}DepFunctor{{.*}}(i1 noundef zeroext true)
// CHECK: %call2 = call noundef i32 {{.*}}dep_lambda1{{.*}}(float noundef 1.000000e+00)
// CHECK: %call3 = call noundef i32 {{.*}}dep_lambda1{{.*}}(i1 noundef zeroext true)
// CHECK: %call4 = call noundef i32 {{.*}}dep_lambda2{{.*}}(float noundef 1.000000e+00)
// CHECK: %call5 = call noundef i32 {{.*}}dep_lambda2{{.*}}(i1 noundef zeroext true)
// CHECK: {{.*}} = call noundef i32 {{.*}}DepFunctor{{.*}}(float noundef 1.000000e+00)
// CHECK: {{.*}} = call noundef i32 {{.*}}DepFunctor{{.*}}(i1 noundef zeroext true)
// CHECK: {{.*}}call {{.*}}dep_lambda1{{.*}}()
// CHECK: {{.*}} = call noundef i32 {{.*}}dep_lambda1{{.*}}(float noundef 1.000000e+00)
// CHECK: {{.*}}call {{.*}}dep_lambda1{{.*}}()
// CHECK: {{.*}} = call noundef i32 {{.*}}dep_lambda1{{.*}}(i1 noundef zeroext true)
// CHECK: {{.*}}call {{.*}}dep_lambda2{{.*}}()
// CHECK: {{.*}} = call noundef i32 {{.*}}dep_lambda2{{.*}}(float noundef 1.000000e+00)
// CHECK: {{.*}}call {{.*}}dep_lambda2{{.*}}()
// CHECK: {{.*}} = call noundef i32 {{.*}}dep_lambda2{{.*}}(i1 noundef zeroext true)
// CHECK: ret void
// CHECK-NEXT: }

Expand Down
11 changes: 9 additions & 2 deletions clang/test/CodeGenCXX/cxx2b-static-subscript-operator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@ struct Functor {
}
};

Functor GetAFunctor() {
return {};
}

void call_static_subscript_operator() {
Functor f;
f[101, 102];
f.operator[](201, 202);
Functor{}[301, 302];
Functor::operator[](401, 402);
GetAFunctor()[501, 502];
}

// CHECK: define {{.*}}call_static_subscript_operator{{.*}}
Expand All @@ -21,6 +26,8 @@ void call_static_subscript_operator() {
// CHECK-NEXT: {{.*}} = call noundef i32 {{.*}}Functor{{.*}}(i32 noundef 201, i32 noundef 202)
// CHECK-NEXT: {{.*}} = call noundef i32 {{.*}}Functor{{.*}}(i32 noundef 301, i32 noundef 302)
// CHECK-NEXT: {{.*}} = call noundef i32 {{.*}}Functor{{.*}}(i32 noundef 401, i32 noundef 402)
// CHECK: {{.*}}call {{.*}}GetAFunctor{{.*}}()
// CHECK-NEXT: {{.*}} = call noundef i32 {{.*}}Functor{{.*}}(i32 noundef 501, i32 noundef 502)
// CHECK-NEXT: ret void
// CHECK-NEXT: }

Expand Down Expand Up @@ -60,7 +67,7 @@ void test_dep_functors() {

// CHECK: define {{.*}}test_dep_functors{{.*}}
// CHECK-NEXT: entry:
// CHECK: %call = call noundef i32 {{.*}}DepFunctor{{.*}}(float noundef 1.000000e+00)
// CHECK: %call1 = call noundef i32 {{.*}}DepFunctor{{.*}}(i1 noundef zeroext true)
// CHECK: {{.*}} = call noundef i32 {{.*}}DepFunctor{{.*}}(float noundef 1.000000e+00)
// CHECK: {{.*}} = call noundef i32 {{.*}}DepFunctor{{.*}}(i1 noundef zeroext true)
// CHECK: ret void
// CHECK-NEXT: }
31 changes: 31 additions & 0 deletions clang/test/SemaCXX/cxx2b-static-operator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++23 %s

// expected-no-diagnostics

namespace A {

struct Foo {
static int operator()(int a, int b) { return a + b; }
static int operator[](int a, int b) { return a + b; }
};

void ok() {
// Should pass regardless of const / volatile
Foo foo;
foo(1, 2);
foo[1, 2];

const Foo fooC;
fooC(1, 2);
fooC[1, 2];

const Foo fooV;
fooV(1, 2);
fooV[1, 2];

const volatile Foo fooCV;
fooCV(1, 2);
fooCV[1, 2];
}

}