This might be more suitable for posting on a GitHub Issue rather than on the Swift Forum, but I am posting it here because it involves recent Swift Evolution content.
First, the following code produces a Fix-it.
func takeAnyCollection(_ collection: any Collection) {
// Inferred result type 'any Collection' requires explicit coercion due to loss of generic requirements
var result = collection.dropFirst()
}
// cf
protocol Collection<Element>: Sequence {
// ...
associatedtype SubSequence : Collection = Slice<Self> where Self.Element == Self.SubSequence.Element, Self.SubSequence == Self.SubSequence.SubSequence
@inlinable public func dropFirst(_ k: Int = 1) -> Self.SubSequence
// ...
}
I have read this thread and also engaged in similar discussions in the review of SE-0352, and I understand this behavior. This is roughly because some information carried by Self.SubSequence is lost in the return value.
What I don't understand is why this error does not occur with startIndex.
func takeAnyCollection(_ collection: any Collection) {
// 👌
var result = collection.startIndex
}
// cf
protocol Collection<Element>: Sequence {
// ...
associatedtype Index : Comparable where Self.Index == Self.Indices.Element, Self.Indices.Element == Self.Indices.Index, Self.Indices.Index == Self.SubSequence.Index
var startIndex: Self.Index { get }
// ...
}
Here, as some information of Self.Index is lost, I believe you still need to write an explicit as any Comparable. I'd like to know why this is not necessary.
Hmm, the rationale for this explicit coercion was that the inferred “upper bound” type would change from version to version of Swift for a type any Collection<T>—i.e., there would be some information lost in one version of Swift but not the next.
This is not the case for an existential that doesn’t constrain the primary associated type. So, unless I’m missing something, the inferred upper bound for the return type of (any Collection).dropFirst() can’t change, just as the inferred upper bound for the type of (any Collection).startIndex can’t change. Neither should require explicit type coercion.
This is not the case for an existential that doesn’t constrain the primary associated type
The concerns outlined in SE-0352 assume the scenario where constraints can be placed on non-primary associated types (any Collection<.Index == Int>). I believe it is reasonable to be concerned about the potential change in the upper-bound type when the type system is strengthened for cases other than the primary associated type.
However, I believe this may not hold true if the Swift type system undergoes an extreme evolution.
func takeAnyCollection(_ collection: any Collection) {
// upper bound: `(type(of: collection).Index)`
// but currently inferred as `Any`
var index = collection.startIndex
// this will be always valid
index = collection.endIndex
// but how about this?
index = 42
}
While such an evolution is highly unlikely, isn't Any inappropriate as an inferred upper bound for Self.Index in this case?
This is how I understand the behavior of dropFirst case, but I'm surprised to see how first works.
I must admit I'm pointing very unlikely concern
If it will be said that "such concerns can be disregarded, and what should be fixed is the behavior of dropFirst() is the issue," then that is understandable.
What you are showing is the case where there's no constraint on any associated type whatsoever, primary or not—any Collection—and I can't see a rationale why the upper bound type of startIndex can be anything except Anyany Comparable for a value of such type. It does not make sense to me that dropFirst() requires explicit type coercion in this scenario.
Ah, this would be from my typo, sorry for confusion.
It will be apparent that this case requires explicit coercion. (Maybe I should report a bug on this)
func returnStartIndexAndEndIndex<C: Collection>(_ c: C) -> (C.Index, C.Index) {
return (c.startIndex, c.endIndex)
}
func takeAnyCollection(_ collection: any Collection) {
// 👌 (s and e is both inferred as any Comparable)
// but type(of: s) == type(of: e) must be expressed in upper bound.
var (s, e) = returnStartIndexAndEndIndex(collection)
}
And I'd considered this is also the case, but it seems I was wrong.
func takeAnyCollection(_ collection: any Collection) {
// it must be broken
var (s, e) = returnStartIndexAndEndIndex(collection)
// and this is effectively equal to
var s2 = collection.startIndex
var e2 = collection.endIndex
// because `collection` is `let` constant
}
My understanding is that the rational is not limited in primary associated types, but any possible source breakage in a future version of swift.
As long as the inferred type is not the true upper bound for the value, there is possibility for source breakage in the future version of Swift, as Swift can improve its capability of type expression and express more narrow types.
About the 'bug', in the case of some Collection, this is working as expected.
func takeSomeCollection(_ collection: some Collection) {
var (s, e) = returnStartIndexAndEndIndex(collection)
s = e // valid
s = 42 // invalid
}
In the case of any Collection, while type(of: s) == type(of: e) is also true because the returned type is a tuple of type (T, T), it is not expressed in Swift 5.9.
If the future Swift gets the ability to treat type(of: s) == type(of: e) constraint, s = 42 will become invalid, and it will cause a source breakage. That's what I said bug of check here.
There's no plan, near-term or distant, that I'm aware of which would allow the static type system to represent dynamic type relationships like that. Of course, if we ever fundamentally overhaul the design of existential type opening, then we would need to make sure there is a workable migration path. Explicit type coercion is designed to ensure a migration path to the language of today, not some unknown future.
As far as I can tell! It's possible I'm missing some constraint that is expressible (either now or in the future with only library-level changes) but currently erased somehow, but I can't see it. That said, I think we agree that eitherdropFirst() shouldn't require explicit type coercion orstartIndex should.