diff --git a/clang/include/clang/Sema/Overload.h b/clang/include/clang/Sema/Overload.h --- a/clang/include/clang/Sema/Overload.h +++ b/clang/include/clang/Sema/Overload.h @@ -535,7 +535,10 @@ }; /// ConversionKind - The kind of implicit conversion sequence. - unsigned ConversionKind; + unsigned ConversionKind : 31; + + // Whether the initializer list was of an incomplete array. + unsigned InitializerListOfIncompleteArray : 1; /// When initializing an array or std::initializer_list from an /// initializer-list, this is the array or std::initializer_list type being @@ -573,12 +576,16 @@ }; ImplicitConversionSequence() - : ConversionKind(Uninitialized), InitializerListContainerType() { + : ConversionKind(Uninitialized), + InitializerListOfIncompleteArray(false), + InitializerListContainerType() { Standard.setAsIdentityConversion(); } ImplicitConversionSequence(const ImplicitConversionSequence &Other) : ConversionKind(Other.ConversionKind), + InitializerListOfIncompleteArray( + Other.InitializerListOfIncompleteArray), InitializerListContainerType(Other.InitializerListContainerType) { switch (ConversionKind) { case Uninitialized: break; @@ -680,8 +687,12 @@ bool hasInitializerListContainerType() const { return !InitializerListContainerType.isNull(); } - void setInitializerListContainerType(QualType T) { + void setInitializerListContainerType(QualType T, bool IA) { InitializerListContainerType = T; + InitializerListOfIncompleteArray = IA; + } + bool isInitializerListOfIncompleteArray() const { + return InitializerListOfIncompleteArray; } QualType getInitializerListContainerType() const { assert(hasInitializerListContainerType() && diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -5865,14 +5865,14 @@ // FIXME: Consider also unwrapping array of unknown bound and VLA. if (auto *CAT1 = dyn_cast(AT1)) { auto *CAT2 = dyn_cast(AT2); - if (!(CAT2 && CAT1->getSize() == CAT2->getSize()) && - !(getLangOpts().CPlusPlus20 && AllowPiMismatch && - isa(AT2))) + if (!((CAT2 && CAT1->getSize() == CAT2->getSize()) || + (AllowPiMismatch && getLangOpts().CPlusPlus20 && + isa(AT2)))) return; } else if (isa(AT1)) { - if (!isa(AT2) && - !(getLangOpts().CPlusPlus20 && AllowPiMismatch && - isa(AT2))) + if (!(isa(AT2) || + (AllowPiMismatch && getLangOpts().CPlusPlus20 && + isa(AT2)))) return; } else { return; diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -3820,8 +3820,8 @@ // if not that, // — L1 and L2 convert to arrays of the same element type, and either the // number of elements n_1 initialized by L1 is less than the number of - // elements n_2 initialized by L2, or (unimplemented:C++20) n_1 = n_2 and L2 - // converts to an array of unknown bound and L1 does not, + // elements n_2 initialized by L2, or (C++20) n_1 = n_2 and L2 converts to + // an array of unknown bound and L1 does not, // even if one of the other rules in this paragraph would otherwise apply. if (!ICS1.isBad()) { bool StdInit1 = false, StdInit2 = false; @@ -3840,13 +3840,23 @@ if (auto *CAT1 = S.Context.getAsConstantArrayType( ICS1.getInitializerListContainerType())) if (auto *CAT2 = S.Context.getAsConstantArrayType( - ICS2.getInitializerListContainerType())) + ICS2.getInitializerListContainerType())) { if (S.Context.hasSameUnqualifiedType(CAT1->getElementType(), - CAT2->getElementType()) && - CAT1->getSize() != CAT2->getSize()) - return CAT1->getSize().ult(CAT2->getSize()) - ? ImplicitConversionSequence::Better - : ImplicitConversionSequence::Worse; + CAT2->getElementType())) { + // Both to arrays of the same element type + if (CAT1->getSize() != CAT2->getSize()) + // Different sized, the smaller wins + return CAT1->getSize().ult(CAT2->getSize()) + ? ImplicitConversionSequence::Better + : ImplicitConversionSequence::Worse; + if (ICS1.isInitializerListOfIncompleteArray() != + ICS2.isInitializerListOfIncompleteArray()) + // One is incomplete, it loses + return ICS2.isInitializerListOfIncompleteArray() + ? ImplicitConversionSequence::Better + : ImplicitConversionSequence::Worse; + } + } } if (ICS1.isStandard()) @@ -5004,9 +5014,15 @@ ImplicitConversionSequence Result; Result.setBad(BadConversionSequence::no_conversion, From, ToType); - // We need a complete type for what follows. Incomplete types can never be - // initialized from init lists. - if (!S.isCompleteType(From->getBeginLoc(), ToType)) + // We need a complete type for what follows. With one C++20 exception, + // incomplete types can never be initialized from init lists. + QualType InitTy = ToType; + const ArrayType *AT = S.Context.getAsArrayType(ToType); + if (AT && S.getLangOpts().CPlusPlus20) + if (const auto *IAT = dyn_cast(AT)) + // C++20 allows list initialization of an incomplete array type. + InitTy = IAT->getElementType(); + if (!S.isCompleteType(From->getBeginLoc(), InitTy)) return Result; // Per DR1467: @@ -5030,18 +5046,16 @@ AllowObjCWritebackConversion); } - if (const auto *AT = S.Context.getAsArrayType(ToType)) { - if (S.IsStringInit(From->getInit(0), AT)) { - InitializedEntity Entity = + if (AT && S.IsStringInit(From->getInit(0), AT)) { + InitializedEntity Entity = InitializedEntity::InitializeParameter(S.Context, ToType, /*Consumed=*/false); - if (S.CanPerformCopyInitialization(Entity, From)) { - Result.setStandard(); - Result.Standard.setAsIdentityConversion(); - Result.Standard.setFromType(ToType); - Result.Standard.setAllToTypes(ToType); - return Result; - } + if (S.CanPerformCopyInitialization(Entity, From)) { + Result.setStandard(); + Result.Standard.setAsIdentityConversion(); + Result.Standard.setFromType(ToType); + Result.Standard.setAllToTypes(ToType); + return Result; } } } @@ -5059,22 +5073,21 @@ // default-constructible, and if all the elements of the initializer list // can be implicitly converted to X, the implicit conversion sequence is // the worst conversion necessary to convert an element of the list to X. - QualType InitTy = ToType; - ArrayType const *AT = S.Context.getAsArrayType(ToType); if (AT || S.isStdInitializerList(ToType, &InitTy)) { unsigned e = From->getNumInits(); ImplicitConversionSequence DfltElt; DfltElt.setBad(BadConversionSequence::no_conversion, QualType(), QualType()); + QualType ContTy = ToType; + bool IsUnbounded = false; if (AT) { - // Result has been initialized above as a BadConversionSequence InitTy = AT->getElementType(); if (ConstantArrayType const *CT = dyn_cast(AT)) { if (CT->getSize().ult(e)) { // Too many inits, fatally bad Result.setBad(BadConversionSequence::too_many_initializers, From, ToType); - Result.setInitializerListContainerType(ToType); + Result.setInitializerListContainerType(ContTy, IsUnbounded); return Result; } if (CT->getSize().ugt(e)) { @@ -5089,10 +5102,23 @@ // No {} init, fatally bad Result.setBad(BadConversionSequence::too_few_initializers, From, ToType); - Result.setInitializerListContainerType(ToType); + Result.setInitializerListContainerType(ContTy, IsUnbounded); return Result; } } + } else { + assert(isa(AT) && "Expected incomplete array"); + IsUnbounded = true; + if (!e) { + // Cannot convert to zero-sized. + Result.setBad(BadConversionSequence::too_few_initializers, From, + ToType); + Result.setInitializerListContainerType(ContTy, IsUnbounded); + return Result; + } + llvm::APInt Size(S.Context.getTypeSize(S.Context.getSizeType()), e); + ContTy = S.Context.getConstantArrayType(InitTy, Size, nullptr, + ArrayType::Normal, 0); } } @@ -5115,7 +5141,7 @@ Result = ICS; // Bail as soon as we find something unconvertible. if (Result.isBad()) { - Result.setInitializerListContainerType(ToType); + Result.setInitializerListContainerType(ContTy, IsUnbounded); return Result; } } @@ -5128,8 +5154,8 @@ S, From->getEndLoc(), DfltElt, Result) == ImplicitConversionSequence::Worse) Result = DfltElt; - - Result.setInitializerListContainerType(ToType); + // Record the type being initialized so that we may compare sequences + Result.setInitializerListContainerType(ContTy, IsUnbounded); return Result; } diff --git a/clang/test/SemaCXX/cxx20-p0388-unbound-ary.cpp b/clang/test/SemaCXX/cxx20-p0388-unbound-ary.cpp --- a/clang/test/SemaCXX/cxx20-p0388-unbound-ary.cpp +++ b/clang/test/SemaCXX/cxx20-p0388-unbound-ary.cpp @@ -77,3 +77,97 @@ } } // namespace Four + +namespace Five { +// from the paper +char (&b(int(&&)[]))[1]; // #1 +char (&b(long(&&)[]))[2]; // #2 +char (&b(int(&&)[1]))[3]; // #3 +char (&b(long(&&)[1]))[4]; // #4 +char (&b(int(&&)[2]))[5]; // #5 +#if __cplusplus < 202002 + // expected-note@-6{{cannot convert initializer}} + // expected-note@-6{{cannot convert initializer}} + // expected-note@-6{{too many initializers}} + // expected-note@-6{{too many initializers}} + // expected-note@-6{{too many initializers}} +#endif + +void f() { + static_assert(sizeof(b({1})) == 3); + static_assert(sizeof(b({1, 2})) == 5); + static_assert(sizeof(b({1, 2, 3})) == 1); +#if __cplusplus < 202002 + // expected-error@-2{{no matching function}} +#endif +} +} // namespace Five + +#if __cplusplus >= 202002 +namespace Six { +// from over.ics.rank 3.1 +char (&f(int(&&)[]))[1]; // #1 +char (&f(double(&&)[]))[2]; // #2 +char (&f(int(&&)[2]))[3]; // #3 + +void toto() { + // Calls #1: Better than #2 due to conversion, better than #3 due to bounds + static_assert(sizeof(f({1})) == 1); + + // Calls #2: Identity conversion is better than floating-integral conversion + static_assert(sizeof(f({1.0})) == 2); + + // Calls #2: Identity conversion is better than floating-integral conversion + static_assert(sizeof(f({1.0, 2.0})) == 2); + + // Calls #3: Converting to array of known bound is better than to unknown + // bound, and an identity conversion is better than + // floating-integral conversion + static_assert(sizeof(f({1, 2})) == 3); +} + +} // namespace Six + +namespace Seven { + +char (&f(int(&&)[]))[1]; // #1 +char (&f(double(&&)[1]))[2]; // #2 + +void quux() { + // Calls #2, float-integral conversion rather than create zero-sized array + static_assert(sizeof(f({})) == 2); +} + +} // namespace Seven + +namespace Eight { + +// brace-elision is not a thing here: +struct A { + int x, y; +}; + +char (&f1(int(&&)[]))[1]; // #1 +char (&f1(A(&&)[]))[2]; // #2 + +void g1() { + // pick #1, even though that is more elements than #2 + // 6 ints, as opposed to 3 As + static_assert(sizeof(f1({1, 2, 3, 4, 5, 6})) == 1); +} + +void f2(A(&&)[]); // expected-note{{candidate function not viable}} +void g2() { + f2({1, 2, 3, 4, 5, 6}); // expected-error{{no matching function}} +} + +void f3(A(&&)[]); +void g3() { + auto &f = f3; + + f({1, 2, 3, 4, 5, 6}); // OK! We're coercing to an already-selected function +} + +} // namespace Eight + +#endif diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -1238,7 +1238,7 @@ Permit conversions to arrays of unknown bound P0388R4 - No + Clang 13 constinit