Should the type "Double & String" be "Never"?

Given the following protocols

protocol P1 {
    var foo: Double { get }
}

protocol P2 {
    var foo: String { get }
}

it is allowed to refer to the existential protocol type P1 & P2, even though it shouldn't be possible.
In theory we should have

(P1 & P2).self == Never.self

since foo's type has to be "Double & String", which is not possible.

func bar<T: P1 & P2>(_ x: T) {
    x.foo  // suggested as of type Double in autocompletion
           // shown as of type String in the Quick Help pop-up
}

Of course, no concrete type can conform to P1 & P2 so the bar function can never be called.
However, I'm wondering if the compiler should consider T == Never to prevent foo from being suggested at all.

There isn't a public way to disambiguate, but P1.foo and P2.foo are distinct, and a type can conform to both protocols (the following code compiles using an undocumented feature):

protocol P1 {
    var foo: Double { get }
}
protocol P2 {
    var foo: String { get }
}
struct S: P1 {
    var foo: Double { 42 }
}
extension S: P2 {
    @_implements(P2, foo)
    var bar: String { "Hello" }
}

let p2: P2 = S()
print(p2.foo) // "Hello"

[Edit: or see @michelf's demonstration with default implementations]

2 Likes

Imho the "cleaner" solution would be to allow a type to have both properties. It might be weird, and it probably should be avoided — but it has always(?) been valid for methods.

That's not the case. This works fine:

protocol P1 {
	var foo: Double { get }
}
protocol P2 {
	var foo: String { get }
}

extension P1 {
	var foo: Double { 1 }
}
extension P2 {
	var foo: String { "S" }
}

extension String: P1, P2 {}
extension Double: P1, P2 {}

func bar<T: P1 & P2>(_ x: T) {
	print(x.foo as Double)
	print(x.foo as String)
}
bar("text")
bar(8.18)

You can even make it semi-useful by implementing one version of the property in each type:

extension Double: P1 & P2 {
	var foo: Double { self }
}
extension String: P1 & P2 {
	var foo: String { self }
}

Not that I'd recommend writing this kind of code (other than to exercise the language).

5 Likes

Variable overloading. Based on the solutions of @xwu and @michelf looks like we already have it in practice.



So here Xcode should suggest both foo: Double and foo: String for completeness.

Ironically, the premises were aiming at an intersection type "Double & String", but instead we got a union type "Double | String" (kind of).

Thanks for the explanations!

Not really. Again, the key is understanding that there are two distinct variables named foo.

I don't know how helpful could be thinking this way. Here foo is inferred to have type E1 & E2 (which is predictable).

protocol E1 {}
protocol E2 {}

protocol P1 {
    associatedtype A: E1
    var foo: A { get }
}
protocol P2 {
    associatedtype A: E2
    var foo: A { get }
}

func bar<T: P1 & P2>(_ x: T) {
    x.foo  // inferred to be of existential protocol type E1 & E2
}


Do we have two overlapping foo properties here too? One of a type conforming to E1 and one of a type conforming to E2?

1 Like

You’ve been misled here: foo is not of any existential type, and the interface is incorrect to suggest that E1 & E2 is the inferred type (it is not; the type is T.A). This would be worth filing a bug.

2 Likes

I see. The bug would be about the autocompletion only, since the Quick Help pop-up correctly shows T.A:

func bar<T: P1 & P2>(_ x: T) {
    let f = x.foo  // "let f: T.A" shown in Quick Help pop-up
}

Apart from that, I expect the type T.A to conform to E1 & E2. Is this assumption correct?

In today’s Swift, yes, because @_implements cannot (yet) be used for associated type requirements.

It seems to be an interface stylistic choice to only show protocol conformances instead of the full (correct) type signature. As an example:

  • in the first example foo's type signature is shown as foo(_ x: Collection)
  • in the second example the type signature doesn't take in account the class C1
  • in the last example max is shown as
max(_ x: Comparable, _ y: Comparable) -> Comparable

which with Unlock Existential Types for All Protocols may be misleading.

In the first three examples, typing x in the function body suggests x: Collection, x: P1 & P2 and x: P1 & P2 respectively, i.e. x's conformances instead of its type. Should it be filed as a bug?

Sounds similar to how some collection's Index is often referred to by the autocomplete interface as just Comparable (which is usually not the information I'm interested in at that moment).

Did it confuse you? Then it is a bug.

1 Like

Filed as SR-13653.

3 Likes
Terms of Service

Privacy Policy

Cookie Policy