Clean up and slightly generalize implementation of composite pointer

type computation, in preparation for P0388R4, which adds another few
cases here.

We now properly handle forming multi-level composite pointer types
involving nested Objective-C pointer types (as is consistent with
including them as part of the notion of 'similar types' on which this
rule is based). We no longer lose non-CVR qualifiers on nested pointer
types.
This commit is contained in:
Richard Smith
2020-01-09 19:24:44 -08:00
parent fbf915f01d
commit 9a6f4d451c
3 changed files with 244 additions and 125 deletions

View File

@@ -6120,10 +6120,10 @@ mergeExceptionSpecs(Sema &S, FunctionProtoType::ExceptionSpecInfo ESI1,
/// Find a merged pointer type and convert the two expressions to it.
///
/// This finds the composite pointer type (or member pointer type) for @p E1
/// and @p E2 according to C++1z 5p14. It converts both expressions to this
/// type and returns it.
/// It does not emit diagnostics.
/// This finds the composite pointer type for \p E1 and \p E2 according to
/// C++2a [expr.type]p3. It converts both expressions to this type and returns
/// it. It does not emit diagnostics (FIXME: that's not true if \p ConvertArgs
/// is \c true).
///
/// \param Loc The location of the operator requiring these two expressions to
/// be converted to the composite pointer type.
@@ -6176,60 +6176,117 @@ QualType Sema::FindCompositePointerType(SourceLocation Loc,
assert(!T1->isNullPtrType() && !T2->isNullPtrType() &&
"nullptr_t should be a null pointer constant");
// - if T1 or T2 is "pointer to cv1 void" and the other type is
// "pointer to cv2 T", "pointer to cv12 void", where cv12 is
// the union of cv1 and cv2;
// - if T1 or T2 is "pointer to noexcept function" and the other type is
// "pointer to function", where the function types are otherwise the same,
// "pointer to function";
// FIXME: This rule is defective: it should also permit removing noexcept
// from a pointer to member function. As a Clang extension, we also
// permit removing 'noreturn', so we generalize this rule to;
// - [Clang] If T1 and T2 are both of type "pointer to function" or
// "pointer to member function" and the pointee types can be unified
// by a function pointer conversion, that conversion is applied
// before checking the following rules.
struct Step {
enum Kind { Pointer, ObjCPointer, MemberPointer, Array } K;
// Qualifiers to apply under the step kind.
Qualifiers Quals;
/// The class for a pointer-to-member; a constant array type with a bound
/// (if any) for an array.
const Type *ClassOrBound;
Step(Kind K, const Type *ClassOrBound = nullptr)
: K(K), Quals(), ClassOrBound(ClassOrBound) {}
QualType rebuild(ASTContext &Ctx, QualType T) const {
T = Ctx.getQualifiedType(T, Quals);
switch (K) {
case Pointer:
return Ctx.getPointerType(T);
case MemberPointer:
return Ctx.getMemberPointerType(T, ClassOrBound);
case ObjCPointer:
return Ctx.getObjCObjectPointerType(T);
case Array:
if (auto *CAT = cast_or_null<ConstantArrayType>(ClassOrBound))
return Ctx.getConstantArrayType(T, CAT->getSize(), nullptr,
ArrayType::Normal, 0);
else
return Ctx.getIncompleteArrayType(T, ArrayType::Normal, 0);
}
llvm_unreachable("unknown step kind");
}
};
SmallVector<Step, 8> Steps;
// - if T1 is "pointer to cv1 C1" and T2 is "pointer to cv2 C2", where C1
// is reference-related to C2 or C2 is reference-related to C1 (8.6.3),
// the cv-combined type of T1 and T2 or the cv-combined type of T2 and T1,
// respectively;
// - if T1 is "pointer to member of C1 of type cv1 U1" and T2 is "pointer
// to member of C2 of type cv2 U2" where C1 is reference-related to C2 or
// C2 is reference-related to C1 (8.6.3), the cv-combined type of T2 and
// T1 or the cv-combined type of T1 and T2, respectively;
// to member of C2 of type cv2 U2" for some non-function type U, where
// C1 is reference-related to C2 or C2 is reference-related to C1, the
// cv-combined type of T2 and T1 or the cv-combined type of T1 and T2,
// respectively;
// - if T1 and T2 are similar types (4.5), the cv-combined type of T1 and
// T2;
//
// If looked at in the right way, these bullets all do the same thing.
// What we do here is, we build the two possible cv-combined types, and try
// the conversions in both directions. If only one works, or if the two
// composite types are the same, we have succeeded.
// FIXME: extended qualifiers?
//
// Note that this will fail to find a composite pointer type for "pointer
// to void" and "pointer to function". We can't actually perform the final
// conversion in this case, even though a composite pointer type formally
// exists.
SmallVector<unsigned, 4> QualifierUnion;
SmallVector<std::pair<const Type *, const Type *>, 4> MemberOfClass;
// Dismantle T1 and T2 to simultaneously determine whether they are similar
// and to prepare to form the cv-combined type if so.
QualType Composite1 = T1;
QualType Composite2 = T2;
unsigned NeedConstBefore = 0;
while (true) {
assert(!Composite1.isNull() && !Composite2.isNull());
Qualifiers Q1, Q2;
Composite1 = Context.getUnqualifiedArrayType(Composite1, Q1);
Composite2 = Context.getUnqualifiedArrayType(Composite2, Q2);
// Top-level qualifiers are ignored. Merge at all lower levels.
if (!Steps.empty()) {
// Find the qualifier union: (approximately) the unique minimal set of
// qualifiers that is compatible with both types.
Qualifiers Quals = Qualifiers::fromCVRUMask(Q1.getCVRUQualifiers() |
Q2.getCVRUQualifiers());
// Under one level of pointer or pointer-to-member, we can change to an
// unambiguous compatible address space.
if (Q1.getAddressSpace() == Q2.getAddressSpace()) {
Quals.setAddressSpace(Q1.getAddressSpace());
} else if (Steps.size() == 1) {
bool MaybeQ1 = Q1.isAddressSpaceSupersetOf(Q2);
bool MaybeQ2 = Q2.isAddressSpaceSupersetOf(Q1);
if (MaybeQ1 == MaybeQ2)
return QualType(); // No unique best address space.
Quals.setAddressSpace(MaybeQ1 ? Q1.getAddressSpace()
: Q2.getAddressSpace());
} else {
return QualType();
}
// FIXME: In C, we merge __strong and none to __strong at the top level.
if (Q1.getObjCGCAttr() == Q2.getObjCGCAttr())
Quals.setObjCGCAttr(Q1.getObjCGCAttr());
else
return QualType();
// Mismatched lifetime qualifiers never compatibly include each other.
if (Q1.getObjCLifetime() == Q2.getObjCLifetime())
Quals.setObjCLifetime(Q1.getObjCLifetime());
else
return QualType();
Steps.back().Quals = Quals;
if (Q1 != Quals || Q2 != Quals)
NeedConstBefore = Steps.size() - 1;
}
// FIXME: Can we unify the following with UnwrapSimilarTypes?
const PointerType *Ptr1, *Ptr2;
if ((Ptr1 = Composite1->getAs<PointerType>()) &&
(Ptr2 = Composite2->getAs<PointerType>())) {
Composite1 = Ptr1->getPointeeType();
Composite2 = Ptr2->getPointeeType();
Steps.emplace_back(Step::Pointer);
continue;
}
// If we're allowed to create a non-standard composite type, keep track
// of where we need to fill in additional 'const' qualifiers.
if (Composite1.getCVRQualifiers() != Composite2.getCVRQualifiers())
NeedConstBefore = QualifierUnion.size();
QualifierUnion.push_back(
Composite1.getCVRQualifiers() | Composite2.getCVRQualifiers());
MemberOfClass.push_back(std::make_pair(nullptr, nullptr));
const ObjCObjectPointerType *ObjPtr1, *ObjPtr2;
if ((ObjPtr1 = Composite1->getAs<ObjCObjectPointerType>()) &&
(ObjPtr2 = Composite2->getAs<ObjCObjectPointerType>())) {
Composite1 = ObjPtr1->getPointeeType();
Composite2 = ObjPtr2->getPointeeType();
Steps.emplace_back(Step::ObjCPointer);
continue;
}
@@ -6239,34 +6296,79 @@ QualType Sema::FindCompositePointerType(SourceLocation Loc,
Composite1 = MemPtr1->getPointeeType();
Composite2 = MemPtr2->getPointeeType();
// If we're allowed to create a non-standard composite type, keep track
// of where we need to fill in additional 'const' qualifiers.
if (Composite1.getCVRQualifiers() != Composite2.getCVRQualifiers())
NeedConstBefore = QualifierUnion.size();
// At the top level, we can perform a base-to-derived pointer-to-member
// conversion:
//
// - [...] where C1 is reference-related to C2 or C2 is
// reference-related to C1
//
// (Note that the only kinds of reference-relatedness in scope here are
// "same type or derived from".) At any other level, the class must
// exactly match.
const Type *Class = nullptr;
QualType Cls1(MemPtr1->getClass(), 0);
QualType Cls2(MemPtr2->getClass(), 0);
if (Context.hasSameType(Cls1, Cls2))
Class = MemPtr1->getClass();
else if (Steps.empty())
Class = IsDerivedFrom(Loc, Cls1, Cls2) ? MemPtr1->getClass() :
IsDerivedFrom(Loc, Cls2, Cls1) ? MemPtr2->getClass() : nullptr;
if (!Class)
return QualType();
QualifierUnion.push_back(
Composite1.getCVRQualifiers() | Composite2.getCVRQualifiers());
MemberOfClass.push_back(std::make_pair(MemPtr1->getClass(),
MemPtr2->getClass()));
Steps.emplace_back(Step::MemberPointer, Class);
continue;
}
// Special case: at the top level, we can decompose an Objective-C pointer
// and a 'cv void *'. Unify the qualifiers.
if (Steps.empty() && ((Composite1->isVoidPointerType() &&
Composite2->isObjCObjectPointerType()) ||
(Composite1->isObjCObjectPointerType() &&
Composite2->isVoidPointerType()))) {
Composite1 = Composite1->getPointeeType();
Composite2 = Composite2->getPointeeType();
Steps.emplace_back(Step::Pointer);
continue;
}
// FIXME: arrays
// FIXME: block pointer types?
// Cannot unwrap any more types.
break;
}
// Apply the function pointer conversion to unify the types. We've already
// unwrapped down to the function types, and we want to merge rather than
// just convert, so do this ourselves rather than calling
// - if T1 or T2 is "pointer to noexcept function" and the other type is
// "pointer to function", where the function types are otherwise the same,
// "pointer to function";
// - if T1 or T2 is "pointer to member of C1 of type function", the other
// type is "pointer to member of C2 of type noexcept function", and C1
// is reference-related to C2 or C2 is reference-related to C1, where
// the function types are otherwise the same, "pointer to member of C2 of
// type function" or "pointer to member of C1 of type function",
// respectively;
//
// We also support 'noreturn' here, so as a Clang extension we generalize the
// above to:
//
// - [Clang] If T1 and T2 are both of type "pointer to function" or
// "pointer to member function" and the pointee types can be unified
// by a function pointer conversion, that conversion is applied
// before checking the following rules.
//
// We've already unwrapped down to the function types, and we want to merge
// rather than just convert, so do this ourselves rather than calling
// IsFunctionConversion.
//
// FIXME: In order to match the standard wording as closely as possible, we
// currently only do this under a single level of pointers. Ideally, we would
// allow this in general, and set NeedConstBefore to the relevant depth on
// the side(s) where we changed anything.
if (QualifierUnion.size() == 1) {
// the side(s) where we changed anything. If we permit that, we should also
// consider this conversion when determining type similarity and model it as
// a qualification conversion.
if (Steps.size() == 1) {
if (auto *FPT1 = Composite1->getAs<FunctionProtoType>()) {
if (auto *FPT2 = Composite2->getAs<FunctionProtoType>()) {
FunctionProtoType::ExtProtoInfo EPI1 = FPT1->getExtProtoInfo();
@@ -6292,88 +6394,72 @@ QualType Sema::FindCompositePointerType(SourceLocation Loc,
}
}
if (NeedConstBefore) {
// Extension: Add 'const' to qualifiers that come before the first qualifier
// mismatch, so that our (non-standard!) composite type meets the
// requirements of C++ [conv.qual]p4 bullet 3.
for (unsigned I = 0; I != NeedConstBefore; ++I)
if ((QualifierUnion[I] & Qualifiers::Const) == 0)
QualifierUnion[I] = QualifierUnion[I] | Qualifiers::Const;
// There are some more conversions we can perform under exactly one pointer.
if (Steps.size() == 1 && Steps.front().K == Step::Pointer &&
!Context.hasSameType(Composite1, Composite2)) {
// - if T1 or T2 is "pointer to cv1 void" and the other type is
// "pointer to cv2 T", where T is an object type or void,
// "pointer to cv12 void", where cv12 is the union of cv1 and cv2;
if (Composite1->isVoidType() && Composite2->isObjectType())
Composite2 = Composite1;
else if (Composite2->isVoidType() && Composite1->isObjectType())
Composite1 = Composite2;
// - if T1 is "pointer to cv1 C1" and T2 is "pointer to cv2 C2", where C1
// is reference-related to C2 or C2 is reference-related to C1 (8.6.3),
// the cv-combined type of T1 and T2 or the cv-combined type of T2 and
// T1, respectively;
//
// The "similar type" handling covers all of this except for the "T1 is a
// base class of T2" case in the definition of reference-related.
else if (IsDerivedFrom(Loc, Composite1, Composite2))
Composite1 = Composite2;
else if (IsDerivedFrom(Loc, Composite2, Composite1))
Composite2 = Composite1;
}
// Rewrap the composites as pointers or member pointers with the union CVRs.
auto MOC = MemberOfClass.rbegin();
for (unsigned CVR : llvm::reverse(QualifierUnion)) {
Qualifiers Quals = Qualifiers::fromCVRMask(CVR);
auto Classes = *MOC++;
if (Classes.first && Classes.second) {
// Rebuild member pointer type
Composite1 = Context.getMemberPointerType(
Context.getQualifiedType(Composite1, Quals), Classes.first);
Composite2 = Context.getMemberPointerType(
Context.getQualifiedType(Composite2, Quals), Classes.second);
} else {
// Rebuild pointer type
Composite1 =
Context.getPointerType(Context.getQualifiedType(Composite1, Quals));
Composite2 =
Context.getPointerType(Context.getQualifiedType(Composite2, Quals));
}
}
// At this point, either the inner types are the same or we have failed to
// find a composite pointer type.
if (!Context.hasSameType(Composite1, Composite2))
return QualType();
struct Conversion {
Sema &S;
Expr *&E1, *&E2;
QualType Composite;
InitializedEntity Entity;
InitializationKind Kind;
InitializationSequence E1ToC, E2ToC;
bool Viable;
// Per C++ [conv.qual]p3, add 'const' to every level before the last
// differing qualifier.
for (unsigned I = 0; I != NeedConstBefore; ++I)
Steps[I].Quals.addConst();
Conversion(Sema &S, SourceLocation Loc, Expr *&E1, Expr *&E2,
QualType Composite)
: S(S), E1(E1), E2(E2), Composite(Composite),
Entity(InitializedEntity::InitializeTemporary(Composite)),
Kind(InitializationKind::CreateCopy(Loc, SourceLocation())),
E1ToC(S, Entity, Kind, E1), E2ToC(S, Entity, Kind, E2),
Viable(E1ToC && E2ToC) {}
// Rebuild the composite type.
QualType Composite = Composite1;
for (auto &S : llvm::reverse(Steps))
Composite = S.rebuild(Context, Composite);
bool perform() {
ExprResult E1Result = E1ToC.Perform(S, Entity, Kind, E1);
if (E1Result.isInvalid())
return true;
E1 = E1Result.getAs<Expr>();
if (ConvertArgs) {
// Convert the expressions to the composite pointer type.
InitializedEntity Entity =
InitializedEntity::InitializeTemporary(Composite);
InitializationKind Kind =
InitializationKind::CreateCopy(Loc, SourceLocation());
ExprResult E2Result = E2ToC.Perform(S, Entity, Kind, E2);
if (E2Result.isInvalid())
return true;
E2 = E2Result.getAs<Expr>();
return false;
}
};
// Try to convert to each composite pointer type.
Conversion C1(*this, Loc, E1, E2, Composite1);
if (C1.Viable && Context.hasSameType(Composite1, Composite2)) {
if (ConvertArgs && C1.perform())
InitializationSequence E1ToC(*this, Entity, Kind, E1);
if (!E1ToC)
return QualType();
return C1.Composite;
}
Conversion C2(*this, Loc, E1, E2, Composite2);
if (C1.Viable == C2.Viable) {
// Either Composite1 and Composite2 are viable and are different, or
// neither is viable.
// FIXME: How both be viable and different?
return QualType();
InitializationSequence E2ToC(*this, Entity, Kind, E2);
if (!E2ToC)
return QualType();
// FIXME: Let the caller know if these fail to avoid duplicate diagnostics.
ExprResult E1Result = E1ToC.Perform(*this, Entity, Kind, E1);
if (E1Result.isInvalid())
return QualType();
E1 = E1Result.get();
ExprResult E2Result = E2ToC.Perform(*this, Entity, Kind, E2);
if (E2Result.isInvalid())
return QualType();
E2 = E2Result.get();
}
// Convert to the chosen type.
if (ConvertArgs && (C1.Viable ? C1 : C2).perform())
return QualType();
return C1.Viable ? C1.Composite : C2.Composite;
return Composite;
}
ExprResult Sema::MaybeBindToTemporary(Expr *E) {

View File

@@ -17,3 +17,13 @@
}
@end
@class A;
void multilevel() {
A **p;
const A **q;
using T = decltype(true ? q : p);
using T = const A * const *;
}

View File

@@ -0,0 +1,23 @@
// RUN: %clang_cc1 %s -cl-std=clc++ -pedantic -verify
namespace PointerRvalues {
void f(__global int *__constant *a, const __global int *__constant *b) {
using T = decltype(true ? +a : +b);
using T = const __global int *const __constant *;
}
void g(const __global int *a, __generic int *b) {
using T = decltype(true ? +a : +b);
using T = const __generic int *;
}
void h(const __global int **a, __generic int **b) {
using T = decltype(true ? +a : +b); // expected-error {{incompatible operand types}}
}
void i(__global int **a, __generic int **b) {
using T = decltype(true ? +a : +b); // expected-error {{incompatible operand types}}
}
}