Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time

where clauses on contextually generic declarations

Introduction

This proposal aims to lift the restriction on attaching where clauses to member declarations that can reference only outer generic parameters. Simply put, this means the 'where' clause cannot be attached error will be relaxed for most declarations nested inside generic contexts:

struct Box<Wrapped> {
    func boxes() -> [Box<Wrapped.Element>] where Wrapped: Sequence { ... }
}

The current proposal only covers member declarations that already support a generic parameter list, that is, properties and unsupported constraints on protocol requirements are out of scope. To illustrate, the following remains invalid:

protocol P {
    // error: Instance method requirement 'foo(arg:)' cannot add constraint 'Self: Equatable' on 'Self'
    func foo() where Self: Equatable  
}

class C {
    // error: type 'Self' in conformance requirement does not refer to a generic parameter or associated type
    func foo() where Self: Equatable  
}

Whereas placing constraints on an extension member rather than the extension itself becomes possible:

extension P {
    func bar() where Self: Equatable { ... }
}

Swift-evolution thread

Motivation

Today, a contextual where clause on a member declaration can only be expressed indirectly by placing the member inside a dedicated constrained extension. Unless constraints are identical, every such member requires a separate extension. This lack of flexibility is a potential obstacle to grouping semantically related APIs, stacking up constraints, and the legibility of heavily generic interfaces.

It is reasonable to expect a where clause to "work" anywhere a constraint is meaningful, which is to say both of these structuring styles should be available to the user:

// 'Foo' can be any kind of nominal type declaration.
// For a protocol, 'T' would be Self or an associatedtype.
struct Foo<T>  

extension Foo where T: Sequence, T.Element: Equatable {
    func slowFoo() { ... }
}
extension Foo where T: Sequence, T.Element: Hashable {
    func optimizedFoo() { ... }
}
extension Foo where T: Sequence, T.Element == Character {
    func specialCaseFoo() { ... }
}

extension Foo where T: Sequence, T.Element: Equatable {
    func slowFoo() { ... }

    func optimizedFoo() where T.Element: Hashable { ... }

    func specialCaseFoo() where T.Element == Character { ... }
}

A step towards generalizing where clause use is an obvious and farsighted improvement to the generics system with numerous future applications, including generic properties, opaque types, generalized existentials and constrained protocol requirements.

Detailed design

Overrides

Because contextual where clauses are effectively visibility constraints, overrides adopting this feature must be at least as visible as the overridden method:

class Base<T> {
    func foo() where T == Int { ... }
}

class Derived<T>: Base<T> {
    // OK, the substitutions for <T: Equatable> are a superset of those for <T == Int>
    override func foo() where T: Equatable { ... } 
}

Source compatibility and ABI stability

This is an additive change with no impact on the ABI and existing code.

Effect on API resilience

For public declarations in resilient libraries such as the Standard Library, moving a constraint from an extension to a member and vice versa will break the ABI due to subtle mangling differences as of the current implementation.