[Pitch] Light-weight same-type constraint syntax

It's bad enough that Swift already promotes generic arguments to implicit associated types when their names match, which I feel might actually be the motivation behind this proposal. But the names of a type's generic arguments should not ever leak, unless one explicitly exports them as associated types.

Apart from this we already have the unfortunate situation that the same syntax can mean completely different things (and be both valid and invalid, based on the specific context), just based on whether a generic argument has been implicitly promoted to an associatedtype, or not:

protocol Foo {
    associatedtype Bar

    init(bar: Self.Bar)
    
    func bar() -> Self.Bar
}

struct DummyA<Bar>: Foo {
    var _bar: Bar
    
    init(bar: Bar) {
        self._bar = bar
    }
    
    func bar() -> Bar {
        self._bar
    }
}

struct DummyB<Bar>: Foo {
    typealias Bar = Int
    
    var _bar: Self.Bar
    
    init(bar: Self.Bar) {
        self._bar = bar
    }
    
    func bar() -> Self.Bar {
        self._bar
    }
}

This code compiles just fine.

Notice however how the Bar in DummyA and the Self.Bar in DummyB both refer to the associatedtype Bar of Foo, yet writing DummyA with the valid syntax of DummyB like this fails…

struct DummyA<Bar>: Foo {
    // This line works, for whatever reason.
    var _bar: Self.Bar
    
    // error: 'Bar' is not a member type of generic struct 'DummyA<Bar>'
    init(bar: Self.Bar) {
        self._bar = bar
    }
    
    // error: 'Bar' is not a member type of generic struct 'DummyA<Bar>'
    func bar() -> Self.Bar {
        self._bar
    }
}

… as does any attempt of writing DummyB with the valid syntax of DummyA:

// error: type 'DummyB<Bar>' does not conform to protocol 'Foo'
struct DummyB<Bar>: Foo {
    typealias Bar = Int
    
    var _bar: Bar
    
    // note: candidate has non-matching type '(bar: Bar)'
    init(bar: Bar) {
        self._bar = bar
    }
    
    // note: candidate has non-matching type '<Bar> () -> Bar'
    func bar() -> Bar {
        self._bar
    }
}

(Why these snippets don't compile doesn't matter here. What matters is that the illusion of "generic arguments and associated types are the same" simply doesn't hold water in practice.)

The code above is semantically (and syntactically) inconsistent (ironically for syntactic consistency's sake) due to exactly the same orthogonality of generic arguments vs. associated types that we're dealing with in this proposal and it will lead to very similar issues.

And to make things worse: by having unified their syntax it is now much, much more difficult for the user to understand why code that works for one doesn't work for the other or worse: why the simple addition of a typealias suddenly broke the whole type.

The implicit promotion of generic arguments to associated types is a bug, not a feature.

Let's please not use it as a justification for making yet another mess, attempting to unify the syntax around generic arguments and associated types. Two wrongs don't make a right.

8 Likes