Skip to content

Parameterized protocol improvements [5.7] #59183

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
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
2 changes: 0 additions & 2 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -3788,8 +3788,6 @@ ERROR(protocol_declares_unknown_primary_assoc_type,none,
(Identifier, Type))
ERROR(protocol_declares_duplicate_primary_assoc_type,none,
"duplicate primary associated type name %0", (Identifier))
ERROR(parameterized_protocol_not_supported,none,
"protocol type with type arguments can only be used as a generic constraint", ())
ERROR(protocol_does_not_have_primary_assoc_type,none,
"cannot specialize protocol type %0", (Type))
ERROR(parameterized_protocol_type_argument_count_mismatch,none,
Expand Down
32 changes: 0 additions & 32 deletions include/swift/AST/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -6752,38 +6752,6 @@ inline TypeBase *TypeBase::getDesugaredType() {
return cast<SugarType>(this)->getSinglyDesugaredType()->getDesugaredType();
}

inline bool TypeBase::hasSimpleTypeRepr() const {
// NOTE: Please keep this logic in sync with TypeRepr::isSimple().
switch (getKind()) {
case TypeKind::Function:
case TypeKind::GenericFunction:
return false;

case TypeKind::Metatype:
return !cast<const AnyMetatypeType>(this)->hasRepresentation();

case TypeKind::ExistentialMetatype:
case TypeKind::Existential:
return false;

case TypeKind::OpaqueTypeArchetype:
case TypeKind::OpenedArchetype:
return false;

case TypeKind::ProtocolComposition: {
// 'Any', 'AnyObject' and single protocol compositions are simple
auto composition = cast<const ProtocolCompositionType>(this);
auto memberCount = composition->getMembers().size();
if (composition->hasExplicitAnyObject())
return memberCount == 0;
return memberCount <= 1;
}

default:
return true;
}
}

} // end namespace swift

namespace llvm {
Expand Down
40 changes: 40 additions & 0 deletions lib/AST/Type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6222,6 +6222,46 @@ bool TypeBase::isForeignReferenceType() {
return false;
}

bool TypeBase::hasSimpleTypeRepr() const {
// NOTE: Please keep this logic in sync with TypeRepr::isSimple().
switch (getKind()) {
case TypeKind::Function:
case TypeKind::GenericFunction:
return false;

case TypeKind::Metatype:
return !cast<const AnyMetatypeType>(this)->hasRepresentation();

case TypeKind::ExistentialMetatype:
case TypeKind::Existential:
return false;

case TypeKind::OpaqueTypeArchetype:
case TypeKind::OpenedArchetype:
return false;

case TypeKind::ProtocolComposition: {
// 'Any', 'AnyObject' and single protocol compositions are simple
auto composition = cast<const ProtocolCompositionType>(this);
auto memberCount = composition->getMembers().size();
if (composition->hasExplicitAnyObject())
return memberCount == 0;
return memberCount <= 1;
}

case TypeKind::GenericTypeParam: {
if (auto *decl = cast<const GenericTypeParamType>(this)->getDecl()) {
return !decl->isOpaqueType();
}

return true;
}

default:
return true;
}
}

bool CanType::isForeignReferenceType() {
if (auto *classDecl = getPointer()->lookThroughAllOptionalTypes()->getClassOrBoundGenericClass())
return classDecl->isForeignReferenceType();
Expand Down
35 changes: 25 additions & 10 deletions lib/Sema/TypeCheckDeclPrimary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,21 @@ using namespace swift;

#define DEBUG_TYPE "TypeCheckDeclPrimary"

static Type containsParameterizedProtocolType(Type inheritedTy) {
if (inheritedTy->is<ParameterizedProtocolType>()) {
return inheritedTy;
}

if (auto *compositionTy = inheritedTy->getAs<ProtocolCompositionType>()) {
for (auto memberTy : compositionTy->getMembers()) {
if (auto paramTy = containsParameterizedProtocolType(memberTy))
return paramTy;
}
}

return Type();
}

/// Check the inheritance clause of a type declaration or extension thereof.
///
/// This routine performs detailed checking of the inheritance clause of the
Expand Down Expand Up @@ -212,10 +227,10 @@ static void checkInheritanceClause(
inheritedAnyObject = { i, inherited.getSourceRange() };
}

if (inheritedTy->is<ParameterizedProtocolType>()) {
if (auto paramTy = containsParameterizedProtocolType(inheritedTy)) {
if (!isa<ProtocolDecl>(decl)) {
decl->diagnose(diag::inheritance_from_parameterized_protocol,
inheritedTy);
paramTy);
}
continue;
}
Expand All @@ -232,9 +247,15 @@ static void checkInheritanceClause(
continue;
}

// Classes and protocols can inherit from subclass existentials.
// For classes, we check for a duplicate superclass below.
// For protocols, the requirement machine emits a requirement
// conflict instead.
if (isa<ProtocolDecl>(decl))
continue;

// AnyObject is not allowed except on protocols.
if (layout.hasExplicitAnyObject &&
!isa<ProtocolDecl>(decl)) {
if (layout.hasExplicitAnyObject) {
decl->diagnose(diag::inheritance_from_anyobject);
continue;
}
Expand All @@ -243,12 +264,6 @@ static void checkInheritanceClause(
if (!layout.explicitSuperclass)
continue;

// Classes and protocols can inherit from subclass existentials.
// For classes, we check for a duplicate superclass below.
// For protocols, the GSB emits its own warning instead.
if (isa<ProtocolDecl>(decl))
continue;

assert(isa<ClassDecl>(decl));
assert(canHaveSuperclass);
inheritedTy = layout.explicitSuperclass;
Expand Down
56 changes: 38 additions & 18 deletions lib/Sema/TypeCheckType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -629,15 +629,8 @@ static Type applyGenericArguments(Type type, TypeResolution resolution,
auto &diags = ctx.Diags;

if (auto *protoType = type->getAs<ProtocolType>()) {
// Build ParameterizedProtocolType if the protocol has a primary associated
// type and we're in a supported context (for now just generic requirements,
// inheritance clause, extension binding).
if (!resolution.getOptions().isParameterizedProtocolSupported()) {
diags.diagnose(loc, diag::parameterized_protocol_not_supported);
return ErrorType::get(ctx);
}

auto *protoDecl = protoType->getDecl();

auto assocTypes = protoDecl->getPrimaryAssociatedTypes();
if (assocTypes.empty()) {
diags.diagnose(loc, diag::protocol_does_not_have_primary_assoc_type,
Expand All @@ -657,6 +650,18 @@ static Type applyGenericArguments(Type type, TypeResolution resolution,
return ErrorType::get(ctx);
}

// Build ParameterizedProtocolType if the protocol has a primary associated
// type and we're in a supported context (for now just generic requirements,
// inheritance clause, extension binding).
if (!resolution.getOptions().isParameterizedProtocolSupported()) {
diags.diagnose(loc, diag::existential_requires_any,
protoDecl->getDeclaredInterfaceType(),
protoDecl->getExistentialType(),
/*isAlias=*/isa<TypeAliasType>(type.getPointer()));

return ErrorType::get(ctx);
}

// Disallow opaque types anywhere in the structure of the generic arguments
// to a parameterized existential type.
if (options.is(TypeResolverContext::ExistentialConstraint))
Expand Down Expand Up @@ -3921,18 +3926,33 @@ TypeResolver::resolveCompositionType(CompositionTypeRepr *repr,
}

// FIXME: Support compositions involving parameterized protocol types,
// like Collection<String> & Sendable, etc.
if (ty->isConstraintType() &&
!ty->is<ParameterizedProtocolType>()) {
auto layout = ty->getExistentialLayout();
if (auto superclass = layout.explicitSuperclass)
if (checkSuperclass(tyR->getStartLoc(), superclass))
continue;
if (!layout.getProtocols().empty())
// like 'any Collection<String> & Sendable', etc.
if (ty->isConstraintType()) {
if (ty->is<ProtocolType>()) {
HasProtocol = true;
Members.push_back(ty);
continue;
}

Members.push_back(ty);
continue;
if (ty->is<ParameterizedProtocolType>() &&
options.isParameterizedProtocolSupported() &&
options.getContext() != TypeResolverContext::ExistentialConstraint) {
HasProtocol = true;
Members.push_back(ty);
continue;
}

if (ty->is<ProtocolCompositionType>()) {
auto layout = ty->getExistentialLayout();
if (auto superclass = layout.explicitSuperclass)
if (checkSuperclass(tyR->getStartLoc(), superclass))
continue;
if (!layout.getProtocols().empty())
HasProtocol = true;

Members.push_back(ty);
continue;
}
}

diagnose(tyR->getStartLoc(),
Expand Down
6 changes: 3 additions & 3 deletions lib/Sema/TypeCheckType.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ enum class TypeResolverContext : uint8_t {
/// Whether we are in the constraint type of an existential type.
ExistentialConstraint,

/// Whether we are in a requirement of a generic declaration.
/// Whether we are in the constraint type of a conformance requirement.
GenericRequirement,

/// Whether we are in a same-type requirement of a generic
Expand Down Expand Up @@ -288,13 +288,13 @@ class TypeResolutionOptions {
switch (context) {
case Context::Inherited:
case Context::ExtensionBinding:
case Context::TypeAliasDecl:
case Context::GenericTypeAliasDecl:
case Context::GenericRequirement:
case Context::ExistentialConstraint:
case Context::MetatypeBase:
return true;
case Context::None:
case Context::TypeAliasDecl:
case Context::GenericTypeAliasDecl:
case Context::InExpression:
case Context::ExplicitCastExpr:
case Context::ForEachStmt:
Expand Down
32 changes: 26 additions & 6 deletions test/type/parameterized_existential.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
// RUN: %target-typecheck-verify-swift -requirement-machine-protocol-signatures=on -requirement-machine-inferred-signatures=on -disable-availability-checking

protocol Sequence<Element> {
protocol Sequence<Element> { // expected-note {{'Sequence' declared here}}
associatedtype Element
}

// 'any' is required here

func takesSequenceOfInt1(_: Sequence<Int>) {}
// expected-error@-1 {{use of protocol 'Sequence' as a type must be written 'any Sequence'}}

func returnsSequenceOfInt1() -> Sequence<Int> {}
// expected-error@-1 {{use of protocol 'Sequence' as a type must be written 'any Sequence'}}

struct ConcreteSequence<Element> : Sequence {}

extension Sequence {
Expand Down Expand Up @@ -65,10 +73,22 @@ func saturation(_ dry: any Sponge, _ wet: any Sponge<Int, Int>) {
// expected-note@-1 {{did you mean to use 'as!' to force downcast?}}
}

protocol Pair<X, Y> where Self.X == Self.Y {
associatedtype X
associatedtype Y
func typeExpr() {
_ = Sequence<Int>.self
// expected-error@-1 {{use of protocol 'Sequence' as a type must be written 'any Sequence'}}

_ = any Sequence<Int>.self
// expected-error@-1 {{'self' is not a member type of protocol 'parameterized_existential.Sequence<Swift.Int>'}}

_ = (any Sequence<Int>).self
}

func splay(_ x: some Pair<Int, String>) -> (Int, String) { fatalError() }
// expected-error@-1 {{no type for 'some Pair<Int, String>.X' can satisfy both 'some Pair<Int, String>.X == String' and 'some Pair<Int, String>.X == Int'}}
/// Not supported as a protocol composition term for now

protocol SomeProto {}

func protocolCompositionNotSupported1(_: SomeProto & Sequence<Int>) {}
// expected-error@-1 {{non-protocol, non-class type 'Sequence<Int>' cannot be used within a protocol-constrained type}}

func protocolCompositionNotSupported2(_: any SomeProto & Sequence<Int>) {}
// expected-error@-1 {{non-protocol, non-class type 'Sequence<Int>' cannot be used within a protocol-constrained type}}
55 changes: 36 additions & 19 deletions test/type/parameterized_protocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ protocol Invalid5<Element, Element> {

/// Test semantics

protocol Sequence<Element> { // expected-note {{'Sequence' declared here}}
protocol Sequence<Element> {
associatedtype Element
// expected-note@-1 {{protocol requires nested type 'Element'; do you want to add it?}}
// expected-note@-1 2{{protocol requires nested type 'Element'; do you want to add it?}}
}

extension Sequence {
Expand Down Expand Up @@ -180,31 +180,48 @@ extension Sequence<Int> {
}


/// Cannot use parameterized protocol as the type of a value
/// Constraint aliases

func takesSequenceOfInt1(_: Sequence<Int>) {}
// expected-error@-1 {{protocol type with type arguments can only be used as a generic constraint}}
typealias SequenceOfInt = Sequence<Int>
typealias SequenceOf<T> = Sequence<T>

func returnsSequenceOfInt1() -> Sequence<Int> {}
// expected-error@-1 {{protocol type with type arguments can only be used as a generic constraint}}
// CHECK-LABEL: .testConstraintAlias1@
// CHECK-NEXT: Generic signature: <T where T : Sequence, T.[Sequence]Element == Int>
func testConstraintAlias1<T : SequenceOfInt>(_: T) {}

func takesSequenceOfInt2(_: any Sequence<Int>) {}
// CHECK-LABEL: .testConstraintAlias2@
// CHECK-NEXT: Generic signature: <T where T : Sequence, T.[Sequence]Element == String>
func testConstraintAlias2<T : SequenceOf<String>>(_: T) {}

func returnsSequenceOfInt2() -> any Sequence<Int> {}

func typeExpr() {
_ = Sequence<Int>.self
// expected-error@-1 {{protocol type with type arguments can only be used as a generic constraint}}
/// Protocol compositions

_ = any Sequence<Int>.self
// expected-error@-1 {{'self' is not a member type of protocol 'parameterized_protocol.Sequence<Swift.Int>'}}
// CHECK-LABEL: .testComposition1@
// CHECK-NEXT: Generic signature: <T where T : Sendable, T : Sequence, T.[Sequence]Element == Int>
func testComposition1<T : Sequence<Int> & Sendable>(_: T) {}

_ = (any Sequence<Int>).self
// CHECK-LABEL: .testComposition2@
// CHECK-NEXT: Generic signature:
// CHECK-NEXT: Canonical generic signature: <τ_0_0 where τ_0_0 : Sendable, τ_0_0 : Sequence, τ_0_0.[Sequence]Element == Int>
func testComposition2(_: some Sequence<Int> & Sendable) {}

// CHECK-LABEL: parameterized_protocol.(file).TestCompositionProtocol1@
// CHECK: Requirement signature: <Self where Self.[TestCompositionProtocol1]S : Sendable, Self.[TestCompositionProtocol1]S : Sequence, Self.[TestCompositionProtocol1]S.[Sequence]Element == Int>
protocol TestCompositionProtocol1 {
associatedtype S : Sequence<Int> & Sendable
}

/// Not supported as a protocol composition term for now
struct TestStructComposition : Sequence<Int> & Sendable {}
// expected-error@-1 {{cannot inherit from protocol type with generic argument 'Sequence<Int>'}}
// expected-error@-2 {{type 'TestStructComposition' does not conform to protocol 'Sequence'}}


protocol SomeProto {}
/// Conflicts

protocol Pair<X, Y> where Self.X == Self.Y {
associatedtype X
associatedtype Y
}

func protocolCompositionNotSupported(_: SomeProto & Sequence<Int>) {}
// expected-error@-1 {{non-protocol, non-class type 'Sequence<Int>' cannot be used within a protocol-constrained type}}
func splay(_ x: some Pair<Int, String>) -> (Int, String) { fatalError() }
// expected-error@-1 {{no type for '(some Pair<Int, String>).X' can satisfy both '(some Pair<Int, String>).X == String' and '(some Pair<Int, String>).X == Int'}}