Source breaking ability to override associated type in protocol extension

I think it is worth exposing this to the community before filing a bug.

This came across my mind while helping out the author of this topic.

protocol P {
    
    associatedtype E
    
    func foo1(_ arg: E)
    func foo2(_ arg: E)
}

protocol P1: P {...} //  (*) See bottom line

class Foo: P {
    
    typealias E = String
    
    func foo1(_ arg: E) {}
    
    func foo2(_ arg: E) {}
}

extension P { typealias E = Bool } /* This will break Foo...
...and any other conformances to P and *: P 
 */

// (*)  All further occurrences of E will refer to the type alias

The ability to declare type aliases that share names with associated types in protocol extensions is not only senseless (that's what same-type constraints are for), but also source-breaking (retroactively as well). It results in broken conformances and modified inheriting protocols [ see (*) ], the conformances to which will also be broken. Moreover, doing so renders useless the flexibility an associated type provides. The typealias either shadows or overrides the associatedtype – every occurrence of the associated type in the protocol will refer to the type alias – which is literally the same as using a concrete type (It can't be overridden).

A similar situation happens when declaring a typealias in an extension of a type that conforms to a protocol with an equally named defaulted associatedtype.

protocol P {
    associatedtype A = String
    
    func foo(_ arg: A)
}

class Foo: P {
    func foo(_ arg: String) {}
}

extension Foo {
    typealias A = Bool
} 
// No redeclaration error and the conformance is broken
2 Likes

Filed SR-7324.

I completely agree, and would love to make it ill-formed to declare a typealias in a protocol (or protocol extension) that has the same name as an associated type in that protocol or any protocol it inherits.

The main problem is that, between the introduction of typealiases in protocols (SE-0092) and the ability to write where clauses on protocols and associated types (SE-0142), a lot of code got written [*] that relied on this behavior to emulate same-type constraints, e.g.,

protocol P {
  associatedtype A
}

protocol Q: P {
  typealias A = Int
}

The warnings present in Swift 4 and newer nudge users toward the more direct expression of this behavior, e.g.,

warning: typealias overriding associated type 'A' from protocol 'P' is better expressed as same-type constraint on the protocol

which can be suppressed by writing Q as:

protocol Q: P where A == Int { }

Personally, I'd support a proposal to make these cases redeclaration errors. Such a proposal should go through the various cases that crop up with inheritance as well (typealias in inherited protocol, typealias in inheriting protocol, etc.).

Doug

[*] ... in which the author uses passive voice to avoid placing blame on developers for emulating an obviously-missing feature of the generics system

1 Like

Hi Douglas,

Do you mean code that is part of the Swift project?

Thank you for the advice, I will develop a proposal in the next few days. Or I can move this thread to the pitches section and edit it step by step.

P.S.
It seems I accidentally found two bugs related to same-type constraints – SR-7336

We can fix code that's part of the Swift project; it's code outside the control of the Swift project (e.g., code in the source compatibility suite) that's the problem, because tightening the rules will break source compatibility. That might be fine as a "Swift 5" thing because we've been warning about this pattern since Swift 4.

Doug

1 Like