Protocol extensions inheriting protocols

Hi S/E,

I’ve prepared small patch to the Swift compiler which allows you to specify a protocol an extension is intending to conform to when creating a protocol extension. This means all types that adopt the protocol being extended conform to to the protocol being inherited from (in fact it works by adding the protocol being inherited from to the protocols the protocol being extended inherit from during validation of the extension.) As an optional feature the code checks that the protocol being extended conforms to the protocol being adopted rather than give an error about a type adopting the protocol not conforming.

The use case where this came up was the following:

extension FixedWidthInteger: ExpressibleByUnicodeScalarLiteral {
  @_transparent
  public init(unicodeScalarLiteral value: Unicode.Scalar) {
    self = Self(value.value)
  }
}

After this, all FixedWidthIntegers can be "expressed by" Strings that are UnicodeScalars.

Does anybody see any pitfalls with this suggestion? It feels like more of a bug fix of a combination of two existing features — Will it need to be subject to a full evolution?

3 Likes

The notation ProtocolA : ProtocolB means that one refines the other, which this does not do.

I think the feature is fine but what it is would be a close cousin of, if not just a specific use of, parameterized extensions:

extension<T: ProtocolA> T: ProtocolB

Is an extension with inheritance really “narrowing of” more than “add conformance to” as it is for a type? What are the advantages of syntax:

extension<T: ProtocolA> T: ProtocolB

over

extension ProtocolA: ProtocolB

? Why involve generic syntax ?

@johnno1962 does this basically make your patch equivalent to the following idea?

If we had to use any, some and potentially meta explicitly then it feels like you're proposing to allow to write extension some P : Q { ... }. Am I correct?

I have long wished for the ability to inject a new protocol underneath an existing one, so +1 if you can actually get it to work.

I cannot find it anymore, but I thought there was discussion about this a long time ago and this was found to be way more complicated that it looks on the surface. For example:

ModuleA:

public struct A : FixedWidthInteger { /* ... */ }

ModuleB:

extension FixedWidthInteger: ExpressibleByUnicodeScalarLiteral {
    public init(unicodeScalarLiteral value: Unicode.Scalar) { /* ... */ }
}

public func use<T>(_ value: T) -> T
    where T : FixedWidthInteger {
        return value + "1" // ← Used ExpressibleByUnicodeScalarLiteral
}

ModuleC:

import ModuleA
import ModuleB

let a = A(/* ... */)
print(use(a)) // ← Indirectly uses ExpressibleByUnicodeScalarLiteral,
// but where does the conformance come from?
// ModuleA didn’t have it. ModuleB didn’t have it.
// Does it live in ModuleC, synthesized by the compiler?
// Where does the compiler get the implementation from?
// And what about the fact that this would be conforming
// a type we don’t own to a protocol we don’t own?

How does your design handle these sorts of things?

That appears to be the semantics implied by the description, but the syntax used isn't able to distinguish conforming types from the existential. The syntax extension FixedWidthInteger: ExpressibleByUnicodeScalarLiteral implies that all conforming types and the existential are given a conformance. One of the nice things about some / any / meta is that it becomes possible to express the intended semantics (regardless of what the intent is).

I can’t comment on the subtleties being discussed here. I’m well behind on Opaque types. All I’m proposing that a protocol extension can be combined with a conformance and this can be achieved using a bit of a compiler hack of adding the conformance to the protocol being extended internally. If this is never going to work or isn’t even desirable let me know.