I understand the central point of your post to be an exploration of whether that statement of intention is the true intent.
In furtherance of that exploration, I'd like to use the example of SR-12881:
extension Collection {
func generic_distance(from i: Index, to j: Index) -> Int {
distance(from: i, to: j)
}
}
extension RandomAccessCollection {
var isRandomAccess: Bool { true }
}
let r: DefaultIndices<ClosedRange<Int>> = (0...10).indices
assert(r.isRandomAccess)
print(r.distance(from: r.endIndex, to: r.startIndex))
print(r.generic_distance(from: r.endIndex, to: r.startIndex)) // trap
Here, the indices
instance property of Array<Int>
vends a concrete instance of DefaultIndices<Element: Collection>
with its type parameter, Element
, bound to ClosedRange<Int>
. If I understand your statement of intent correctly, at the moment of instantiation of the concrete instance of DefaultIndices
, we should determine all of the protocol conformances of the instance and should do so based on whatever is visible within the current scope. Correct?
Because Int
conforms to Strideable
and SignedInteger
, ClosedRange<Int>
conforms to RandomAccessCollection
. And, because ClosedRange<Int>
conforms to RandomAccessCollection
and is the Element
type for this instance of DefaultIndices
, this instance of DefaultIndices
conforms to RandomAccessCollection
. Also, because RandomAccessCollection
itself conforms to BidirectionalCollection
and, in turn, that protocol conforms to Collection
, this instance of DefaultIndices
also conforms to BidirectionalCollection
and Collection
.
Does your statement of intention stop with determining the protocols to which the given type conforms? Or does your statement of intention continue forward to the step of determining which implementation of a protocol's properties and methods would be dispatched for the type? I assume you intend the latter, but I may be mistaken.
If I understand your statement of intent correctly, at the moment of instantiation of the concrete instance of DefaultIndices
, we would determine which implementations of the various applicable protocol required/provided properties and methods would be used by the type, based on what is visible from the current scope. Correct?
Here, the example focuses on the distance(from:to:)
instance method. Each of the Collection
, BidirectionalCollection
and RandomAccessCollection
protocols provides its own default implementation of this method. Based on the rule of specialization, the implementation provided by RandomAccessCollection
would be selected. So, when distance(from: r.endIndex, to: r.startIndex)
is called on this instance of DefaultIndices
, the RandomAccessCollection
implementation of that method is called.
Where the example turns to the generic_distance(from:to:)
method, things get interesting. In the visible scope, that method is implemented only by the Collection
protocol. Since this instance of DefaultIndices
conforms to Collection
, if the method is called on this type, Collection
's implementation of the method will be used.
When Collection
's implementation of generic_distance(from:to:)
is called via this type, the method in turn calls to the distance(from:to:)
method. One might think that that internal call would/should call to RandomAccessCollection
's implementation of the distance(from:to:)
method. But, it does not do so. Instead, it calls to Collection
's implementation of the distance(from:to:)
method. Leaving aside "why" that behavior happens, I would characterize the behavior as surprising, and not documented. I'm not sure whether the behavior is consistent or not with your statement of intent.
The "fix" offered for SR-12881 proposes a workaround of sorts that will provide the expected behavior. That workaround involves providing DefaultIndices
with its own implementation of the distance(from:to:)
method as part of its conformance to Collection
. Again, I'm not sure whether that behavior is consistent or not with your statement of intent. Again, let's leave aside the "why" of how that workaround achieves the expected behavior.