https://github.com/offsetof updated https://github.com/llvm/llvm-project/pull/131320
>From 12a50619db5432d479f767724691c306aa289ce2 Mon Sep 17 00:00:00 2001 From: offsetof <offse...@mailo.com> Date: Sun, 16 Mar 2025 09:59:44 +0000 Subject: [PATCH] [clang] Refine handling of C++20 aggregate initialization * Move parts of `InitializationSequence::InitializeFrom` corresponding to C++ [dcl.init.general] p16.6.1 and p16.6.2 into a separate function, `TryConstructorOrParenListInitialization` * Use it in `TryListInitialization` to implement [dcl.init.list] p3.2 * Fix parenthesized aggregate initialization being attempted in copy-initialization contexts or when the constructor call is ambiguous --- clang/docs/ReleaseNotes.rst | 1 + clang/lib/Sema/SemaInit.cpp | 106 ++++++++------ .../dcl.init/dcl.init.general/p16-cxx20.cpp | 132 ++++++++++++++++-- 3 files changed, 183 insertions(+), 56 deletions(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 2a1c5ee2d788e..9aba41c91c216 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -318,6 +318,7 @@ Bug Fixes to C++ Support - Fixed an assertion failure affecting code that uses C++23 "deducing this". (#GH130272) - Clang now properly instantiates destructors for initialized members within non-delegating constructors. (#GH93251) - Correctly diagnoses if unresolved using declarations shadows template paramters (#GH129411) +- Fixed C++20 aggregate initialization rules being incorrectly applied in certain contexts. (#GH131320) Bug Fixes to AST Handling ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index 56ec33fe37bf3..56b6cafe5c53d 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -4633,6 +4633,59 @@ static void TryConstructorInitialization(Sema &S, IsListInit | IsInitListCopy, AsInitializerList); } +static void TryOrBuildParenListInitialization( + Sema &S, const InitializedEntity &Entity, const InitializationKind &Kind, + ArrayRef<Expr *> Args, InitializationSequence &Sequence, bool VerifyOnly, + ExprResult *Result = nullptr); + +/// Attempt to initialize an object of a class type either by +/// direct-initialization, or by copy-initialization from an +/// expression of the same or derived class type. This corresponds +/// to the first two sub-bullets of C++2c [dcl.init.general] p16.6. +/// +/// \param IsAggrListInit Is this non-list-initialization being done as +/// part of a list-initialization of an aggregate +/// from a single expression of the same or +/// derived class type (C++2c [dcl.init.list] p3.2)? +static void TryConstructorOrParenListInitialization( + Sema &S, const InitializedEntity &Entity, const InitializationKind &Kind, + MultiExprArg Args, QualType DestType, InitializationSequence &Sequence, + bool IsAggrListInit) { + // C++2c [dcl.init.general] p16.6: + // * Otherwise, if the destination type is a class type: + // * If the initializer expression is a prvalue and + // the cv-unqualified version of the source type is the same + // as the destination type, the initializer expression is used + // to initialize the destination object. + // * Otherwise, if the initialization is direct-initialization, + // or if it is copy-initialization where the cv-unqualified + // version of the source type is the same as or is derived from + // the class of the destination type, constructors are considered. + // The applicable constructors are enumerated, and the best one + // is chosen through overload resolution. Then: + // * If overload resolution is successful, the selected + // constructor is called to initialize the object, with + // the initializer expression or expression-list as its + // argument(s). + TryConstructorInitialization(S, Entity, Kind, Args, DestType, DestType, + Sequence, /*IsListInit=*/false, IsAggrListInit); + + // * Otherwise, if no constructor is viable, the destination type + // is an aggregate class, and the initializer is a parenthesized + // expression-list, the object is initialized as follows. [...] + // Parenthesized initialization of aggregates is a C++20 feature. + if (S.getLangOpts().CPlusPlus20 && + Kind.getKind() == InitializationKind::IK_Direct && Sequence.Failed() && + Sequence.getFailureKind() == + InitializationSequence::FK_ConstructorOverloadFailed && + Sequence.getFailedOverloadResult() == OR_No_Viable_Function && + (IsAggrListInit || DestType->isAggregateType())) + TryOrBuildParenListInitialization(S, Entity, Kind, Args, Sequence, + /*VerifyOnly=*/true); + + // * Otherwise, the initialization is ill-formed. +} + static bool ResolveOverloadedFunctionForReferenceBinding(Sema &S, Expr *Initializer, @@ -4846,11 +4899,16 @@ static void TryListInitialization(Sema &S, QualType InitType = InitList->getInit(0)->getType(); if (S.Context.hasSameUnqualifiedType(InitType, DestType) || S.IsDerivedFrom(InitList->getBeginLoc(), InitType, DestType)) { + InitializationKind SubKind = + Kind.getKind() == InitializationKind::IK_DirectList + ? InitializationKind::CreateDirect(Kind.getLocation(), + InitList->getLBraceLoc(), + InitList->getRBraceLoc()) + : Kind; Expr *InitListAsExpr = InitList; - TryConstructorInitialization(S, Entity, Kind, InitListAsExpr, DestType, - DestType, Sequence, - /*InitListSyntax*/false, - /*IsInitListCopy*/true); + TryConstructorOrParenListInitialization( + S, Entity, SubKind, InitListAsExpr, DestType, Sequence, + /*IsAggrListInit=*/true); return; } } @@ -5709,7 +5767,7 @@ static void TryDefaultInitialization(Sema &S, static void TryOrBuildParenListInitialization( Sema &S, const InitializedEntity &Entity, const InitializationKind &Kind, ArrayRef<Expr *> Args, InitializationSequence &Sequence, bool VerifyOnly, - ExprResult *Result = nullptr) { + ExprResult *Result) { unsigned EntityIndexToProcess = 0; SmallVector<Expr *, 4> InitExprs; QualType ResultType; @@ -6688,42 +6746,8 @@ void InitializationSequence::InitializeFrom(Sema &S, (Context.hasSameUnqualifiedType(SourceType, DestType) || (Initializer && S.IsDerivedFrom(Initializer->getBeginLoc(), SourceType, DestType))))) { - TryConstructorInitialization(S, Entity, Kind, Args, DestType, DestType, - *this); - - // We fall back to the "no matching constructor" path if the - // failed candidate set has functions other than the three default - // constructors. For example, conversion function. - if (const auto *RD = - dyn_cast<CXXRecordDecl>(DestType->getAs<RecordType>()->getDecl()); - // In general, we should call isCompleteType for RD to check its - // completeness, we don't call it here as it was already called in the - // above TryConstructorInitialization. - S.getLangOpts().CPlusPlus20 && RD && RD->hasDefinition() && - RD->isAggregate() && Failed() && - getFailureKind() == FK_ConstructorOverloadFailed) { - // Do not attempt paren list initialization if overload resolution - // resolves to a deleted function . - // - // We may reach this condition if we have a union wrapping a class with - // a non-trivial copy or move constructor and we call one of those two - // constructors. The union is an aggregate, but the matched constructor - // is implicitly deleted, so we need to prevent aggregate initialization - // (otherwise, it'll attempt aggregate initialization by initializing - // the first element with a reference to the union). - OverloadCandidateSet::iterator Best; - OverloadingResult OR = getFailedCandidateSet().BestViableFunction( - S, Kind.getLocation(), Best); - if (OR != OverloadingResult::OR_Deleted) { - // C++20 [dcl.init] 17.6.2.2: - // - Otherwise, if no constructor is viable, the destination type is - // an - // aggregate class, and the initializer is a parenthesized - // expression-list. - TryOrBuildParenListInitialization(S, Entity, Kind, Args, *this, - /*VerifyOnly=*/true); - } - } + TryConstructorOrParenListInitialization(S, Entity, Kind, Args, DestType, + *this, /*IsAggrListInit=*/false); } else { // - Otherwise (i.e., for the remaining copy-initialization cases), // user-defined conversion sequences that can convert from the diff --git a/clang/test/CXX/dcl.decl/dcl.init/dcl.init.general/p16-cxx20.cpp b/clang/test/CXX/dcl.decl/dcl.init/dcl.init.general/p16-cxx20.cpp index 1ad205d769c38..fc6908fefac85 100644 --- a/clang/test/CXX/dcl.decl/dcl.init/dcl.init.general/p16-cxx20.cpp +++ b/clang/test/CXX/dcl.decl/dcl.init/dcl.init.general/p16-cxx20.cpp @@ -1,18 +1,120 @@ // RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 %s -// If the initializer is (), the object is value-initialized. - -// expected-no-diagnostics namespace GH69890 { -struct A { - constexpr A() {} - int x; -}; - -struct B : A { - int y; -}; - -static_assert(B().x == 0); -static_assert(B().y == 0); -} + // If the initializer is (), the object is value-initialized. + struct A { + constexpr A() {} + int x; + }; + + struct B : A { + int y; + }; + + static_assert(B().x == 0); + static_assert(B().y == 0); +} // namespace GH69890 + +namespace P0960R3 { + struct A { // expected-note 22 {{candidate constructor}} + int i; + operator int() volatile; + }; + volatile A va; + + A a1(va); + A a2 = va; // expected-error {{no matching constructor for initialization of 'A'}} + A a3 {va}; + A a4 = {va}; // expected-error {{no matching constructor for initialization of 'A'}} + + A f() { + return va; // expected-error {{no matching constructor for initialization of 'A'}} + return {va}; // expected-error {{no matching constructor for initialization of 'A'}} + } + + int g(A); // expected-note 2 {{passing argument to parameter here}} + int i = g(va); // expected-error {{no matching constructor for initialization of 'A'}} + int j = g({va}); // expected-error {{no matching constructor for initialization of 'A'}} + + struct Ambig { + operator const A&(); // expected-note {{candidate function}} + operator A&&(); // expected-note {{candidate function}} + operator int(); + }; + + A a5(Ambig {}); // expected-error {{call to constructor of 'A' is ambiguous}} + A a6 = Ambig {}; // expected-error {{conversion from 'Ambig' to 'A' is ambiguous}} + A a7 {Ambig {}}; + A a8 = {Ambig {}}; + + A a9(1); + A a10 = 1; // expected-error {{no viable conversion from 'int' to 'A'}} + A a11 {1}; + A a12 = {1}; + + + struct B { // expected-note 12 {{candidate constructor}} + int i; + virtual operator int() volatile; + }; + volatile B vb; + + B b1(vb); // expected-error {{no matching constructor for initialization of 'B'}} + B b2 = vb; // expected-error {{no matching constructor for initialization of 'B'}} + B b3 {vb}; // expected-error {{no matching constructor for initialization of 'B'}} + B b4 = {vb}; // expected-error {{no matching constructor for initialization of 'B'}} + + + struct Immovable { + Immovable(); + Immovable(const Immovable&) = delete; // #Imm_copy + }; + + struct C { // #C + int i; + Immovable j; // #C_j + + operator int() volatile; + }; + C c; + volatile C vc; + + C c1(c); // expected-error {{call to implicitly-deleted copy constructor of 'C'}} + C c2 = c; // expected-error {{call to implicitly-deleted copy constructor of 'C'}} + C c3 {c}; // expected-error {{call to implicitly-deleted copy constructor of 'C'}} + C c4 = {c}; // expected-error {{call to implicitly-deleted copy constructor of 'C'}} + // expected-note@#C_j 4 {{copy constructor of 'C' is implicitly deleted}} + // expected-note@#Imm_copy 4 {{'Immovable' has been explicitly marked deleted here}} + + C c5(vc); + C c6 = vc; // expected-error {{no matching constructor for initialization of 'C'}} + C c7 {vc}; + C c8 = {vc}; // expected-error {{no matching constructor for initialization of 'C'}} + // expected-note@#C 4 {{candidate constructor}} + + C c9(C {}); + C c10 = C(123); + C c11 {C {0, Immovable()}}; + C c12 = {C()}; + + + struct D { // expected-note 6 {{candidate constructor}} + int i; + }; + + struct DD : private D { // expected-note 4 {{declared private here}} + virtual operator int() volatile; + }; + DD dd; + volatile DD vdd; + + D d1(dd); // expected-error {{cannot cast 'const DD' to its private base class 'const D'}} + D d2 = dd; // expected-error {{cannot cast 'const DD' to its private base class 'const D'}} + D d3 {dd}; // expected-error {{cannot cast 'const DD' to its private base class 'const D'}} + D d4 = {dd}; // expected-error {{cannot cast 'const DD' to its private base class 'const D'}} + + D d5(vdd); + D d6 = vdd; // expected-error {{no matching constructor for initialization of 'D'}} + D d7 {vdd}; + D d8 = {vdd}; // expected-error {{no matching constructor for initialization of 'D'}} +} // namespace P0960R3 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits