The policy of thwarting calls when covariant erasure would drop constraints that could be expressed in the future makes a lot more sense now that I understand the cause of a potential source break. I like the idea of requiring an explicit coercion rather than rejecting the call altogether. Making use of contextual type information (which is what @ensan-hcl initially suggested) is not always possible, as in a reference to an overloaded function that is applied to a parameter of erased function type:
func foo(_: any Collection)
func foo(_: any Collection<Int>)
protocol P {
associatedtype C: Collection<Int>
func doSomething(fn: (C) -> Void)
}
let p: P
// With today's erasure, p.doSomething
// has type '((any Collection) -> Void) -> Void'.
p.doSomething(fn: foo)
Though I didn't have any confusion about as any P as suppression mechanism, as I thought about it more and more, I became more and more confused.
let x = PX() as any P
This code introduces a constant x whose type is any P. It's fine.
func foo<T: P>(_ value: T) {}
foo(x)
When user call foo with x, as said in proposal, x is implicitly opened and a value of PX is passed into foo. It's of course fine too. Also, x as any P is totally equal to x in terms of both value and type. Therefore, the following call should be equal to foo(x)
foo(x as any P)
However, this code, works unexpectedly. It would cause a compile error because implicit opening doesn't happen.
What on earth is going on? I cannot come up convincing explanation for this behavior.
Iāve verified that yes, in the implementation provided above, this does happen. Causing further confusion, any intervening expression between the as any P and the function call makes the error disappear again:
protocol P { }
struct S: P { }
let x = S() as any P
func foo<T: P>(_ value: T) {}
foo(x) // ā
foo(x as any P) // ā compile error
foo((x as any P)) // ā parens arenāt enough, butā¦
foo((x as any P, "ignored").0) // ā ā¦this works
// Or evenā¦
extension P {
func itself() -> Self { self }
}
foo((x as any P).itself()) // ā
Iām not delighted with this special behavior of as when it appears in one specific position. However, reading through the discussion and playing with examples, Iām hard pressed to think of a realistic case where somebody would type as any P for a value whose type is already any P and have some intent other than āplease donāt unwrap.ā
We have somewhat similar behavior today with regards to e.g., coercion from Optional to Any:
func f(_ x: Any) {}
let x: Int? = 0
f(x) // warning: expression implicitly coerced from 'Int?' to 'Any'
where the fix is to write:
f(x as Any)
Under the simplest model for as that @ensan-hcl discusses, as Any for an argument already passed as Any should be meaningless. But as is imbued with an additional meaning which is something akin to "I want the result of this expression to really be type T." This meaning is only available in specific contexts (I'm not sure if it appears anywhere else besides the Optional-to-Any warning fix?) but I don't think it's an unreasonable meaning for as. I'd prefer this use of as to coming up with a whole new syntax for expressing the idea of "use this type exactly without implicit coercions."
as is useful for influencing type deduction. The simplest case is let x = 12 as Float, but you can also use it to e.g. control which override is taken.
This is a good point, and there are deeper parallels here between unwrapping optionals and unwrappingā¦erā¦opening existentials: unwrapping an optional requires a new variable for the unwrapped value (with some sugar like ?? and ?. that can hide it); unwrapping/opening an existential requires a new type variable for the unwrapped type (with some sugar that can hide it). So the precedent does carry some weight.
Sure, and as is also used for things like disambiguating overloads and bridging conversions with Objective-C types. But I agree with @ensan-hcl that there's something a little odd about using and as coercion on an expression which would (ostensibly) already be coerced to the type in question implicitly. And I also agree with @Paul_Cantrell that I can't think of a reasonable meaning for such a use of asother than "I really do mean to use this type, it's not a mistake and please don't change it on me."
Also worth noting: my comparison of as any P with the existing as Any isn't perfect, because in the as Any case it's just requiring the user to be explicit in source about a coercion that will happen the same way with or without the as, whereas with as any P we're explicitly suppressing a coercion that the compiler would perform without our guidance to the contrary. But the two still feel closely linked in my mind, enough so that I'm not troubled by the reuse of as for this purpose.