Hi all,
Consider this code,
class Base : Collection {
var startIndex: Int { return 0 }
var endIndex: Int { return 10 }
func index(after i: Int) -> Int { return i + 1 }
subscript(index: Int) -> Int { return index }
}
We infer the associated type ‘Iterator’ as ‘IndexingIterator<Base>’. I can use
an instance of Base as a sequence just fine:
for x in Base() {} // OK
Now if I subclass Base, the associated type is still ‘IndexingIterator<Base>’:
class Derived : Base {}
However the implementation of makeIterator is defined in a constrained
extension by the standard library,
extension Collection where Self.Iterator == IndexingIterator<Self> {
func makeIterator() -> IndexingIterator<Self> { … }
}
So I cannot call it on a subclass:
for x in Derived() {} // fails
The error is bizarre, "'IndexingIterator<Base>' is not convertible to
'IndexingIterator<Derived>’” — I’m not doing a conversion here.
If you try to call makeIterator() directly, you get an ambiguity error instead:
col.swift:17:5: error: ambiguous reference to member 'makeIterator()'
_ = Derived().makeIterator()
^~~~~~~~~
Swift.Collection:6:17: note: found this candidate
public func makeIterator() -> IndexingIterator<Self>
^
Swift.Sequence:5:17: note: found this candidate
public func makeIterator() -> Self
^
Now I couldn’t come up with an example where the code compiles but crashes at
runtime because of a type mismatch, but it’s not outside the realm of
possibility.
With my PR here the conformance itself no longer type checks:
https://github.com/apple/swift/pull/12174
<https://github.com/apple/swift/pull/12174>
col.swift:1:7: error: type 'Base' does not conform to protocol 'Collection'
class Base : Collection {
^
Swift.Sequence:5:17: note: candidate has non-matching type '<Self> () -> Self'
[with Element = Int, Index = Int, IndexDistance = Int, Iterator =
IndexingIterator<Base>, SubSequence = Slice<Base>, Indices =
DefaultIndices<Base>]
public func makeIterator() -> Self
^
Swift.Collection:6:17: note: candidate has non-matching type '<Self> () ->
IndexingIterator<Self>' [with Element = Int, Index = Int, IndexDistance = Int,
Iterator = IndexingIterator<Base>, SubSequence = Slice<Base>, Indices =
DefaultIndices<Base>]
public func makeIterator() -> IndexingIterator<Self>
I found one example in our code base where this pattern comes up, and that’s
SyntaxCollection in tools/SwiftSyntax/SyntaxCollection.swift. It has no
subclasses so making it final works there.
This was reported externally as https://bugs.swift.org/browse/SR-1863
<https://bugs.swift.org/browse/SR-1863>. I’m not sure if the user expects it to
work or just to produce a reasonable diagnostic instructing them to make the
class final.
What does everyone think of this?
1) Can anyone suggest a way to make it work, so that ‘for x in Derived()’ type
checks and the correct Self type (Base, not Derived) for the substitution?
2) Should we just ban such ’non-covariant’ conformances? There is precedent for
this — in Swift 3, we used to allow non-final classes to conform to protocols
whose requirements had same-type constraints with the right hand side equal to
‘Self’, and Doug closed this hole in Swift 4. My PR is essentially a more
comprehensive fix for this hole.
3) Should we allow the hole to remain in place, admitting non-final classes
that model Collection, at the cost of not being able to ever fix
https://bugs.swift.org/browse/SR-617 <https://bugs.swift.org/browse/SR-617>?
Slava
_______________________________________________
swift-dev mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-dev