[clang] p0388 array list initialization overloads

This is the second part of p0388, dealing with overloads of list
initialization to incomplete array types.  It extends the handling
added in D103088 to permit incomplete arrays.  We have to record that
the conversion involved an incomplete array, and so (re-add) a bit flag
into the standard conversion sequence object.  Comparing such
conversion sequences requires knowing (a) the number of array elements
initialized and (b) whether the initialization is of an incomplete array.

This also updates the web page to indicate p0388 is implemented (there
is no feature macro).

Differential Revision: https://reviews.llvm.org/D103908
This commit is contained in:
Nathan Sidwell 2021-05-21 11:34:23 -07:00
parent dcd74716f9
commit 444ec0957c
5 changed files with 170 additions and 39 deletions

View File

@ -535,7 +535,10 @@ class Sema;
};
/// 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 @@ class Sema;
};
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 @@ class Sema;
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() &&

View File

@ -5865,14 +5865,14 @@ void ASTContext::UnwrapSimilarArrayTypes(QualType &T1, QualType &T2,
// FIXME: Consider also unwrapping array of unknown bound and VLA.
if (auto *CAT1 = dyn_cast<ConstantArrayType>(AT1)) {
auto *CAT2 = dyn_cast<ConstantArrayType>(AT2);
if (!(CAT2 && CAT1->getSize() == CAT2->getSize()) &&
!(getLangOpts().CPlusPlus20 && AllowPiMismatch &&
isa<IncompleteArrayType>(AT2)))
if (!((CAT2 && CAT1->getSize() == CAT2->getSize()) ||
(AllowPiMismatch && getLangOpts().CPlusPlus20 &&
isa<IncompleteArrayType>(AT2))))
return;
} else if (isa<IncompleteArrayType>(AT1)) {
if (!isa<IncompleteArrayType>(AT2) &&
!(getLangOpts().CPlusPlus20 && AllowPiMismatch &&
isa<ConstantArrayType>(AT2)))
if (!(isa<IncompleteArrayType>(AT2) ||
(AllowPiMismatch && getLangOpts().CPlusPlus20 &&
isa<ConstantArrayType>(AT2))))
return;
} else {
return;

View File

@ -3820,8 +3820,8 @@ CompareImplicitConversionSequences(Sema &S, SourceLocation Loc,
// 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 @@ CompareImplicitConversionSequences(Sema &S, SourceLocation Loc,
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 @@ TryListConversion(Sema &S, InitListExpr *From, QualType ToType,
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<IncompleteArrayType>(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 @@ TryListConversion(Sema &S, InitListExpr *From, QualType ToType,
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 @@ TryListConversion(Sema &S, InitListExpr *From, QualType ToType,
// 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<ConstantArrayType>(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 @@ TryListConversion(Sema &S, InitListExpr *From, QualType ToType,
// No {} init, fatally bad
Result.setBad(BadConversionSequence::too_few_initializers, From,
ToType);
Result.setInitializerListContainerType(ToType);
Result.setInitializerListContainerType(ContTy, IsUnbounded);
return Result;
}
}
} else {
assert(isa<IncompleteArrayType>(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 @@ TryListConversion(Sema &S, InitListExpr *From, QualType ToType,
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 @@ TryListConversion(Sema &S, InitListExpr *From, QualType ToType,
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;
}

View File

@ -77,3 +77,97 @@ auto *frob2(Mat *(*arp)[1]) {
}
} // 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

View File

@ -1238,7 +1238,7 @@ code. This issue is expected to be rectified soon.
<tr>
<td>Permit conversions to arrays of unknown bound</td>
<td><a href="https://wg21.link/p0388r4">P0388R4</a></td>
<td class="none" align="center">No</td>
<td class="unreleased" align="center">Clang 13</td>
</tr>
<tr>
<td><tt>constinit</tt></td>