Skip to content

Latest commit

 

History

History
196 lines (128 loc) · 8.28 KB

0361-bound-generic-extensions.md

File metadata and controls

196 lines (128 loc) · 8.28 KB

Extensions on bound generic types

Contents

Introduction

Specifying the type arguments to a generic type in Swift is almost always written in angle brackets, such as Array<String>. Extensions are a notable exception, and if you attempt to extend Array<String>, the compiler reports the following error message:

extension Array<String> { ... } // error: Constrained extension must be declared on the unspecialized generic type 'Array' with constraints specified by a 'where' clause

As the error message suggests, this extension must instead be written using a where clause:

extension Array where Element == String { ... }

This proposal removes this limitation on extensions, allowing you to write bound generic extensions the same way you write bound generic types everywhere else in the language.

Swift evolution discussion thread: [Pitch] Extensions on bound generic types.

Motivation

Nearly everywhere in the language, you write bound generic types using angle brackets after the generic type name. For example, you can write a typealias to an array of strings using angle brackets, and extend that type using the typealias:

typealias StringArray = Array<String>

extension StringArray { ... }

With SE-0346, we can also declare a primary associated type, and bind it in an extension using angle-brackets:

protocol Collection<Element> {
  associatedtype Element
}

extension Collection<String> { ... }

Not allowing this syntax directly on generic type extensions is clearly an artificial limitation, and even the error message produced by the compiler suggests that the compiler understood what the programmer was trying to do:

extension Array<String> { ... } // error: Constrained extension must be declared on the unspecialized generic type 'Array' with constraints specified by a 'where' clause

This limitation is confusing, because programmers don’t understand why they can write Array<String> everywhere except to extend Array<String>, as evidenced by the numerous questions about this limitation here on the forums, such as this thread.

Proposed solution

I propose to allow extending bound generic types using angle-brackets for binding type arguments, or using sugared types such as [String] and Int?.

The following declarations all express an extension over the same type:

extension Array where Element == String { ... }

extension Array<String> { ... }

extension [String] { ... }

Detailed design

A generic type name in an extension can be followed by a comma-separated type argument list in angle brackets. The type argument list binds the type parameters of the generic type to each of the specified type arguments. This is equivalent to writing same-type requirements in a where clause. For example:

struct Pair<T, U> {}

extension Pair<Int, String> {}

is equivalent to

extension Pair where T == Int, U == String {}

A type argument list applied to an extended type name must specify all type arguments; constraining only a subset of the type parameters in an extension must still use a where clause:

struct Pair<T, U> {}

extension Pair<Int> {} // error: Generic type 'Pair' specialized with too few type parameters (got 1, but expected 2)

extension Pair where T == Int {} // okay

The types specified in the type argument list must be concrete types. For example, you cannot extend a generic type with placeholders as type arguments:

extension Pair<Int, _> {} // error: Cannot extend a type that contains placeholders

Rationale: When _ is used as a type placeholder, it directs the compiler to infer the type at the position of the underscore. Using _ in a bound generic extension would introduce a subtly different meaning of _, which is to leave the type at that position unconstrained, so Pair<Int, _> would mean different things in different contexts.

Name lookup of the type arguments is performed outside the extension context, so the type parameters of the generic type cannot appear in the type argument list:

extension Array<Element> {} // error: Cannot find type 'Element' in scope

If a generic type has a sugared spelling, the sugared type can also be used to extend the generic type:

extension [String] { ... } // Extends Array<String>

extension String? { ... } // Extends Optional<String>

Source compatibility

This change has no impact on source compatibility.

Effect on ABI stability

This is a syntactic sugar change with no impact on ABI.

Effect on API resilience

This change has no impact on API resilience. Changing an existing bound generic extension using a where clause to the sugared syntax and vice versa is a resilient change.

Alternatives considered

Reserving syntax for parameterized extensions

Using angle brackets after an extended type name as sugar for same-type requirements prevents this syntax from being used to declare a parameterized extension. Alternatively, extension Array<T, U> { ... } could mean an extension that declares two new type parameters T and U, rather than an (invalid) application of type arguments to Array's type parameters. However, SE-0346 already introduced this syntax as sugar for same-type requirements on associated types:

protocol Collection<Element> {
  associatedtype Element
}

// Already sugar for `extension Collection where Element == String`
extension Collection<String> { ... }

Instead of reserving this syntax for parameterized extensions, type parameters could be declared in angle brackets after the extension keyword, which will help indicate that the type parameters belong to the extension:

// Introduces new type parameters `T` and `U` for the APIs
// in this extension.
extension <T, U> Array { ... }

Future directions

Parameterized extensions

This proposal does not provide parameterized extensions, but a separate proposal could build upon this proposal to allow extending a generic type with more sophisticated constraints on the type parameters:

extension <Wrapped> Array<Optional<Wrapped>> { ... }

extension <Wrapped> [Wrapped?] { ... }

Parameterized extensions could also allow using the shorthand some syntax to write generic extensions where a type parameter has a conformance requirement:

extension Array<some Equatable> { ... }

extension [some Equatable] { ... }

Writing the type parameter list after the extension keyword applies more naturally to extensions over structural types. With this syntax, an extension over all two-element tuples could be spelled

extension <T, U> (T, U) { ... }

This syntax also generalizes to variadic type parameters, e.g. to extend all tuple types to provide a protocol conformance:

extension <T...> (T...): Hashable where T: Hashable { ... }