[clang] p0388 conversion to incomplete array

This implements the new implicit conversion sequence to an incomplete
(unbounded) array type.  It is mostly Richard Smith's work, updated to
trunk, testcases added and a few bugs fixed found in such testing.

It is not a complete implementation of p0388.

Differential Revision: https://reviews.llvm.org/D102645
This commit is contained in:
Nathan Sidwell 2021-05-20 13:31:31 -07:00
parent a76cfc2e84
commit dcd74716f9
11 changed files with 261 additions and 89 deletions

View File

@ -2535,8 +2535,10 @@ public:
bool ObjCMethodsAreEqual(const ObjCMethodDecl *MethodDecl,
const ObjCMethodDecl *MethodImp);
bool UnwrapSimilarTypes(QualType &T1, QualType &T2);
void UnwrapSimilarArrayTypes(QualType &T1, QualType &T2);
bool UnwrapSimilarTypes(QualType &T1, QualType &T2,
bool AllowPiMismatch = true);
void UnwrapSimilarArrayTypes(QualType &T1, QualType &T2,
bool AllowPiMismatch = true);
/// Determine if two types are similar, according to the C++ rules. That is,
/// determine if they are the same other than qualifiers on the initial

View File

@ -5844,7 +5844,11 @@ QualType ASTContext::getUnqualifiedArrayType(QualType type,
/// Attempt to unwrap two types that may both be array types with the same bound
/// (or both be array types of unknown bound) for the purpose of comparing the
/// cv-decomposition of two types per C++ [conv.qual].
void ASTContext::UnwrapSimilarArrayTypes(QualType &T1, QualType &T2) {
///
/// \param AllowPiMismatch Allow the Pi1 and Pi2 to differ as described in
/// C++20 [conv.qual], if permitted by the current language mode.
void ASTContext::UnwrapSimilarArrayTypes(QualType &T1, QualType &T2,
bool AllowPiMismatch) {
while (true) {
auto *AT1 = getAsArrayType(T1);
if (!AT1)
@ -5856,12 +5860,21 @@ void ASTContext::UnwrapSimilarArrayTypes(QualType &T1, QualType &T2) {
// If we don't have two array types with the same constant bound nor two
// incomplete array types, we've unwrapped everything we can.
// C++20 also permits one type to be a constant array type and the other
// to be an incomplete array type.
// 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())
if (!(CAT2 && CAT1->getSize() == CAT2->getSize()) &&
!(getLangOpts().CPlusPlus20 && AllowPiMismatch &&
isa<IncompleteArrayType>(AT2)))
return;
} else if (!isa<IncompleteArrayType>(AT1) ||
!isa<IncompleteArrayType>(AT2)) {
} else if (isa<IncompleteArrayType>(AT1)) {
if (!isa<IncompleteArrayType>(AT2) &&
!(getLangOpts().CPlusPlus20 && AllowPiMismatch &&
isa<ConstantArrayType>(AT2)))
return;
} else {
return;
}
@ -5880,10 +5893,14 @@ void ASTContext::UnwrapSimilarArrayTypes(QualType &T1, QualType &T2) {
/// "unwraps" pointer and pointer-to-member types to compare them at each
/// level.
///
/// \param AllowPiMismatch Allow the Pi1 and Pi2 to differ as described in
/// C++20 [conv.qual], if permitted by the current language mode.
///
/// \return \c true if a pointer type was unwrapped, \c false if we reached a
/// pair of types that can't be unwrapped further.
bool ASTContext::UnwrapSimilarTypes(QualType &T1, QualType &T2) {
UnwrapSimilarArrayTypes(T1, T2);
bool ASTContext::UnwrapSimilarTypes(QualType &T1, QualType &T2,
bool AllowPiMismatch) {
UnwrapSimilarArrayTypes(T1, T2, AllowPiMismatch);
const auto *T1PtrType = T1->getAs<PointerType>();
const auto *T2PtrType = T2->getAs<PointerType>();
@ -5944,7 +5961,7 @@ bool ASTContext::hasCvrSimilarType(QualType T1, QualType T2) {
if (hasSameType(T1, T2))
return true;
if (!UnwrapSimilarTypes(T1, T2))
if (!UnwrapSimilarTypes(T1, T2, /*AllowPiMismatch*/ false))
return false;
}
}

View File

@ -4683,10 +4683,28 @@ LValue CodeGenFunction::EmitCastLValue(const CastExpr *E) {
case CK_UserDefinedConversion:
case CK_CPointerToObjCPointerCast:
case CK_BlockPointerToObjCPointerCast:
case CK_NoOp:
case CK_LValueToRValue:
return EmitLValue(E->getSubExpr());
case CK_NoOp: {
// CK_NoOp can model a qualification conversion, which can remove an array
// bound and change the IR type.
// FIXME: Once pointee types are removed from IR, remove this.
LValue LV = EmitLValue(E->getSubExpr());
if (LV.isSimple()) {
Address V = LV.getAddress(*this);
if (V.isValid()) {
llvm::Type *T =
ConvertTypeForMem(E->getType())
->getPointerTo(
cast<llvm::PointerType>(V.getType())->getAddressSpace());
if (V.getType() != T)
LV.setAddress(Builder.CreateBitCast(V, T));
}
}
return LV;
}
case CK_UncheckedDerivedToBase:
case CK_DerivedToBase: {
const auto *DerivedClassTy =

View File

@ -2154,10 +2154,22 @@ Value *ScalarExprEmitter::VisitCastExpr(CastExpr *CE) {
}
case CK_AtomicToNonAtomic:
case CK_NonAtomicToAtomic:
case CK_NoOp:
case CK_UserDefinedConversion:
return Visit(const_cast<Expr*>(E));
case CK_NoOp: {
llvm::Value *V = Visit(const_cast<Expr *>(E));
if (V) {
// CK_NoOp can model a pointer qualification conversion, which can remove
// an array bound and change the IR type.
// FIXME: Once pointee types are removed from IR, remove this.
llvm::Type *T = ConvertType(DestTy);
if (T != V->getType())
V = Builder.CreateBitCast(V, T);
}
return V;
}
case CK_BaseToDerived: {
const CXXRecordDecl *DerivedClassDecl = DestTy->getPointeeCXXRecordDecl();
assert(DerivedClassDecl && "BaseToDerived arg isn't a C++ object pointer!");

View File

@ -1555,6 +1555,12 @@ CodeGenFunction::generateObjCSetterBody(const ObjCImplementationDecl *classImpl,
argCK = CK_AnyPointerToBlockPointerCast;
} else if (ivarRef.getType()->isPointerType()) {
argCK = CK_BitCast;
} else if (argLoad.getType()->isAtomicType() &&
!ivarRef.getType()->isAtomicType()) {
argCK = CK_AtomicToNonAtomic;
} else if (!argLoad.getType()->isAtomicType() &&
ivarRef.getType()->isAtomicType()) {
argCK = CK_NonAtomicToAtomic;
}
ImplicitCastExpr argCast(ImplicitCastExpr::OnStack, ivarRef.getType(), argCK,
&argLoad, VK_PRValue, FPOptionsOverride());

View File

@ -1313,7 +1313,9 @@ static TryCastResult TryStaticCast(Sema &Self, ExprResult &SrcExpr,
// lvalue-to-rvalue, array-to-pointer, function-to-pointer, and boolean
// conversions, subject to further restrictions.
// Also, C++ 5.2.9p1 forbids casting away constness, which makes reversal
// of qualification conversions impossible.
// of qualification conversions impossible. (In C++20, adding an array bound
// would be the reverse of a qualification conversion, but adding permission
// to add an array bound in a static_cast is a wording oversight.)
// In the CStyle case, the earlier attempt to const_cast should have taken
// care of reverse qualification conversions.

View File

@ -6711,6 +6711,36 @@ QualType Sema::FindCompositePointerType(SourceLocation Loc,
}
// FIXME: Can we unify the following with UnwrapSimilarTypes?
const ArrayType *Arr1, *Arr2;
if ((Arr1 = Context.getAsArrayType(Composite1)) &&
(Arr2 = Context.getAsArrayType(Composite2))) {
auto *CAT1 = dyn_cast<ConstantArrayType>(Arr1);
auto *CAT2 = dyn_cast<ConstantArrayType>(Arr2);
if (CAT1 && CAT2 && CAT1->getSize() == CAT2->getSize()) {
Composite1 = Arr1->getElementType();
Composite2 = Arr2->getElementType();
Steps.emplace_back(Step::Array, CAT1);
continue;
}
bool IAT1 = isa<IncompleteArrayType>(Arr1);
bool IAT2 = isa<IncompleteArrayType>(Arr2);
if ((IAT1 && IAT2) ||
(getLangOpts().CPlusPlus20 && (IAT1 != IAT2) &&
((bool)CAT1 != (bool)CAT2) &&
(Steps.empty() || Steps.back().K != Step::Array))) {
// In C++20 onwards, we can unify an array of N T with an array of
// a different or unknown bound. But we can't form an array whose
// element type is an array of unknown bound by doing so.
Composite1 = Arr1->getElementType();
Composite2 = Arr2->getElementType();
Steps.emplace_back(Step::Array);
if (CAT1 || CAT2)
NeedConstBefore = Steps.size();
continue;
}
}
const PointerType *Ptr1, *Ptr2;
if ((Ptr1 = Composite1->getAs<PointerType>()) &&
(Ptr2 = Composite2->getAs<PointerType>())) {
@ -6771,8 +6801,6 @@ QualType Sema::FindCompositePointerType(SourceLocation Loc,
continue;
}
// FIXME: arrays
// FIXME: block pointer types?
// Cannot unwrap any more types.

View File

@ -3246,6 +3246,19 @@ static bool isQualificationConversionStep(QualType FromType, QualType ToType,
!PreviousToQualsIncludeConst)
return false;
// The following wording is from C++20, where the result of the conversion
// is T3, not T2.
// -- if [...] P1,i [...] is "array of unknown bound of", P3,i is
// "array of unknown bound of"
if (FromType->isIncompleteArrayType() && !ToType->isIncompleteArrayType())
return false;
// -- if the resulting P3,i is different from P1,i [...], then const is
// added to every cv 3_k for 0 < k < i.
if (!CStyle && FromType->isConstantArrayType() &&
ToType->isIncompleteArrayType() && !PreviousToQualsIncludeConst)
return false;
// Keep track of whether all prior cv-qualifiers in the "to" type
// include const.
PreviousToQualsIncludeConst =
@ -4199,12 +4212,15 @@ static ImplicitConversionSequence::CompareKind
CompareQualificationConversions(Sema &S,
const StandardConversionSequence& SCS1,
const StandardConversionSequence& SCS2) {
// C++ 13.3.3.2p3:
// C++ [over.ics.rank]p3:
// -- S1 and S2 differ only in their qualification conversion and
// yield similar types T1 and T2 (C++ 4.4), respectively, and the
// cv-qualification signature of type T1 is a proper subset of
// the cv-qualification signature of type T2, and S1 is not the
// yield similar types T1 and T2 (C++ 4.4), respectively, [...]
// [C++98]
// [...] and the cv-qualification signature of type T1 is a proper subset
// of the cv-qualification signature of type T2, and S1 is not the
// deprecated string literal array-to-pointer conversion (4.2).
// [C++2a]
// [...] where T1 can be converted to T2 by a qualification conversion.
if (SCS1.First != SCS2.First || SCS1.Second != SCS2.Second ||
SCS1.Third != SCS2.Third || SCS1.Third != ICK_Qualification)
return ImplicitConversionSequence::Indistinguishable;
@ -4225,79 +4241,35 @@ CompareQualificationConversions(Sema &S,
if (UnqualT1 == UnqualT2)
return ImplicitConversionSequence::Indistinguishable;
ImplicitConversionSequence::CompareKind Result
= ImplicitConversionSequence::Indistinguishable;
// Don't ever prefer a standard conversion sequence that uses the deprecated
// string literal array to pointer conversion.
bool CanPick1 = !SCS1.DeprecatedStringLiteralToCharPtr;
bool CanPick2 = !SCS2.DeprecatedStringLiteralToCharPtr;
// Objective-C++ ARC:
// Prefer qualification conversions not involving a change in lifetime
// to qualification conversions that do not change lifetime.
if (SCS1.QualificationIncludesObjCLifetime !=
SCS2.QualificationIncludesObjCLifetime) {
Result = SCS1.QualificationIncludesObjCLifetime
? ImplicitConversionSequence::Worse
: ImplicitConversionSequence::Better;
}
// to qualification conversions that do change lifetime.
if (SCS1.QualificationIncludesObjCLifetime &&
!SCS2.QualificationIncludesObjCLifetime)
CanPick1 = false;
if (SCS2.QualificationIncludesObjCLifetime &&
!SCS1.QualificationIncludesObjCLifetime)
CanPick2 = false;
while (S.Context.UnwrapSimilarTypes(T1, T2)) {
// Within each iteration of the loop, we check the qualifiers to
// determine if this still looks like a qualification
// conversion. Then, if all is well, we unwrap one more level of
// pointers or pointers-to-members and do it all again
// until there are no more pointers or pointers-to-members left
// to unwrap. This essentially mimics what
// IsQualificationConversion does, but here we're checking for a
// strict subset of qualifiers.
if (T1.getQualifiers().withoutObjCLifetime() ==
T2.getQualifiers().withoutObjCLifetime())
// The qualifiers are the same, so this doesn't tell us anything
// about how the sequences rank.
// ObjC ownership quals are omitted above as they interfere with
// the ARC overload rule.
;
else if (T2.isMoreQualifiedThan(T1)) {
// T1 has fewer qualifiers, so it could be the better sequence.
if (Result == ImplicitConversionSequence::Worse)
// Neither has qualifiers that are a subset of the other's
// qualifiers.
return ImplicitConversionSequence::Indistinguishable;
bool ObjCLifetimeConversion;
if (CanPick1 &&
!S.IsQualificationConversion(T1, T2, false, ObjCLifetimeConversion))
CanPick1 = false;
// FIXME: In Objective-C ARC, we can have qualification conversions in both
// directions, so we can't short-cut this second check in general.
if (CanPick2 &&
!S.IsQualificationConversion(T2, T1, false, ObjCLifetimeConversion))
CanPick2 = false;
Result = ImplicitConversionSequence::Better;
} else if (T1.isMoreQualifiedThan(T2)) {
// T2 has fewer qualifiers, so it could be the better sequence.
if (Result == ImplicitConversionSequence::Better)
// Neither has qualifiers that are a subset of the other's
// qualifiers.
return ImplicitConversionSequence::Indistinguishable;
Result = ImplicitConversionSequence::Worse;
} else {
// Qualifiers are disjoint.
return ImplicitConversionSequence::Indistinguishable;
}
// If the types after this point are equivalent, we're done.
if (S.Context.hasSameUnqualifiedType(T1, T2))
break;
}
// Check that the winning standard conversion sequence isn't using
// the deprecated string literal array to pointer conversion.
switch (Result) {
case ImplicitConversionSequence::Better:
if (SCS1.DeprecatedStringLiteralToCharPtr)
Result = ImplicitConversionSequence::Indistinguishable;
break;
case ImplicitConversionSequence::Indistinguishable:
break;
case ImplicitConversionSequence::Worse:
if (SCS2.DeprecatedStringLiteralToCharPtr)
Result = ImplicitConversionSequence::Indistinguishable;
break;
}
return Result;
if (CanPick1 != CanPick2)
return CanPick1 ? ImplicitConversionSequence::Better
: ImplicitConversionSequence::Worse;
return ImplicitConversionSequence::Indistinguishable;
}
/// CompareDerivedToBaseConversions - Compares two standard conversion

View File

@ -373,10 +373,19 @@ namespace dr330 { // dr330: 7
q = p; // ok
q2 = p; // ok
r = p; // expected-error {{incompatible}}
s = p; // expected-error {{incompatible}} (for now)
s = p;
#if __cplusplus < 202002
// expected-error@-2 {{incompatible}} (fixed by p0388)
#endif
t = p; // expected-error {{incompatible}}
s = q; // expected-error {{incompatible}}
s = q2; // expected-error {{incompatible}}
s = q;
#if __cplusplus < 202002
// expected-error@-2 {{incompatible}} (fixed by p0388)
#endif
s = q2;
#if __cplusplus < 202002
// expected-error@-2 {{incompatible}} (fixed by p0388)
#endif
s = t; // ok, adding const
t = s; // expected-error {{discards qualifiers}}
(void) const_cast<P>(q);

View File

@ -0,0 +1,27 @@
// RUN: %clang_cc1 %s -triple %itanium_abi_triple -std=c++20 -emit-llvm -O2 -o - | FileCheck %s
// p0388 conversions to unbounded array
// dcl.init.list/3
namespace One {
int ga[1];
// CHECK-LABEL: @_ZN3One5frob1Ev
// CHECK-NEXT: entry:
// CHECK-NEXT: ret [0 x i32]* bitcast ([1 x i32]* @_ZN3One2gaE to [0 x i32]*)
auto &frob1() {
int(&r1)[] = ga;
return r1;
}
// CHECK-LABEL: @_ZN3One5frob2ERA1_i
// CHECK-NEXT: entry:
// CHECK-NEXT: %0 = bitcast [1 x i32]* %arp to [0 x i32]*
// CHECK-NEXT: ret [0 x i32]* %0
auto &frob2(int (&arp)[1]) {
int(&r2)[] = arp;
return r2;
}
} // namespace One

View File

@ -0,0 +1,79 @@
// RUN: %clang_cc1 -std=c++20 -verify %s
// RUN: %clang_cc1 -std=c++17 -verify %s
// p0388 conversions to unbounded array
// dcl.init.list/3
namespace One {
int ga[1];
auto &frob1() {
int(&r1)[] = ga;
#if __cplusplus < 202002
// expected-error@-2{{cannot bind to a value of unrelated type}}
#endif
return r1;
}
auto &frob2(int (&arp)[1]) {
int(&r2)[] = arp;
#if __cplusplus < 202002
// expected-error@-2{{cannot bind to a value of unrelated type}}
#endif
return r2;
}
} // namespace One
namespace Two {
int ga[1];
auto *frob1() {
int(*r1)[] = &ga;
#if __cplusplus < 202002
// expected-error@-2{{with an rvalue of type}}
#endif
return r1;
}
auto *frob2(int (*arp)[1]) {
int(*r2)[] = arp;
#if __cplusplus < 202002
// expected-error@-2{{with an lvalue of type}}
#endif
return r2;
}
} // namespace Two
namespace Four {
using Inc = int[2];
using Mat = Inc[1];
Mat *ga[2];
auto *frob1() {
Inc(*const(*r1)[])[] = &ga;
#if __cplusplus < 202002
// expected-error@-2{{with an rvalue of type}}
#else
// missing a required 'const'
Inc(*(*r2)[])[] = &ga; // expected-error{{cannot initialize}}
#endif
return r1;
}
auto *frob2(Mat *(*arp)[1]) {
Inc(*const(*r2)[])[] = arp;
#if __cplusplus < 202002
// expected-error@-2{{with an lvalue of type}}
#else
Inc(*(*r3)[])[] = arp; // expected-error{{cannot initialize}}
#endif
return r2;
}
} // namespace Four