Weird implicitly opened existential behaviour

I'm trying to understand what can and can't be done with this feature. In Xcode 14.0 beta 4 I get this behaviour:

func specific<T : Incredible>(_ x: T) {
}

func general(_ x: any Collection<any Incredible>) {
    specific(x.first!) // 1: Type 'any Incredible' cannot conform to 'Incredible'

    let z = x.first!
    specific(z) // 2: works

    for y in x {
        specific(y) // 3: Global function 'specific' requires that 'T' conform to 'Incredible'
    }
}

Leading to several observations and questions:

  1. It works fine with "some Collection...". Can this feature not be used with nested existentials? (Edit: code block 1 still doesn't work with "some Collection".)
  2. The compiler doesn't care if I spell it "any Collection", without the second any keyword. I assume this is a limitations in the error reporting, and it's treated the same?
  3. That line 2 works and line 1 doesn't surely must be a compiler error? They should do exactly the same thing. And note that line 2 does work, i.e. it definitely executes the code correctly.
  4. In line 3, it can't be made to work with introduction of more constants.
  5. Edit: In line 3, the type of y seems to be "Any".

Does anyone have any input on this? Thanks in advance.

@hborla is the authority. But I think you’re hitting a couple of known issues.

Specifically, this sounds familiar to me. It might be a known issue.

any Collection<any Incredible> is sugar for any Collection where Element == any Incredible. If we drop the any before Incredible, we get any Collection where Element == Incredible. But a bare protocol name in a same-type constraint is just the old syntax for any P, so they’re equivalent.

I do think it would be good to force the new syntax in a primary associated type constraint.

Implicit existential opening is specific to arguments, so I would not be surprised if there’s a subtle interaction with unwrapping syntax in argument position. Sounds worthy of a GitHub issue.

This definitely seems like a bug.

Thanks for the insight. Line 3 (or code block 3; the for-loop) can't be made to work with extra constant, like I mentioned, but it can using a trampoline function like this:

func specific(_ x: some Incredible) {
}

func trampoline(_ x: some Collection<any Incredible>) {
    for y in x {
        specific(y)
    }
}

func general(_ x: any Collection<any Incredible>) {
    trampoline(x)
}

While I'm at it: Is there any way to print or debug the compile-time type of an expression? If I print type(of:) I get the runtime type, but I am trying to figure out what the compiler knows the type to be.

You can get the static type of a value by dispatching through a generic context:

func staticType<T>(of _: T) -> String {
    return "\(T.self)"
}

print(type(of: 1 as Any)) // → "Int"
print(staticType(of: 1 as Any)) // → "Any"

Courtesy of @Joe_Groff: Printing the Static Type of a Value - #2 by Joe_Groff

6 Likes