Skip to content

Commit 00abccb

Browse files
Merge pull request #39694 from AnthonyLatsis/mincomp
Sema: Explicit stricter minimization of protocol composition types for redeclaration checking
2 parents 70e1129 + 4b1424e commit 00abccb

File tree

6 files changed

+154
-29
lines changed

6 files changed

+154
-29
lines changed

include/swift/AST/Types.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,11 @@ class alignas(1 << TypeAlignInBits) TypeBase
499499
/// associated types, or type parameters that have been made concrete.
500500
CanType getCanonicalType(GenericSignature sig);
501501

502+
/// Canonical protocol composition types are minimized only to a certain
503+
/// degree to preserve ABI compatibility. This routine enables performing
504+
/// slower, but stricter minimization at need (e.g. redeclaration checking).
505+
CanType getMinimalCanonicalType() const;
506+
502507
/// Reconstitute type sugar, e.g., for array types, dictionary
503508
/// types, optionals, etc.
504509
TypeBase *reconstituteSugar(bool Recursive);
@@ -5175,7 +5180,12 @@ class ProtocolCompositionType final : public TypeBase,
51755180
/// given set of members.
51765181
static Type get(const ASTContext &C, ArrayRef<Type> Members,
51775182
bool HasExplicitAnyObject);
5178-
5183+
5184+
/// Canonical protocol composition types are minimized only to a certain
5185+
/// degree to preserve ABI compatibility. This routine enables performing
5186+
/// slower, but stricter minimization at need (e.g. redeclaration checking).
5187+
CanType getMinimalCanonicalType() const;
5188+
51795189
/// Retrieve the set of members composed to create this type.
51805190
///
51815191
/// For non-canonical types, this can contain classes, protocols and

lib/AST/ASTContext.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3210,6 +3210,8 @@ ClassType *ClassType::get(ClassDecl *D, Type Parent, const ASTContext &C) {
32103210
ProtocolCompositionType *
32113211
ProtocolCompositionType::build(const ASTContext &C, ArrayRef<Type> Members,
32123212
bool HasExplicitAnyObject) {
3213+
assert(Members.size() != 1 || HasExplicitAnyObject);
3214+
32133215
// Check to see if we've already seen this protocol composition before.
32143216
void *InsertPos = nullptr;
32153217
llvm::FoldingSetNodeID ID;

lib/AST/Decl.cpp

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2713,7 +2713,7 @@ static Type mapSignatureType(ASTContext &ctx, Type type) {
27132713
if (type->is<FunctionType>()) {
27142714
return mapSignatureFunctionType(ctx, type, false, false, false, 1);
27152715
}
2716-
2716+
27172717
return type;
27182718
});
27192719
}
@@ -2855,12 +2855,11 @@ OverloadSignature ValueDecl::getOverloadSignature() const {
28552855
CanType ValueDecl::getOverloadSignatureType() const {
28562856
if (auto *afd = dyn_cast<AbstractFunctionDecl>(this)) {
28572857
bool isMethod = afd->hasImplicitSelfDecl();
2858-
return mapSignatureFunctionType(
2859-
getASTContext(), getInterfaceType(),
2860-
/*topLevelFunction=*/true,
2861-
isMethod,
2862-
/*isInitializer=*/isa<ConstructorDecl>(afd),
2863-
getNumCurryLevels())->getCanonicalType();
2858+
return mapSignatureFunctionType(getASTContext(), getInterfaceType(),
2859+
/*topLevelFunction=*/true, isMethod,
2860+
/*isInitializer=*/isa<ConstructorDecl>(afd),
2861+
getNumCurryLevels())
2862+
->getMinimalCanonicalType();
28642863
}
28652864

28662865
if (isa<AbstractStorageDecl>(this)) {
@@ -2871,27 +2870,27 @@ CanType ValueDecl::getOverloadSignatureType() const {
28712870
if (isa<VarDecl>(this)) {
28722871
defaultSignatureType = TupleType::getEmpty(getASTContext());
28732872
} else {
2874-
defaultSignatureType = mapSignatureFunctionType(
2875-
getASTContext(), getInterfaceType(),
2876-
/*topLevelFunction=*/true,
2877-
/*isMethod=*/false,
2878-
/*isInitializer=*/false,
2879-
getNumCurryLevels())->getCanonicalType();
2873+
defaultSignatureType =
2874+
mapSignatureFunctionType(getASTContext(), getInterfaceType(),
2875+
/*topLevelFunction=*/true,
2876+
/*isMethod=*/false,
2877+
/*isInitializer=*/false, getNumCurryLevels())
2878+
->getMinimalCanonicalType();
28802879
}
28812880

28822881
// We want to curry the default signature type with the 'self' type of the
28832882
// given context (if any) in order to ensure the overload signature type
28842883
// is unique across different contexts, such as between a protocol extension
28852884
// and struct decl.
28862885
return defaultSignatureType->addCurriedSelfType(getDeclContext())
2887-
->getCanonicalType();
2886+
->getMinimalCanonicalType();
28882887
}
28892888

28902889
if (isa<EnumElementDecl>(this)) {
28912890
auto mappedType = mapSignatureFunctionType(
28922891
getASTContext(), getInterfaceType(), /*topLevelFunction=*/false,
28932892
/*isMethod=*/false, /*isInitializer=*/false, getNumCurryLevels());
2894-
return mappedType->getCanonicalType();
2893+
return mappedType->getMinimalCanonicalType();
28952894
}
28962895

28972896
// Note: If you add more cases to this function, you should update the

lib/AST/Type.cpp

Lines changed: 109 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1559,6 +1559,49 @@ CanType TypeBase::getCanonicalType(GenericSignature sig) {
15591559
return sig.getCanonicalTypeInContext(this);
15601560
}
15611561

1562+
CanType TypeBase::getMinimalCanonicalType() const {
1563+
const auto MinimalTy = getCanonicalType().transform([](Type Ty) -> Type {
1564+
const CanType CanTy = CanType(Ty);
1565+
1566+
if (const auto ET = dyn_cast<ExistentialType>(CanTy)) {
1567+
const auto PCT =
1568+
dyn_cast<ProtocolCompositionType>(ET.getConstraintType());
1569+
if (!PCT) {
1570+
return CanTy;
1571+
}
1572+
1573+
const auto MinimalTy = PCT->getMinimalCanonicalType();
1574+
if (MinimalTy->getClassOrBoundGenericClass()) {
1575+
return MinimalTy;
1576+
}
1577+
1578+
return ExistentialType::get(MinimalTy);
1579+
}
1580+
1581+
if (const auto EM = dyn_cast<ExistentialMetatypeType>(CanTy)) {
1582+
const auto PCT = dyn_cast<ProtocolCompositionType>(EM.getInstanceType());
1583+
if (!PCT) {
1584+
return CanTy;
1585+
}
1586+
1587+
const auto MinimalTy = PCT->getMinimalCanonicalType();
1588+
if (MinimalTy->getClassOrBoundGenericClass()) {
1589+
return MetatypeType::get(MinimalTy);
1590+
}
1591+
1592+
return ExistentialMetatypeType::get(MinimalTy);
1593+
}
1594+
1595+
if (const auto Composition = dyn_cast<ProtocolCompositionType>(CanTy)) {
1596+
return Composition->getMinimalCanonicalType();
1597+
}
1598+
1599+
return CanTy;
1600+
});
1601+
1602+
return CanType(MinimalTy);
1603+
}
1604+
15621605
TypeBase *TypeBase::reconstituteSugar(bool Recursive) {
15631606
auto Func = [Recursive](Type Ty) -> Type {
15641607
if (auto boundGeneric = dyn_cast<BoundGenericType>(Ty.getPointer())) {
@@ -3712,6 +3755,16 @@ bool ProtocolCompositionType::requiresClass() {
37123755
Type ProtocolCompositionType::get(const ASTContext &C,
37133756
ArrayRef<Type> Members,
37143757
bool HasExplicitAnyObject) {
3758+
// Fast path for 'AnyObject' and 'Any'.
3759+
if (Members.empty()) {
3760+
return build(C, Members, HasExplicitAnyObject);
3761+
}
3762+
3763+
// If there's a single member and no layout constraint, return that type.
3764+
if (Members.size() == 1 && !HasExplicitAnyObject) {
3765+
return Members.front();
3766+
}
3767+
37153768
for (Type t : Members) {
37163769
if (!t->isCanonical())
37173770
return build(C, Members, HasExplicitAnyObject);
@@ -3730,11 +3783,6 @@ Type ProtocolCompositionType::get(const ASTContext &C,
37303783
if (Superclass)
37313784
HasExplicitAnyObject = false;
37323785

3733-
// If one protocol remains with no further constraints, its nominal
3734-
// type is the canonical type.
3735-
if (Protocols.size() == 1 && !Superclass && !HasExplicitAnyObject)
3736-
return Protocols.front()->getDeclaredInterfaceType();
3737-
37383786
// Form the set of canonical protocol types from the protocol
37393787
// declarations, and use that to build the canonical composition type.
37403788
SmallVector<Type, 4> CanTypes;
@@ -3744,11 +3792,65 @@ Type ProtocolCompositionType::get(const ASTContext &C,
37443792
Protocols, std::back_inserter(CanTypes),
37453793
[](ProtocolDecl *Proto) { return Proto->getDeclaredInterfaceType(); });
37463794

3747-
// TODO: Canonicalize away HasExplicitAnyObject if it is implied
3748-
// by one of our member protocols.
3795+
// If one member remains and no layout constraint, return that type.
3796+
if (CanTypes.size() == 1 && !HasExplicitAnyObject)
3797+
return CanTypes.front();
3798+
37493799
return build(C, CanTypes, HasExplicitAnyObject);
37503800
}
37513801

3802+
CanType ProtocolCompositionType::getMinimalCanonicalType() const {
3803+
const CanType CanTy = getCanonicalType();
3804+
3805+
// If the canonical type is not a composition, it's minimal.
3806+
const auto Composition = dyn_cast<ProtocolCompositionType>(CanTy);
3807+
if (!Composition) {
3808+
return CanTy;
3809+
}
3810+
3811+
// Nothing to minimize.
3812+
if (Composition->getMembers().empty()) {
3813+
return CanTy;
3814+
}
3815+
3816+
// The only cases we're missing out on proper minimization is when a
3817+
// composition has an explicit superclass or AnyObject constraint.
3818+
if (!Composition->hasExplicitAnyObject() &&
3819+
!Composition->getMembers().front()->getClassOrBoundGenericClass()) {
3820+
// Already minimal.
3821+
return CanTy;
3822+
}
3823+
3824+
auto &Ctx = CanTy->getASTContext();
3825+
3826+
// Use generic signature minimization: the requirements of the signature will
3827+
// represent the minimal composition.
3828+
const auto Sig = Ctx.getOpenedArchetypeSignature(CanTy);
3829+
const auto &Reqs = Sig.getRequirements();
3830+
if (Reqs.size() == 1) {
3831+
return Reqs.front().getSecondType()->getCanonicalType();
3832+
}
3833+
3834+
llvm::SmallVector<Type, 2> MinimalMembers;
3835+
bool MinimalHasExplicitAnyObject = false;
3836+
for (const auto &Req : Reqs) {
3837+
switch (Req.getKind()) {
3838+
case RequirementKind::Conformance:
3839+
case RequirementKind::Superclass:
3840+
MinimalMembers.push_back(Req.getSecondType());
3841+
break;
3842+
case RequirementKind::Layout:
3843+
MinimalHasExplicitAnyObject = true;
3844+
break;
3845+
case RequirementKind::SameType:
3846+
llvm_unreachable("");
3847+
}
3848+
}
3849+
3850+
// The resulting composition is necessarily canonical.
3851+
return CanType(build(Ctx, MinimalMembers, MinimalHasExplicitAnyObject));
3852+
}
3853+
37523854
ClangTypeInfo AnyFunctionType::getClangTypeInfo() const {
37533855
switch (getKind()) {
37543856
case TypeKind::Function:

test/Constraints/generics.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,8 +358,8 @@ func testFixItClassBound() {
358358
let y1: String = y // expected-error {{cannot convert value of type 'ClassBound2<X>' to specified type 'String'}}
359359

360360
// ...but not in types.
361-
let z1: ClassBound // expected-error {{reference to generic type 'ClassBound' requires arguments in <...>}} {{21-21=<X>}}
362-
let z2: ClassBound2 // expected-error {{reference to generic type 'ClassBound2' requires arguments in <...>}} {{22-22=<X>}}
361+
let z1: ClassBound // expected-error {{reference to generic type 'ClassBound' requires arguments in <...>}} {{21-21=<<#Foo: X#>>}}
362+
let z2: ClassBound2 // expected-error {{reference to generic type 'ClassBound2' requires arguments in <...>}} {{22-22=<<#Foo: X#>>}}
363363
}
364364

365365
func testFixItCasting(x: Any) {

test/type/subclass_composition.swift

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,25 @@ struct Unrelated {}
5151
// FIXME: Not implemented yet.
5252
//
5353

54-
func alreadyConforms<T>(_: Base<T>) {} // expected-note {{'alreadyConforms' previously declared here}}
55-
func alreadyConforms<T>(_: Base<T> & P1) {} // expected-note {{'alreadyConforms' previously declared here}}
54+
func alreadyConforms<T>(_: Base<T>) {} // expected-note 3 {{'alreadyConforms' previously declared here}}
55+
func alreadyConforms<T>(_: Base<T> & P1) {} // expected-error {{invalid redeclaration of 'alreadyConforms'}}
5656
func alreadyConforms<T>(_: Base<T> & AnyObject) {} // expected-error {{invalid redeclaration of 'alreadyConforms'}}
5757
func alreadyConforms<T>(_: Base<T> & P1 & AnyObject) {} // expected-error {{invalid redeclaration of 'alreadyConforms'}}
5858

59-
func alreadyConforms(_: P3) {}
60-
func alreadyConforms(_: P3 & AnyObject) {}
59+
func alreadyConformsMeta<T>(_: Base<T>.Type) {} // expected-note 7 {{'alreadyConformsMeta' previously declared here}}
60+
func alreadyConformsMeta<T>(_: (Base<T> & P1).Type) {} // expected-error {{invalid redeclaration of 'alreadyConformsMeta'}}
61+
func alreadyConformsMeta<T>(_: (Base<T> & P1).Protocol) {} // expected-error {{invalid redeclaration of 'alreadyConformsMeta'}}
62+
func alreadyConformsMeta<T>(_: (any Base<T> & P1).Type) {} // expected-error {{invalid redeclaration of 'alreadyConformsMeta'}}
63+
func alreadyConformsMeta<T>(_: (Base<T> & AnyObject).Type) {} // expected-error {{invalid redeclaration of 'alreadyConformsMeta'}}
64+
func alreadyConformsMeta<T>(_: (Base<T> & P1 & AnyObject).Type) {} // expected-error {{invalid redeclaration of 'alreadyConformsMeta'}}
65+
func alreadyConformsMeta<T>(_: (Base<T> & P1 & AnyObject).Protocol) {} // expected-error {{invalid redeclaration of 'alreadyConformsMeta'}}
66+
func alreadyConformsMeta<T>(_: (any Base<T> & P1 & AnyObject).Type) {} // expected-error {{invalid redeclaration of 'alreadyConformsMeta'}}
67+
68+
func alreadyConforms(_: P3) {} // expected-note {{'alreadyConforms' previously declared here}}
69+
func alreadyConforms(_: P3 & AnyObject) {} // expected-error {{invalid redeclaration of 'alreadyConforms'}}
70+
71+
func alreadyConformsMeta(_: P3.Type) {} // expected-note {{'alreadyConformsMeta' previously declared here}}
72+
func alreadyConformsMeta(_: (P3 & AnyObject).Type) {} // expected-error {{invalid redeclaration of 'alreadyConformsMeta'}}
6173

6274
// SE-0156 stipulates that a composition can contain multiple classes, as long
6375
// as they are all the same.

0 commit comments

Comments
 (0)