Hello, Swift Evolution!
I've been working on a pitch for enabling extensions of bound generic types using angle-bracket syntax, e.g. extension Array<String> {}
. This feature was subsetted out of the original pitch for SE-0346. I've copied the current proposal draft below.
Please let me know your questions, thoughts, and other constructive feedback!
-Holly
[Pitch] Extensions on bound generic types
- Proposal: SE-NNNN
- Authors: Holly Borla
- Review Manager: TBD
- Status: Awaiting implementation
- Implementation: apple/swift#41172, gated behind the frontend flag
-enable-experimental-bound-generic-extensions
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.
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 GenericType<T1, T2> { ... }
extension GenericType<Arg1, Arg2> { ... }
is equivalent to
extension GenericType where T1 == Arg1, T2 == Arg2 { ... }
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 Array<_> {} // error: Cannot extend a type that contains placeholders
Similarly, 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.
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 { ... }
Note that SE-0346 and this proposal solidify using the extension <T>
syntax for parameterized extensions, because this proposal specifies that a type in angle brackets after a generic type name in an extension is an application of type arguments, not a declaration of new type parameters.