Here's the prototype for my doubly-linked list (DLL) protocol:
public protocol TransferableCollection: PoorlyTransferableCollection where Self: BidirectionalCollection, Self.SubSequence: TransferableCollection
Since PoorlyTransferableCollection is a singly-linked list (SLL) protocol, it needs to work with half-closed ranges instead of our usual half-open ones (to have O(1) operations instead of O(n)). The DLL has versions of the SLL core methods, but they take Range<Index> instead of my custom InvertedRange<Index>. The DLL has default implementations of the SLL's version that just convert & forward:
private func convertRange(_ range: InvertedRange<Index>) -> Range<Index> { /*...*/ }
private func convertRange<R>(_ range: R) -> InvertedRange<Index> where R: RangeExpression, R.Bound == Index {
let r = range.relative(to: self)
switch (r.lowerBound, r.upperBound) {
case (startIndex, endIndex):
return .all
case (startIndex, startIndex):
return .beforeStart
case (startIndex, let i):
return .prefix(upThrough: index(before: i)) // ERROR
case (let i, endIndex):
return .suffix(after: index(before: i)) // AND HERE
case (endIndex, endIndex):
return .afterEnd
case (let s, let e):
return .range(after: index(before: s), upThrough: index(before: e)) // AND HERE
}
}
But I got errors of:
Argument labels '(before:)' do not match any available overloads
until I changed my protocol's header to:
public protocol TransferableCollection: PoorlyTransferableCollection, BidirectionalCollection where Self.SubSequence: TransferableCollection
It seems that the compiler can't peek at where-clauses to help determine what interface Self must already have. Is this a bug? Or am I triggering something that shouldn't be fixed due to causing other problems? There is a more definitive work-around, after all.
I wouldn't call this "look-ahead", but yes, it's a bug—where clauses should be treated equivalently to inheritance. You've already got a nice, self-contained test case, so please file it!
In general for protocols, constraints on Self are handled differently than entries in the inheritance clause. Once we've fully type checked the protocol definition they're equivalent, but sometimes we need to find inherited protocols before type checking, and this code is not as general as I'd like. I consider this a bug that we need to fix at some point.
I’d like us to think a bit more before fixing this bug, because relying on type checking for name lookup introduces more dependencies into the type-checking problem that slow down compilation and make cyclic dependencies more common.
Do we really need to fully check the requirement signature though? I think we could proceed in a hacky manner and walk the RequirementReprs looking for things conforming to Self. Maybe that's too hacky though, but I recall we do something similar with extensions since people write code like extension MyProto where Self : SomeClass.
We could look for Self : Foo requirements in the extension's where clause, but have to draw a line somewhere, e.g., extension MyProto where Self == Self.A, Self.A: SomeClass { } shouldn't work with name lookup.