llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang Author: cor3ntin (cor3ntin) <details> <summary>Changes</summary> Implement wg21.link/P2797. Because taking the address of an explicit object member function results in a function pointer, a call expression where the id-expression is an explicit object member is made to behave consistently with that model. This change forces clang to perform overload resolution in the presence of an id-expression of the form `(&Foo::bar)(args...)`, which we previously failed to do consistently. --- Patch is 20.88 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/93430.diff 12 Files Affected: - (modified) clang/docs/ReleaseNotes.rst (+3) - (modified) clang/include/clang/AST/ExprCXX.h (+4) - (modified) clang/include/clang/Sema/Overload.h (+4) - (modified) clang/lib/Sema/SemaExpr.cpp (+24-3) - (modified) clang/lib/Sema/SemaOverload.cpp (+64-10) - (modified) clang/test/CXX/drs/cwg1xx.cpp (+3-5) - (modified) clang/test/CXX/drs/cwg26xx.cpp (+26) - (added) clang/test/CXX/drs/cwg2771.cpp (+18) - (modified) clang/test/CodeGenCXX/cxx2b-deducing-this.cpp (+20) - (modified) clang/test/SemaCXX/cxx2b-deducing-this.cpp (+27) - (modified) clang/www/cxx_dr_status.html (+3-3) - (modified) clang/www/cxx_status.html (+1-1) ``````````diff diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 825e91876ffce..0a945a9989b0d 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -209,6 +209,9 @@ C++23 Feature Support - Added a ``__reference_converts_from_temporary`` builtin, completing the necessary compiler support for `P2255R2: Type trait to determine if a reference binds to a temporary <https://wg21.link/P2255R2>`_. +- Implemented `P2797R0: Static and explicit object member functions with the same parameter-type-lists <https://wg21.link/P2797R0>`. + This completes the support for "deducing this". + C++2c Feature Support ^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h index dbf693611a7fa..557e9fd99c293 100644 --- a/clang/include/clang/AST/ExprCXX.h +++ b/clang/include/clang/AST/ExprCXX.h @@ -3027,6 +3027,7 @@ class OverloadExpr : public Expr { struct FindResult { OverloadExpr *Expression; bool IsAddressOfOperand; + bool IsAddressOfOperandWithParen; bool HasFormOfMemberPointer; }; @@ -3039,6 +3040,7 @@ class OverloadExpr : public Expr { assert(E->getType()->isSpecificBuiltinType(BuiltinType::Overload)); FindResult Result; + bool HasParen = isa<ParenExpr>(E); E = E->IgnoreParens(); if (isa<UnaryOperator>(E)) { @@ -3048,10 +3050,12 @@ class OverloadExpr : public Expr { Result.HasFormOfMemberPointer = (E == Ovl && Ovl->getQualifier()); Result.IsAddressOfOperand = true; + Result.IsAddressOfOperandWithParen = HasParen; Result.Expression = Ovl; } else { Result.HasFormOfMemberPointer = false; Result.IsAddressOfOperand = false; + Result.IsAddressOfOperandWithParen = false; Result.Expression = cast<OverloadExpr>(E); } diff --git a/clang/include/clang/Sema/Overload.h b/clang/include/clang/Sema/Overload.h index 76311b00d2fc5..64cdd6cdf043d 100644 --- a/clang/include/clang/Sema/Overload.h +++ b/clang/include/clang/Sema/Overload.h @@ -899,6 +899,8 @@ class Sema; /// object argument. bool IgnoreObjectArgument : 1; + bool TookAddressOfOverload : 1; + /// True if the candidate was found using ADL. CallExpr::ADLCallKind IsADLCandidate : 1; @@ -999,6 +1001,8 @@ class Sema; /// Initialization of an object of class type by constructor, /// using either a parenthesized or braced list of arguments. CSK_InitByConstructor, + + CSK_AddressOfOverloadSet, }; /// Information about operator rewrites to consider when adding operator diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 410f80ae864a1..15496f3323b02 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -5813,6 +5813,24 @@ static TypoCorrection TryTypoCorrectionForCall(Sema &S, Expr *Fn, return TypoCorrection(); } +static bool isParenthetizedAndQualifiedAddressOfExpr(Expr *Fn) { + if (!isa<ParenExpr>(Fn)) + return false; + + Fn = Fn->IgnoreParens(); + auto *UO = dyn_cast<UnaryOperator>(Fn); + if (!UO) + return false; + assert(cast<UnaryOperator>(Fn)->getOpcode() == UO_AddrOf); + if (auto *DRE = dyn_cast<DeclRefExpr>(UO->getSubExpr()->IgnoreParens())) { + return DRE->hasQualifier(); + } + if (auto *OVL = dyn_cast<OverloadExpr>(UO->getSubExpr()->IgnoreParens())) { + return OVL->getQualifier(); + } + return false; +} + /// ConvertArgumentsForCall - Converts the arguments specified in /// Args/NumArgs to the parameter types of the function FDecl with /// function prototype Proto. Call is the call expression itself, and @@ -5834,9 +5852,12 @@ Sema::ConvertArgumentsForCall(CallExpr *Call, Expr *Fn, // C99 6.5.2.2p7 - the arguments are implicitly converted, as if by // assignment, to the types of the corresponding parameter, ... + + bool AddressOf = isParenthetizedAndQualifiedAddressOfExpr(Fn); bool HasExplicitObjectParameter = - FDecl && FDecl->hasCXXExplicitFunctionObjectParameter(); - unsigned ExplicitObjectParameterOffset = HasExplicitObjectParameter ? 1 : 0; + !AddressOf && FDecl && FDecl->hasCXXExplicitFunctionObjectParameter(); + unsigned ExplicitObjectParameterOffset = + HasExplicitObjectParameter && !AddressOf ? 1 : 0; unsigned NumParams = Proto->getNumParams(); bool Invalid = false; unsigned MinArgs = FDecl ? FDecl->getMinRequiredArguments() : NumParams; @@ -6546,7 +6567,7 @@ ExprResult Sema::BuildCallExpr(Scope *Scope, Expr *Fn, SourceLocation LParenLoc, OverloadExpr::FindResult find = OverloadExpr::find(Fn); // We aren't supposed to apply this logic if there's an '&' involved. - if (!find.HasFormOfMemberPointer) { + if (!find.HasFormOfMemberPointer || find.IsAddressOfOperandWithParen) { if (Expr::hasAnyTypeDependentArguments(ArgExprs)) return CallExpr::Create(Context, Fn, ArgExprs, Context.DependentTy, VK_PRValue, RParenLoc, CurFPFeatureOverrides()); diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index 0c89fca8d38eb..17cccefa8e929 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6971,6 +6971,7 @@ void Sema::AddOverloadCandidate( Candidate.IsSurrogate = false; Candidate.IsADLCandidate = IsADLCandidate; Candidate.IgnoreObjectArgument = false; + Candidate.TookAddressOfOverload = false; Candidate.ExplicitCallArguments = Args.size(); // Explicit functions are not actually candidates at all if we're not @@ -7545,10 +7546,19 @@ Sema::AddMethodCandidate(CXXMethodDecl *Method, DeclAccessPair FoundDecl, CandidateSet.getRewriteInfo().getRewriteKind(Method, PO); Candidate.IsSurrogate = false; Candidate.IgnoreObjectArgument = false; + Candidate.TookAddressOfOverload = + CandidateSet.getKind() == OverloadCandidateSet::CSK_AddressOfOverloadSet; Candidate.ExplicitCallArguments = Args.size(); - unsigned NumParams = Method->getNumExplicitParams(); - unsigned ExplicitOffset = Method->isExplicitObjectMemberFunction() ? 1 : 0; + bool IgnoreExplicitObject = + (Method->isExplicitObjectMemberFunction() && + CandidateSet.getKind() == + OverloadCandidateSet::CSK_AddressOfOverloadSet); + unsigned ExplicitOffset = + !IgnoreExplicitObject && Method->isExplicitObjectMemberFunction() ? 1 : 0; + unsigned NumParams = Method->getNumParams() - ExplicitOffset; + if (CandidateSet.getKind() == OverloadCandidateSet::CSK_AddressOfOverloadSet) + NumParams += int(Method->isImplicitObjectMemberFunction()); // (C++ 13.3.2p2): A candidate function having fewer than m // parameters is viable only if it has an ellipsis in its parameter @@ -7566,7 +7576,12 @@ Sema::AddMethodCandidate(CXXMethodDecl *Method, DeclAccessPair FoundDecl, // (8.3.6). For the purposes of overload resolution, the // parameter list is truncated on the right, so that there are // exactly m parameters. - unsigned MinRequiredArgs = Method->getMinRequiredExplicitArguments(); + unsigned MinRequiredArgs = Method->getMinRequiredArguments() - ExplicitOffset; + if (CandidateSet.getKind() == + OverloadCandidateSet::CSK_AddressOfOverloadSet && + Method->isImplicitObjectMemberFunction()) + MinRequiredArgs++; + if (Args.size() < MinRequiredArgs && !PartialOverloading) { // Not enough arguments. Candidate.Viable = false; @@ -7636,7 +7651,16 @@ Sema::AddMethodCandidate(CXXMethodDecl *Method, DeclAccessPair FoundDecl, // exist for each argument an implicit conversion sequence // (13.3.3.1) that converts that argument to the corresponding // parameter of F. - QualType ParamType = Proto->getParamType(ArgIdx + ExplicitOffset); + QualType ParamType; + if (CandidateSet.getKind() == + OverloadCandidateSet::CSK_AddressOfOverloadSet && + Method->isImplicitObjectMemberFunction()) { + ParamType = ArgIdx == 0 + ? Method->getFunctionObjectParameterReferenceType() + : Proto->getParamType(ArgIdx - 1); + } else { + ParamType = Proto->getParamType(ArgIdx + ExplicitOffset); + } Candidate.Conversions[ConvIdx] = TryCopyInitialization(*this, Args[ArgIdx], ParamType, SuppressUserConversions, @@ -7998,6 +8022,7 @@ void Sema::AddConversionCandidate( Candidate.Function = Conversion; Candidate.IsSurrogate = false; Candidate.IgnoreObjectArgument = false; + Candidate.TookAddressOfOverload = false; Candidate.FinalConversion.setAsIdentityConversion(); Candidate.FinalConversion.setFromType(ConvType); Candidate.FinalConversion.setAllToTypes(ToType); @@ -8200,6 +8225,7 @@ void Sema::AddTemplateConversionCandidate( Candidate.FailureKind = ovl_fail_bad_deduction; Candidate.IsSurrogate = false; Candidate.IgnoreObjectArgument = false; + Candidate.TookAddressOfOverload = false; Candidate.ExplicitCallArguments = 1; Candidate.DeductionFailure = MakeDeductionFailureInfo(Context, Result, Info); @@ -8240,6 +8266,7 @@ void Sema::AddSurrogateCandidate(CXXConversionDecl *Conversion, Candidate.Viable = true; Candidate.IsSurrogate = true; Candidate.IgnoreObjectArgument = false; + Candidate.TookAddressOfOverload = false; Candidate.ExplicitCallArguments = Args.size(); // Determine the implicit conversion sequence for the implicit @@ -8465,6 +8492,7 @@ void Sema::AddBuiltinCandidate(QualType *ParamTys, ArrayRef<Expr *> Args, Candidate.Function = nullptr; Candidate.IsSurrogate = false; Candidate.IgnoreObjectArgument = false; + Candidate.TookAddressOfOverload = false; std::copy(ParamTys, ParamTys + Args.size(), Candidate.BuiltinParamTypes); // Determine the implicit conversion sequences for each of the @@ -10929,6 +10957,12 @@ OverloadCandidateSet::BestViableFunction(Sema &S, SourceLocation Loc, if (Best->Function && Best->Function->isDeleted()) return OR_Deleted; + if (auto *M = dyn_cast_or_null<CXXMethodDecl>(Best->Function); + Kind == CSK_AddressOfOverloadSet && M && + M->isImplicitObjectMemberFunction()) { + return OR_No_Viable_Function; + } + if (!EquivalentCands.empty()) S.diagnoseEquivalentInternalLinkageDeclarations(Loc, Best->Function, EquivalentCands); @@ -11538,7 +11572,8 @@ static bool CheckArityMismatch(Sema &S, OverloadCandidate *Cand, /// General arity mismatch diagnosis over a candidate in a candidate set. static void DiagnoseArityMismatch(Sema &S, NamedDecl *Found, Decl *D, - unsigned NumFormalArgs) { + unsigned NumFormalArgs, + bool IsAddressOf = false) { assert(isa<FunctionDecl>(D) && "The templated declaration should at least be a function" " when diagnosing bad template argument deduction due to too many" @@ -11551,7 +11586,8 @@ static void DiagnoseArityMismatch(Sema &S, NamedDecl *Found, Decl *D, unsigned MinParams = Fn->getMinRequiredExplicitArguments(); // at least / at most / exactly - bool HasExplicitObjectParam = Fn->hasCXXExplicitFunctionObjectParameter(); + bool HasExplicitObjectParam = + !IsAddressOf && Fn->hasCXXExplicitFunctionObjectParameter(); unsigned ParamCount = FnTy->getNumParams() - (HasExplicitObjectParam ? 1 : 0); unsigned mode, modeCount; if (NumFormalArgs < MinParams) { @@ -11593,7 +11629,8 @@ static void DiagnoseArityMismatch(Sema &S, NamedDecl *Found, Decl *D, static void DiagnoseArityMismatch(Sema &S, OverloadCandidate *Cand, unsigned NumFormalArgs) { if (!CheckArityMismatch(S, Cand, NumFormalArgs)) - DiagnoseArityMismatch(S, Cand->FoundDecl, Cand->Function, NumFormalArgs); + DiagnoseArityMismatch(S, Cand->FoundDecl, Cand->Function, NumFormalArgs, + Cand->TookAddressOfOverload); } static TemplateDecl *getDescribedTemplate(Decl *Templated) { @@ -14076,6 +14113,21 @@ static ExprResult FinishOverloadedCallExpr(Sema &SemaRef, Scope *S, Expr *Fn, } case OR_No_Viable_Function: { + if (*Best != CandidateSet->end() && + CandidateSet->getKind() == + clang::OverloadCandidateSet::CSK_AddressOfOverloadSet) { + if (CXXMethodDecl *M = + dyn_cast_if_present<CXXMethodDecl>((*Best)->Function); + M && M->isImplicitObjectMemberFunction()) { + CandidateSet->NoteCandidates( + PartialDiagnosticAt( + Fn->getBeginLoc(), + SemaRef.PDiag(diag::err_member_call_without_object) << 0 << M), + SemaRef, OCD_AmbiguousCandidates, Args); + return ExprError(); + } + } + // Try to recover by looking for viable functions which the user might // have meant to call. ExprResult Recovery = BuildRecoveryCallExpr(SemaRef, S, Fn, ULE, LParenLoc, @@ -14167,8 +14219,10 @@ ExprResult Sema::BuildOverloadedCallExpr(Scope *S, Expr *Fn, Expr *ExecConfig, bool AllowTypoCorrection, bool CalleesAddressIsTaken) { - OverloadCandidateSet CandidateSet(Fn->getExprLoc(), - OverloadCandidateSet::CSK_Normal); + OverloadCandidateSet CandidateSet( + Fn->getExprLoc(), CalleesAddressIsTaken + ? OverloadCandidateSet::CSK_AddressOfOverloadSet + : OverloadCandidateSet::CSK_Normal); ExprResult result; if (buildOverloadedCallSet(S, Fn, ULE, Args, LParenLoc, &CandidateSet, @@ -16333,7 +16387,7 @@ ExprResult Sema::FixOverloadedFunctionReference(Expr *E, DeclAccessPair Found, assert(UnOp->getOpcode() == UO_AddrOf && "Can only take the address of an overloaded function"); if (CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(Fn)) { - if (Method->isStatic()) { + if (!Method->isImplicitObjectMemberFunction()) { // Do nothing: static member functions aren't any different // from non-member functions. } else { diff --git a/clang/test/CXX/drs/cwg1xx.cpp b/clang/test/CXX/drs/cwg1xx.cpp index a8f9b705a9866..21859fc6b1cbf 100644 --- a/clang/test/CXX/drs/cwg1xx.cpp +++ b/clang/test/CXX/drs/cwg1xx.cpp @@ -843,23 +843,21 @@ namespace cwg161 { // cwg161: 3.1 }; } -namespace cwg162 { // cwg162: no +namespace cwg162 { // cwg162: 19 struct A { char &f(char); static int &f(int); void g() { int &a = (&A::f)(0); - // FIXME: expected-error@-1 {{reference to overloaded function could not be resolved; did you mean to call it?}} char &b = (&A::f)('0'); - // expected-error@-1 {{reference to overloaded function could not be resolved; did you mean to call it?}} + // expected-error@-1 {{non-const lvalue reference to type 'char' cannot bind to a value of unrelated type 'int'}} } }; int &c = (&A::f)(0); - // FIXME: expected-error@-1 {{reference to overloaded function could not be resolved; did you mean to call it?}} char &d = (&A::f)('0'); - // expected-error@-1 {{reference to overloaded function could not be resolved; did you mean to call it?}} + // expected-error@-1 {{non-const lvalue reference to type 'char' cannot bind to a value of unrelated type 'int'}} } // cwg163: na diff --git a/clang/test/CXX/drs/cwg26xx.cpp b/clang/test/CXX/drs/cwg26xx.cpp index d3c5b5bb7b6b9..6c91377643fb9 100644 --- a/clang/test/CXX/drs/cwg26xx.cpp +++ b/clang/test/CXX/drs/cwg26xx.cpp @@ -240,3 +240,29 @@ void test() { } } #endif + + +#if __cplusplus >= 202302L +namespace cwg2692 { // cwg2692: 19 + + struct A { + static void f(A); // #cwg2692-1 + void f(this A); // #cwg2692-2 + + void g(); + }; + + void A::g() { + (&A::f)(A()); // expected-error {{call to 'f' is ambiguous}} + // expected-note@#cwg2692-1 {{candidate}} + // expected-note@#cwg2692-2 {{candidate}} + + + + (&A::f)(); // expected-error {{no matching function for call to 'f'}} + // expected-note@#cwg2692-1 {{candidate function not viable: requires 1 argument, but 0 were provided}} + // expected-note@#cwg2692-2 {{candidate function not viable: requires at most 1 argument, but 0 were provided}} + } + +} +#endif diff --git a/clang/test/CXX/drs/cwg2771.cpp b/clang/test/CXX/drs/cwg2771.cpp new file mode 100644 index 0000000000000..e877e6fe4a5fe --- /dev/null +++ b/clang/test/CXX/drs/cwg2771.cpp @@ -0,0 +1,18 @@ +// RUN: %clang_cc1 -std=c++23 %s -ast-dump | FileCheck --check-prefixes=CXX23 %s + +namespace cwg2771 { // cwg2771: 18 + +struct A{ + int a; + void cwg2771(){ + int* r = &a; + } +}; +// CXX23: CXXMethodDecl{{.+}}cwg2771 +// CXX23-NEXT: CompoundStmt +// CXX23-NEXT: DeclStmt +// CXX23-NEXT: VarDecl +// CXX23-NEXT: UnaryOperator +// CXX23-NEXT: MemberExpr +// CXX23-NEXT: CXXThisExpr{{.+}}'cwg2771::A *' +} diff --git a/clang/test/CodeGenCXX/cxx2b-deducing-this.cpp b/clang/test/CodeGenCXX/cxx2b-deducing-this.cpp index 649fe2afbf4e9..f9f9fbd7397f8 100644 --- a/clang/test/CodeGenCXX/cxx2b-deducing-this.cpp +++ b/clang/test/CodeGenCXX/cxx2b-deducing-this.cpp @@ -245,3 +245,23 @@ void f() { d(); } } + + +namespace P2797 { +struct C { + void c(this const C&); // #first + void c() &; // #second + static void c(int = 0); // #third + + void d() { + (&C::c)(C{}); + (&C::c)(); + } +}; +void test() { + (void)C{}.d(); +} +// CHECK-LABEL: {{.*}} @_ZN5P27971C1dEv +// CHECK: call void @_ZNH5P27971C1cERKS0_ +// CHECK: call void @_ZN5P27971C1cEi +} diff --git a/clang/test/SemaCXX/cxx2b-deducing-this.cpp b/clang/test/SemaCXX/cxx2b-deducing-this.cpp index cdb9d1324b974..86046497c2ef2 100644 --- a/clang/test/SemaCXX/cxx2b-deducing-this.cpp +++ b/clang/test/SemaCXX/cxx2b-deducing-this.cpp @@ -893,3 +893,30 @@ void g() { a * lval; } } + +namespace P2797 { +struct C { + void c(this const C&); // #first + void c() &; // #second + static void c(int = 0); // #third + + void d() { + c(); // expected-error {{call to member function 'c' is ambiguous}} + // expected-note@#first {{candidate function}} + // expected-note@#second {{candidate function}} + // expected-note@#third {{candidate function}} + + (C::c)(); // expected-error {{call to member function 'c' is ambiguous}} + // expected-note@#first {{candidate function}} + // expected-note@#second {{candidate function}} + // expected-note@#third {{candidate function}} + + (&(C::c))(); // expected-error {{cannot create a non-constant pointer to member function}} + (&C::c)(C{}); + (&C::c)(*this); // expected-error {{call to non-static member function without an object argument}} + // expected-note@#second {{candidate function}} + + (&C::c)(); + } +}; +} diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html index 4cce88fe0490f..7df6c0a05a487 100755 --- a/clang/www/cxx_dr_status.html +++ b/clang/www/cxx_dr_status.html @@ -1010,7 +1010,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2> <td><a href="https://cplusplus.github.io/CWG/issues/162.html">162</a></td> <td>CD1</td> <td>(<TT>&C::f)()</TT> with nonstatic members</td> - <td class="none" align="center">No</td> + <td class="unreleased" align="center">Clang 19</td> </tr> <tr id="163"> <td><a href="https://cplusplus.github.io/CWG/issues/163.html">163</a></td> @@ -15960,7 +15960,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2> <td><a href="https://cplusplus.github.io/CWG/issues/2692.html">2692</a></td> <td>C++23</td> <td>Static and explicit object member functions with the same parameter-type-lists</td> - <td class="unknown" align="center">Unknown</td> + <td class="unreleased" align="center">Clang 19</td> </tr> <tr class="open" id="2693"> <td><a href="https://cplusplus.github.io/CWG/issues/2693.html">2693</a></td> @@ -16435,7 +16435,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2> <td><a ... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/93430 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits