[clang] [clang] Avoid doing C++20 aggregate init during copy-initialization (PR #131320)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Mar 16 03:08:37 PDT 2025
https://github.com/offsetof updated https://github.com/llvm/llvm-project/pull/131320
>From 07120d060bf4e5ac447950bfe681deead1ce62ed Mon Sep 17 00:00:00 2001
From: offsetof <offsetof at 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]/3.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..22ebf86e05f7d 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -281,6 +281,7 @@ Bug Fixes in This Version
- Fixed a modules crash where exception specifications were not propagated properly (#GH121245, relanded in #GH129982)
- Fixed a problematic case with recursive deserialization within ``FinishedDeserializing()`` where
``PassInterestingDeclsToConsumer()`` was called before the declarations were safe to be passed. (#GH129982)
+- Fixed C++20 aggregate initialization rules being incorrectly applied in certain contexts (#GH131320)
Bug Fixes to Compiler Builtins
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index 56ec33fe37bf3..e612500df5192 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,
@@ -4847,10 +4900,15 @@ static void TryListInitialization(Sema &S,
if (S.Context.hasSameUnqualifiedType(InitType, DestType) ||
S.IsDerivedFrom(InitList->getBeginLoc(), InitType, DestType)) {
Expr *InitListAsExpr = InitList;
- TryConstructorInitialization(S, Entity, Kind, InitListAsExpr, DestType,
- DestType, Sequence,
- /*InitListSyntax*/false,
- /*IsInitListCopy*/true);
+ InitializationKind SubKind =
+ Kind.getKind() == InitializationKind::IK_DirectList
+ ? InitializationKind::CreateDirect(Kind.getLocation(),
+ InitList->getLBraceLoc(),
+ InitList->getRBraceLoc())
+ : Kind;
+ 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
More information about the cfe-commits
mailing list