Skip to content

Commit 0ad6f73

Browse files
authored
Merge pull request #9090 from slavapestov/enable-subclass-existentials
Enable subclass existentials
2 parents 97a9bfa + 6594a4c commit 0ad6f73

26 files changed

+217
-67
lines changed

CHANGELOG.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,70 @@ CHANGELOG
2121
Swift 4.0
2222
---------
2323

24+
* [SE-0156][]
25+
26+
Protocol composition types can now contain one or more class type terms,
27+
forming a class-constrained protocol composition.
28+
29+
For example:
30+
31+
```swift
32+
protocol Paintable {
33+
func paint()
34+
}
35+
36+
class Canvas {
37+
var origin: CGPoint
38+
}
39+
40+
class Wall : Canvas, Paintable {
41+
func paint() { ... }
42+
}```
43+
44+
func render(_: Canvas & Paintable) { ... }
45+
46+
render(Wall())
47+
```
48+
49+
Note that class-constrained protocol compositions can be written and
50+
used in both Swift 3 and Swift 4 mode.
51+
52+
Generated headers for Swift APIs will map class-constrained protocol
53+
compositions to Objective-C protocol-qualified class types in both
54+
Swift 3 and Swift 4 mode (for instance, `NSSomeClass & SomeProto &
55+
OtherProto` in Swift becomes `NSSomeClass <SomeProto, OtherProto>`
56+
in Objective-C).
57+
58+
Objective-C APIs which use protocol-qualified class types differ in
59+
behavior when imported by a module compiled in Swift 3 mode and
60+
Swift 4 mode. In Swift 3 mode, these APIs will continue to import as
61+
protocol compositions without a class constraint
62+
(eg, `SomeProto & OtherProto`).
63+
64+
In Swift 4 mode, protocol-qualified class types import as
65+
class-constrained protocol compositions, for a more faithful mapping
66+
of APIs from Objective-C to Swift.
67+
68+
Note that the current implementation of class-constrained protocol
69+
compositions lacks three features outlined in the Swift evolution proposal:
70+
71+
- In the evolution proposal, a class-constrained is permitted to contain
72+
two different classes as long as one is a superclass of the other.
73+
The current implementation only allows multiple classes to appear in
74+
the composition if they are identical.
75+
76+
- In the evolution proposal, associated type and class inheritance clauses
77+
are generalized to allow class-constrained protocol compositions. The
78+
current implementation does not allow this.
79+
80+
- In the evolution proposal, protocol inheritance clauses are allowed to
81+
contain a class, placing a requirement that all conforming types are
82+
a subclass of the given class. The current implementation does not
83+
allow this.
84+
85+
These missing aspects of the proposal can be introduced in a future
86+
release without breaking source compatibility with existing code.
87+
2488
* [SE-0142][]
2589

2690
Protocols and associated types can now contain `where` clauses that

include/swift/Basic/LangOptions.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,6 @@ namespace swift {
165165
/// optimized custom allocator, so that memory debugging tools can be used.
166166
bool UseMalloc = false;
167167

168-
/// \brief Enable classes to appear in protocol composition types.
169-
bool EnableExperimentalSubclassExistentials = false;
170-
171168
/// \brief Enable experimental property behavior feature.
172169
bool EnableExperimentalPropertyBehaviors = false;
173170

include/swift/Option/FrontendOptions.td

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,10 +274,6 @@ def enable_experimental_deserialization_recovery :
274274
Flag<["-"], "enable-experimental-deserialization-recovery">,
275275
HelpText<"Attempt to recover from missing xrefs (etc) in swiftmodules">;
276276

277-
def enable_experimental_subclass_existentials : Flag<["-"],
278-
"enable-experimental-subclass-existentials">,
279-
HelpText<"Enable classes to appear in protocol composition types">;
280-
281277
def enable_cow_existentials : Flag<["-"], "enable-cow-existentials">,
282278
HelpText<"Enable the copy-on-write existential implementation">;
283279

lib/ClangImporter/ImportType.cpp

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -883,10 +883,8 @@ namespace {
883883
} else {
884884
SmallVector<Type, 4> memberTypes;
885885

886-
if (Impl.SwiftContext.LangOpts.EnableExperimentalSubclassExistentials) {
887-
if (auto superclassType = typeParam->getSuperclass())
888-
memberTypes.push_back(superclassType);
889-
}
886+
if (auto superclassType = typeParam->getSuperclass())
887+
memberTypes.push_back(superclassType);
890888

891889
for (auto protocolDecl : typeParam->getConformingProtocols())
892890
memberTypes.push_back(protocolDecl->getDeclaredType());
@@ -1029,9 +1027,7 @@ namespace {
10291027
// Swift 3 compatibility -- don't import subclass existentials
10301028
if (!type->qual_empty() &&
10311029
(importedType->isAnyObject() ||
1032-
(!Impl.SwiftContext.isSwiftVersion3() &&
1033-
Impl.SwiftContext.LangOpts.EnableExperimentalSubclassExistentials))) {
1034-
1030+
!Impl.SwiftContext.isSwiftVersion3())) {
10351031
SmallVector<Type, 4> members;
10361032
if (!importedType->isAnyObject())
10371033
members.push_back(importedType);

lib/Frontend/CompilerInvocation.cpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -910,9 +910,6 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
910910
Opts.EnableExperimentalKeyPaths |=
911911
Args.hasArg(OPT_enable_experimental_keypaths);
912912

913-
Opts.EnableExperimentalSubclassExistentials |=
914-
Args.hasArg(OPT_enable_experimental_subclass_existentials);
915-
916913
Opts.EnableClassResilience |=
917914
Args.hasArg(OPT_enable_class_resilience);
918915

lib/Sema/TypeCheckType.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3100,8 +3100,7 @@ Type TypeResolver::resolveCompositionType(CompositionTypeRepr *repr,
31003100
if (!ty || ty->hasError()) return ty;
31013101

31023102
auto nominalDecl = ty->getAnyNominal();
3103-
if (TC.Context.LangOpts.EnableExperimentalSubclassExistentials &&
3104-
nominalDecl && isa<ClassDecl>(nominalDecl)) {
3103+
if (nominalDecl && isa<ClassDecl>(nominalDecl)) {
31053104
if (checkSuperclass(tyR->getStartLoc(), ty))
31063105
continue;
31073106

stdlib/public/runtime/Casting.cpp

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -411,8 +411,19 @@ static bool _conformsToProtocol(const OpaqueValue *value,
411411
/// list of conformances.
412412
static bool _conformsToProtocols(const OpaqueValue *value,
413413
const Metadata *type,
414-
const ProtocolDescriptorList &protocols,
414+
const ExistentialTypeMetadata *existentialType,
415415
const WitnessTable **conformances) {
416+
if (auto *superclass = existentialType->getSuperclassConstraint()) {
417+
if (!swift_dynamicCastMetatype(type, superclass))
418+
return false;
419+
}
420+
421+
if (existentialType->isClassBounded()) {
422+
if (!Metadata::isAnyKindOfClass(type->getKind()))
423+
return false;
424+
}
425+
426+
auto &protocols = existentialType->Protocols;
416427
for (unsigned i = 0, n = protocols.NumProtocols; i != n; ++i) {
417428
const ProtocolDescriptor *protocol = protocols[i];
418429
if (!_conformsToProtocol(value, type, protocol, conformances))
@@ -876,7 +887,7 @@ static bool _dynamicCastToExistential(OpaqueValue *dest,
876887
#if SWIFT_OBJC_INTEROP
877888
// If the destination type is a set of protocols that SwiftValue
878889
// implements, we're fine.
879-
if (findSwiftValueConformances(targetType->Protocols,
890+
if (findSwiftValueConformances(targetType,
880891
destExistential->getWitnessTables())) {
881892
bool consumeValue = dynamicFlags & DynamicCastFlags::TakeOnSuccess;
882893
destExistential->Value =
@@ -963,7 +974,7 @@ static bool _dynamicCastToExistential(OpaqueValue *dest,
963974
// container with a class instance to AnyObject. In this case no check is
964975
// necessary.
965976
if (srcDynamicType && !_conformsToProtocols(srcDynamicValue, srcDynamicType,
966-
targetType->Protocols,
977+
targetType,
967978
destExistential->getWitnessTables()))
968979
return fallbackForNonDirectConformance();
969980

@@ -981,7 +992,7 @@ static bool _dynamicCastToExistential(OpaqueValue *dest,
981992

982993
// Check for protocol conformances and fill in the witness tables.
983994
if (!_conformsToProtocols(srcDynamicValue, srcDynamicType,
984-
targetType->Protocols,
995+
targetType,
985996
destExistential->getWitnessTables()))
986997
return fallbackForNonDirectConformance();
987998

@@ -1015,7 +1026,7 @@ static bool _dynamicCastToExistential(OpaqueValue *dest,
10151026
assert(targetType->Protocols.NumProtocols == 1);
10161027
const WitnessTable *errorWitness;
10171028
if (!_conformsToProtocols(srcDynamicValue, srcDynamicType,
1018-
targetType->Protocols,
1029+
targetType,
10191030
&errorWitness))
10201031
return fallbackForNonDirectConformance();
10211032

@@ -1052,6 +1063,8 @@ static bool _dynamicCastToExistential(OpaqueValue *dest,
10521063
static const void *
10531064
_dynamicCastUnknownClassToExistential(const void *object,
10541065
const ExistentialTypeMetadata *targetType) {
1066+
// FIXME: check superclass constraint here.
1067+
10551068
for (unsigned i = 0, e = targetType->Protocols.NumProtocols; i < e; ++i) {
10561069
const ProtocolDescriptor *protocol = targetType->Protocols[i];
10571070

@@ -1848,18 +1861,14 @@ static bool _dynamicCastMetatypeToExistentialMetatype(OpaqueValue *dest,
18481861
dyn_cast<ExistentialTypeMetadata>(targetInstanceType)) {
18491862
// Check for conformance to all the protocols.
18501863
// TODO: collect the witness tables.
1851-
auto &protocols = targetInstanceTypeAsExistential->Protocols;
18521864
const WitnessTable **conformance
18531865
= writeDestMetatype ? destMetatype->getWitnessTables() : nullptr;
1854-
for (unsigned i = 0, n = protocols.NumProtocols; i != n; ++i) {
1855-
const ProtocolDescriptor *protocol = protocols[i];
1856-
if (!_conformsToProtocol(nullptr, srcMetatype, protocol, conformance)) {
1857-
if (flags & DynamicCastFlags::Unconditional)
1858-
swift_dynamicCastFailure(srcMetatype, targetType);
1859-
return false;
1860-
}
1861-
if (conformance && protocol->Flags.needsWitnessTable())
1862-
++conformance;
1866+
if (!_conformsToProtocols(nullptr, srcMetatype,
1867+
targetInstanceTypeAsExistential,
1868+
conformance)) {
1869+
if (flags & DynamicCastFlags::Unconditional)
1870+
swift_dynamicCastFailure(srcMetatype, targetType);
1871+
return false;
18631872
}
18641873

18651874
if (writeDestMetatype)

stdlib/public/runtime/Demangle.cpp

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -149,15 +149,42 @@ swift::_swift_buildDemanglingForMetadata(const Metadata *type,
149149
}
150150
case MetadataKind::Existential: {
151151
auto exis = static_cast<const ExistentialTypeMetadata *>(type);
152-
NodePointer proto_list = Dem.createNode(Node::Kind::ProtocolList);
153-
NodePointer type_list = Dem.createNode(Node::Kind::TypeList);
154-
155-
proto_list->addChild(type_list, Dem);
152+
NodePointer proto_list;
156153

157154
std::vector<const ProtocolDescriptor *> protocols;
158155
protocols.reserve(exis->Protocols.NumProtocols);
159156
for (unsigned i = 0, e = exis->Protocols.NumProtocols; i < e; ++i)
160157
protocols.push_back(exis->Protocols[i]);
158+
159+
if (exis->getSuperclassConstraint()) {
160+
// If there is a superclass constraint, we mangle it specially.
161+
proto_list = Dem.createNode(Node::Kind::ProtocolListWithClass);
162+
} else if (exis->isClassBounded()) {
163+
// Check if the class constraint is implied by any of our
164+
// protocols.
165+
bool requiresClassImplicit = false;
166+
167+
for (auto *protocol : protocols) {
168+
if (protocol->Flags.getClassConstraint()
169+
== ProtocolClassConstraint::Class)
170+
requiresClassImplicit = true;
171+
}
172+
173+
// If it was implied, we don't do anything special.
174+
if (requiresClassImplicit)
175+
proto_list = Dem.createNode(Node::Kind::ProtocolList);
176+
// If the existential type has an explicit AnyObject constraint,
177+
// we must mangle it as such.
178+
else
179+
proto_list = Dem.createNode(Node::Kind::ProtocolListWithAnyObject);
180+
} else {
181+
// Just a simple composition of protocols.
182+
proto_list = Dem.createNode(Node::Kind::ProtocolList);
183+
}
184+
185+
NodePointer type_list = Dem.createNode(Node::Kind::TypeList);
186+
187+
proto_list->addChild(type_list, Dem);
161188

162189
// Sort the protocols by their mangled names.
163190
// The ordering in the existential type metadata is by metadata pointer,
@@ -198,7 +225,12 @@ swift::_swift_buildDemanglingForMetadata(const Metadata *type,
198225
assert(protocolNode->getChild(0)->getKind() == Node::Kind::Protocol);
199226
type_list->addChild(protocolNode, Dem);
200227
}
201-
228+
229+
if (auto *superclass = exis->getSuperclassConstraint()) {
230+
auto superclassNode = _swift_buildDemanglingForMetadata(superclass, Dem);
231+
proto_list->addChild(superclassNode, Dem);
232+
}
233+
202234
return proto_list;
203235
}
204236
case MetadataKind::ExistentialMetatype: {

stdlib/public/runtime/SwiftValue.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@ getValueFromSwiftValue(_SwiftValue *v);
5252
/// or nil if it is not.
5353
_SwiftValue *getAsSwiftValue(id object);
5454

55-
/// Find conformances for SwiftValue to the given list of protocols.
55+
/// Find conformances for SwiftValue to the given existential type.
5656
///
5757
/// Returns true if SwiftValue does conform to all the protocols.
58-
bool findSwiftValueConformances(const ProtocolDescriptorList &protocols,
58+
bool findSwiftValueConformances(const ExistentialTypeMetadata *existentialType,
5959
const WitnessTable **tablesBuffer);
6060

6161
} // namespace swift

stdlib/public/runtime/SwiftValue.mm

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,14 @@ static size_t getSwiftValuePayloadAlignMask(const Metadata *type) {
213213
}
214214

215215
bool
216-
swift::findSwiftValueConformances(const ProtocolDescriptorList &protocols,
216+
swift::findSwiftValueConformances(const ExistentialTypeMetadata *existentialType,
217217
const WitnessTable **tablesBuffer) {
218+
// _SwiftValue never satisfies a superclass constraint.
219+
if (existentialType->getSuperclassConstraint() != nullptr)
220+
return false;
221+
222+
auto &protocols = existentialType->Protocols;
223+
218224
Class cls = nullptr;
219225

220226
// Note that currently we never modify tablesBuffer because

test/ClangImporter/objc_bridging_generics.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -parse-as-library -verify -enable-experimental-subclass-existentials -swift-version 4 %s
1+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -parse-as-library -verify -swift-version 4 %s
22

33
// REQUIRES: objc_interop
44

test/ClangImporter/objc_bridging_generics_swift3.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -parse-as-library -verify -enable-experimental-subclass-existentials -swift-version 3 %s
1+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -parse-as-library -verify -swift-version 3 %s
22

33
// REQUIRES: objc_interop
44

test/ClangImporter/subclass_existentials.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -verify -o - -primary-file %s -swift-version 4 -enable-experimental-subclass-existentials
1+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -verify -o - -primary-file %s -swift-version 4
22

33
// REQUIRES: objc_interop
44

test/ClangImporter/subclass_existentials_ir.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-ir -o - -primary-file %s -swift-version 4 -enable-experimental-subclass-existentials
1+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-ir -o - -primary-file %s -swift-version 4
22

33
// REQUIRES: objc_interop
44

test/ClangImporter/subclass_existentials_swift3.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -verify -o - -primary-file %s -enable-experimental-subclass-existentials -swift-version 3
1+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -verify -o - -primary-file %s -swift-version 3
22

33
// REQUIRES: objc_interop
44

test/IRGen/objc_type_encoding.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// RUN: rm -rf %t && mkdir -p %t
22
// RUN: %build-irgen-test-overlays
3-
// RUN: %target-swift-frontend(mock-sdk: -sdk %S/Inputs -I %t) %s -emit-ir -disable-objc-attr-requires-foundation-module -enable-experimental-subclass-existentials | %FileCheck %s -check-prefix=CHECK-%target-os
3+
// RUN: %target-swift-frontend(mock-sdk: -sdk %S/Inputs -I %t) %s -emit-ir -disable-objc-attr-requires-foundation-module | %FileCheck %s -check-prefix=CHECK-%target-os
44

55
// REQUIRES: CPU=x86_64
66
// REQUIRES: objc_interop

test/IRGen/subclass_existentials.sil

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend -primary-file %s -emit-ir -enable-experimental-subclass-existentials | %FileCheck %s --check-prefix=CHECK-%target-runtime --check-prefix=CHECK
1+
// RUN: %target-swift-frontend -primary-file %s -emit-ir | %FileCheck %s --check-prefix=CHECK-%target-runtime --check-prefix=CHECK
22

33
sil_stage canonical
44

test/IRGen/type_layout_reference_storage.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend -assume-parsing-unqualified-ownership-sil -emit-ir -enable-experimental-subclass-existentials %s | %FileCheck %s --check-prefix=CHECK-%target-ptrsize --check-prefix=CHECK
1+
// RUN: %target-swift-frontend -assume-parsing-unqualified-ownership-sil -emit-ir %s | %FileCheck %s --check-prefix=CHECK-%target-ptrsize --check-prefix=CHECK
22

33
class C {}
44
protocol P: class {}

test/IRGen/type_layout_reference_storage_objc.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-ir %s -enable-experimental-subclass-existentials | %FileCheck %s
1+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-ir %s | %FileCheck %s
22
// REQUIRES: objc_interop
33

44
import Foundation

0 commit comments

Comments
 (0)