Skip to content

Enable subclass existentials #9090

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,70 @@ CHANGELOG
Swift 4.0
---------

* [SE-0156][]

Protocol composition types can now contain one or more class type terms,
forming a class-constrained protocol composition.

For example:

```swift
protocol Paintable {
func paint()
}

class Canvas {
var origin: CGPoint
}

class Wall : Canvas, Paintable {
func paint() { ... }
}```

func render(_: Canvas & Paintable) { ... }

render(Wall())
```

Note that class-constrained protocol compositions can be written and
used in both Swift 3 and Swift 4 mode.

Generated headers for Swift APIs will map class-constrained protocol
compositions to Objective-C protocol-qualified class types in both
Swift 3 and Swift 4 mode (for instance, `NSSomeClass & SomeProto &
OtherProto` in Swift becomes `NSSomeClass <SomeProto, OtherProto>`
in Objective-C).

Objective-C APIs which use protocol-qualified class types differ in
behavior when imported by a module compiled in Swift 3 mode and
Swift 4 mode. In Swift 3 mode, these APIs will continue to import as
protocol compositions without a class constraint
(eg, `SomeProto & OtherProto`).

In Swift 4 mode, protocol-qualified class types import as
class-constrained protocol compositions, for a more faithful mapping
of APIs from Objective-C to Swift.

Note that the current implementation of class-constrained protocol
compositions lacks three features outlined in the Swift evolution proposal:

- In the evolution proposal, a class-constrained is permitted to contain
two different classes as long as one is a superclass of the other.
The current implementation only allows multiple classes to appear in
the composition if they are identical.

- In the evolution proposal, associated type and class inheritance clauses
are generalized to allow class-constrained protocol compositions. The
current implementation does not allow this.

- In the evolution proposal, protocol inheritance clauses are allowed to
contain a class, placing a requirement that all conforming types are
a subclass of the given class. The current implementation does not
allow this.

These missing aspects of the proposal can be introduced in a future
release without breaking source compatibility with existing code.

* [SE-0142][]

Protocols and associated types can now contain `where` clauses that
Expand Down
3 changes: 0 additions & 3 deletions include/swift/Basic/LangOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,6 @@ namespace swift {
/// optimized custom allocator, so that memory debugging tools can be used.
bool UseMalloc = false;

/// \brief Enable classes to appear in protocol composition types.
bool EnableExperimentalSubclassExistentials = false;

/// \brief Enable experimental property behavior feature.
bool EnableExperimentalPropertyBehaviors = false;

Expand Down
4 changes: 0 additions & 4 deletions include/swift/Option/FrontendOptions.td
Original file line number Diff line number Diff line change
Expand Up @@ -274,10 +274,6 @@ def enable_experimental_deserialization_recovery :
Flag<["-"], "enable-experimental-deserialization-recovery">,
HelpText<"Attempt to recover from missing xrefs (etc) in swiftmodules">;

def enable_experimental_subclass_existentials : Flag<["-"],
"enable-experimental-subclass-existentials">,
HelpText<"Enable classes to appear in protocol composition types">;

def enable_cow_existentials : Flag<["-"], "enable-cow-existentials">,
HelpText<"Enable the copy-on-write existential implementation">;

Expand Down
10 changes: 3 additions & 7 deletions lib/ClangImporter/ImportType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -883,10 +883,8 @@ namespace {
} else {
SmallVector<Type, 4> memberTypes;

if (Impl.SwiftContext.LangOpts.EnableExperimentalSubclassExistentials) {
if (auto superclassType = typeParam->getSuperclass())
memberTypes.push_back(superclassType);
}
if (auto superclassType = typeParam->getSuperclass())
memberTypes.push_back(superclassType);

for (auto protocolDecl : typeParam->getConformingProtocols())
memberTypes.push_back(protocolDecl->getDeclaredType());
Expand Down Expand Up @@ -1029,9 +1027,7 @@ namespace {
// Swift 3 compatibility -- don't import subclass existentials
if (!type->qual_empty() &&
(importedType->isAnyObject() ||
(!Impl.SwiftContext.isSwiftVersion3() &&
Impl.SwiftContext.LangOpts.EnableExperimentalSubclassExistentials))) {

!Impl.SwiftContext.isSwiftVersion3())) {
SmallVector<Type, 4> members;
if (!importedType->isAnyObject())
members.push_back(importedType);
Expand Down
3 changes: 0 additions & 3 deletions lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -910,9 +910,6 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
Opts.EnableExperimentalKeyPaths |=
Args.hasArg(OPT_enable_experimental_keypaths);

Opts.EnableExperimentalSubclassExistentials |=
Args.hasArg(OPT_enable_experimental_subclass_existentials);

Opts.EnableClassResilience |=
Args.hasArg(OPT_enable_class_resilience);

Expand Down
3 changes: 1 addition & 2 deletions lib/Sema/TypeCheckType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3100,8 +3100,7 @@ Type TypeResolver::resolveCompositionType(CompositionTypeRepr *repr,
if (!ty || ty->hasError()) return ty;

auto nominalDecl = ty->getAnyNominal();
if (TC.Context.LangOpts.EnableExperimentalSubclassExistentials &&
nominalDecl && isa<ClassDecl>(nominalDecl)) {
if (nominalDecl && isa<ClassDecl>(nominalDecl)) {
if (checkSuperclass(tyR->getStartLoc(), ty))
continue;

Expand Down
39 changes: 24 additions & 15 deletions stdlib/public/runtime/Casting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -411,8 +411,19 @@ static bool _conformsToProtocol(const OpaqueValue *value,
/// list of conformances.
static bool _conformsToProtocols(const OpaqueValue *value,
const Metadata *type,
const ProtocolDescriptorList &protocols,
const ExistentialTypeMetadata *existentialType,
const WitnessTable **conformances) {
if (auto *superclass = existentialType->getSuperclassConstraint()) {
if (!swift_dynamicCastMetatype(type, superclass))
return false;
}

if (existentialType->isClassBounded()) {
if (!Metadata::isAnyKindOfClass(type->getKind()))
return false;
}

auto &protocols = existentialType->Protocols;
for (unsigned i = 0, n = protocols.NumProtocols; i != n; ++i) {
const ProtocolDescriptor *protocol = protocols[i];
if (!_conformsToProtocol(value, type, protocol, conformances))
Expand Down Expand Up @@ -876,7 +887,7 @@ static bool _dynamicCastToExistential(OpaqueValue *dest,
#if SWIFT_OBJC_INTEROP
// If the destination type is a set of protocols that SwiftValue
// implements, we're fine.
if (findSwiftValueConformances(targetType->Protocols,
if (findSwiftValueConformances(targetType,
destExistential->getWitnessTables())) {
bool consumeValue = dynamicFlags & DynamicCastFlags::TakeOnSuccess;
destExistential->Value =
Expand Down Expand Up @@ -963,7 +974,7 @@ static bool _dynamicCastToExistential(OpaqueValue *dest,
// container with a class instance to AnyObject. In this case no check is
// necessary.
if (srcDynamicType && !_conformsToProtocols(srcDynamicValue, srcDynamicType,
targetType->Protocols,
targetType,
destExistential->getWitnessTables()))
return fallbackForNonDirectConformance();

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

// Check for protocol conformances and fill in the witness tables.
if (!_conformsToProtocols(srcDynamicValue, srcDynamicType,
targetType->Protocols,
targetType,
destExistential->getWitnessTables()))
return fallbackForNonDirectConformance();

Expand Down Expand Up @@ -1015,7 +1026,7 @@ static bool _dynamicCastToExistential(OpaqueValue *dest,
assert(targetType->Protocols.NumProtocols == 1);
const WitnessTable *errorWitness;
if (!_conformsToProtocols(srcDynamicValue, srcDynamicType,
targetType->Protocols,
targetType,
&errorWitness))
return fallbackForNonDirectConformance();

Expand Down Expand Up @@ -1052,6 +1063,8 @@ static bool _dynamicCastToExistential(OpaqueValue *dest,
static const void *
_dynamicCastUnknownClassToExistential(const void *object,
const ExistentialTypeMetadata *targetType) {
// FIXME: check superclass constraint here.

for (unsigned i = 0, e = targetType->Protocols.NumProtocols; i < e; ++i) {
const ProtocolDescriptor *protocol = targetType->Protocols[i];

Expand Down Expand Up @@ -1848,18 +1861,14 @@ static bool _dynamicCastMetatypeToExistentialMetatype(OpaqueValue *dest,
dyn_cast<ExistentialTypeMetadata>(targetInstanceType)) {
// Check for conformance to all the protocols.
// TODO: collect the witness tables.
auto &protocols = targetInstanceTypeAsExistential->Protocols;
const WitnessTable **conformance
= writeDestMetatype ? destMetatype->getWitnessTables() : nullptr;
for (unsigned i = 0, n = protocols.NumProtocols; i != n; ++i) {
const ProtocolDescriptor *protocol = protocols[i];
if (!_conformsToProtocol(nullptr, srcMetatype, protocol, conformance)) {
if (flags & DynamicCastFlags::Unconditional)
swift_dynamicCastFailure(srcMetatype, targetType);
return false;
}
if (conformance && protocol->Flags.needsWitnessTable())
++conformance;
if (!_conformsToProtocols(nullptr, srcMetatype,
targetInstanceTypeAsExistential,
conformance)) {
if (flags & DynamicCastFlags::Unconditional)
swift_dynamicCastFailure(srcMetatype, targetType);
return false;
}

if (writeDestMetatype)
Expand Down
42 changes: 37 additions & 5 deletions stdlib/public/runtime/Demangle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,15 +149,42 @@ swift::_swift_buildDemanglingForMetadata(const Metadata *type,
}
case MetadataKind::Existential: {
auto exis = static_cast<const ExistentialTypeMetadata *>(type);
NodePointer proto_list = Dem.createNode(Node::Kind::ProtocolList);
NodePointer type_list = Dem.createNode(Node::Kind::TypeList);

proto_list->addChild(type_list, Dem);
NodePointer proto_list;

std::vector<const ProtocolDescriptor *> protocols;
protocols.reserve(exis->Protocols.NumProtocols);
for (unsigned i = 0, e = exis->Protocols.NumProtocols; i < e; ++i)
protocols.push_back(exis->Protocols[i]);

if (exis->getSuperclassConstraint()) {
// If there is a superclass constraint, we mangle it specially.
proto_list = Dem.createNode(Node::Kind::ProtocolListWithClass);
} else if (exis->isClassBounded()) {
// Check if the class constraint is implied by any of our
// protocols.
bool requiresClassImplicit = false;

for (auto *protocol : protocols) {
if (protocol->Flags.getClassConstraint()
== ProtocolClassConstraint::Class)
requiresClassImplicit = true;
}

// If it was implied, we don't do anything special.
if (requiresClassImplicit)
proto_list = Dem.createNode(Node::Kind::ProtocolList);
// If the existential type has an explicit AnyObject constraint,
// we must mangle it as such.
else
proto_list = Dem.createNode(Node::Kind::ProtocolListWithAnyObject);
} else {
// Just a simple composition of protocols.
proto_list = Dem.createNode(Node::Kind::ProtocolList);
}

NodePointer type_list = Dem.createNode(Node::Kind::TypeList);

proto_list->addChild(type_list, Dem);

// Sort the protocols by their mangled names.
// The ordering in the existential type metadata is by metadata pointer,
Expand Down Expand Up @@ -198,7 +225,12 @@ swift::_swift_buildDemanglingForMetadata(const Metadata *type,
assert(protocolNode->getChild(0)->getKind() == Node::Kind::Protocol);
type_list->addChild(protocolNode, Dem);
}


if (auto *superclass = exis->getSuperclassConstraint()) {
auto superclassNode = _swift_buildDemanglingForMetadata(superclass, Dem);
proto_list->addChild(superclassNode, Dem);
}

return proto_list;
}
case MetadataKind::ExistentialMetatype: {
Expand Down
4 changes: 2 additions & 2 deletions stdlib/public/runtime/SwiftValue.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ getValueFromSwiftValue(_SwiftValue *v);
/// or nil if it is not.
_SwiftValue *getAsSwiftValue(id object);

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

} // namespace swift
Expand Down
8 changes: 7 additions & 1 deletion stdlib/public/runtime/SwiftValue.mm
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,14 @@ static size_t getSwiftValuePayloadAlignMask(const Metadata *type) {
}

bool
swift::findSwiftValueConformances(const ProtocolDescriptorList &protocols,
swift::findSwiftValueConformances(const ExistentialTypeMetadata *existentialType,
const WitnessTable **tablesBuffer) {
// _SwiftValue never satisfies a superclass constraint.
if (existentialType->getSuperclassConstraint() != nullptr)
return false;

auto &protocols = existentialType->Protocols;

Class cls = nullptr;

// Note that currently we never modify tablesBuffer because
Expand Down
2 changes: 1 addition & 1 deletion test/ClangImporter/objc_bridging_generics.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -parse-as-library -verify -enable-experimental-subclass-existentials -swift-version 4 %s
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -parse-as-library -verify -swift-version 4 %s

// REQUIRES: objc_interop

Expand Down
2 changes: 1 addition & 1 deletion test/ClangImporter/objc_bridging_generics_swift3.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -parse-as-library -verify -enable-experimental-subclass-existentials -swift-version 3 %s
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -parse-as-library -verify -swift-version 3 %s

// REQUIRES: objc_interop

Expand Down
2 changes: 1 addition & 1 deletion test/ClangImporter/subclass_existentials.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -verify -o - -primary-file %s -swift-version 4 -enable-experimental-subclass-existentials
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -verify -o - -primary-file %s -swift-version 4

// REQUIRES: objc_interop

Expand Down
2 changes: 1 addition & 1 deletion test/ClangImporter/subclass_existentials_ir.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-ir -o - -primary-file %s -swift-version 4 -enable-experimental-subclass-existentials
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-ir -o - -primary-file %s -swift-version 4

// REQUIRES: objc_interop

Expand Down
2 changes: 1 addition & 1 deletion test/ClangImporter/subclass_existentials_swift3.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -verify -o - -primary-file %s -enable-experimental-subclass-existentials -swift-version 3
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -verify -o - -primary-file %s -swift-version 3

// REQUIRES: objc_interop

Expand Down
2 changes: 1 addition & 1 deletion test/IRGen/objc_type_encoding.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// RUN: rm -rf %t && mkdir -p %t
// RUN: %build-irgen-test-overlays
// 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
// 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

// REQUIRES: CPU=x86_64
// REQUIRES: objc_interop
Expand Down
2 changes: 1 addition & 1 deletion test/IRGen/subclass_existentials.sil
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %target-swift-frontend -primary-file %s -emit-ir -enable-experimental-subclass-existentials | %FileCheck %s --check-prefix=CHECK-%target-runtime --check-prefix=CHECK
// RUN: %target-swift-frontend -primary-file %s -emit-ir | %FileCheck %s --check-prefix=CHECK-%target-runtime --check-prefix=CHECK

sil_stage canonical

Expand Down
2 changes: 1 addition & 1 deletion test/IRGen/type_layout_reference_storage.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// 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
// RUN: %target-swift-frontend -assume-parsing-unqualified-ownership-sil -emit-ir %s | %FileCheck %s --check-prefix=CHECK-%target-ptrsize --check-prefix=CHECK

class C {}
protocol P: class {}
Expand Down
2 changes: 1 addition & 1 deletion test/IRGen/type_layout_reference_storage_objc.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-ir %s -enable-experimental-subclass-existentials | %FileCheck %s
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-ir %s | %FileCheck %s
// REQUIRES: objc_interop

import Foundation
Expand Down
Loading