Need Help Understanding Protocols and Generics

Right, the terminology is confusing.

The declared protocol requirement Q.id is exactly identical to the declared protocol requirement P.id.

Q.id does not create a new entry in the witness table. It uses the same entry as P.id.

They are identical. The same thing. One.

Thus, as a requirement, Q.id is an override of P.id. It occupies the same location. It does not create something new. It simply restates the existing requirement.

However, if you add the attribute @_nonoverride, then the protocol requirement Q.id is not identical to P.id. With that attribute, the witness table for Q gets its own, separate, distinct, entry for the requirement id.

Without the attribute, Q.id is identical to P.id. With the attribute, they are separate things.

2 Likes

@Nevin, we are on the same page.

Any thoughts about the logic question that I stated a couple of posts earlier?

There's one weird scenario I stumbled upon when experimenting, that whether or not you re-declare id requirement changes the function resolution:

protocol P { var id: String { get } }
protocol Q: P { }
extension P { var id: String { "P" } }
extension Q { var id: String { "Q" } }

// Like `Q`, just re-added `id` requirement
// You can actually add/remove `id` requirement
// to `Q` and see how it changes.
protocol Q1: P { var id: String { get } }
extension Q1 { var id: String { "Q1" } }

class C: P {}
class D: C, Q, Q1 { var id: String { "D" } }

print((D() as C & Q).id) // Q (8L)
print((D() as C & Q1).id) // P

I probably don't know enough to say whether one of them is a bug.

2 Likes

See my earlier posts about classes. That might help you get a handle on this behavior.

I donā€™t see how.

@Lantuaā€™s example has Q like in mine that we discussed earlier, plus also Q1 which is identical except that Q1 redeclares id.

As far as I understood, redeclaring a protocol requirement in a subprotocol has no effect except on type inference (and even that is probably a bugā€”the type inference ā€œshouldā€ be able to work without the redeclaration).

So why do Q and Q1 behave differently in Lantuaā€™s example, and is that how we want them to act?

Hmm, it is very difficult to figure this out indeed.

As you know, there are three ways to get dynamic dispatch in Swift: classes (using overridable class members), protocols (by implementing protocol requirements), and dynamic casting. In other cases, when two declarations have the same name and (but for their name) would be visible in the same scope, one declaration is said to shadow the other. This behavior is very distinct from dynamic dispatch, as you know also.

The examples you give here mix all three ways of getting dynamic dispatch. The behavior could not be more complicated. Having puzzled over this in detail, I do wonder if Swift is doing something unexpected in terms of how these features interact with each other. But I'm having trouble even thinking about this advanced case without my head hurting; let me show you my work so far:


I'll begin by saying that this is my mental model of how Swift does dynamic dispatch. No doubt it is not 100% accurate, because nothing except "whatever the compiler currently does" is 100% accurate, and that cannot be modeled.

Ultimately, we will have to decide if the overarching system is what we want, but that is about a hundred steps ahead of where we are. First, we need to enunciate what that system is in a way that doesn't refer to its implementation. Then, once we have a consensus on that answer, we need to fix the bugs in the implementation of that system, document it, and find a way to teach it. Once we have done that, then we can perhaps talk intelligently about what we don't like about what we have.

With that in mind, here it goes, an attempt to describe how dynamic dispatch works without reference to the actual implementation details:


Example 1

protocol P { var id: String { get } }
protocol Q: P { }
extension P { var id: String { "P" } }
extension Q { var id: String { "Q" } }
struct S: Q { var id: String { "S" } }

let s = S()
s.id // "S"

Does id need to be dynamically dispatched?
No, because it is a member of a struct.

If not, which implementation is statically chosen?
S.id.


Example 2

// Continued from example 1.
let q = s as Q
q.id // "S"

Does id need to be dynamically dispatched?
Yes, because we have dynamically cast s to Q, and Q.id is a protocol requirement.

If so, via the interface of what type or protocol?
Via P, because that protocol declares the requirement within the hierarchy of Q (to which we have dynamically cast the value).

Via that interface, which implementation is chosen?
S.id, because it is the "most specific" implementation available for P.id for this value.


Example 3

// Continued from example 2.
func f<T: P>(_ x: T) { print(x.id) }
f(s) // "S"

Does id need to be dynamically dispatched?
Yes, because P.id is a protocol requirement.

If so, via the interface of what type or protocol?
Via P, because that protocol declares the requirement within the hierarchy of P, which is the only interface we have given the generic constraint.

Via that interface, which implementation is chosen?
S.id, because it is the "most specific" implementation available for P.id for this T.


Example 4

// Continued from example 3.
protocol Q1: P { var id: String { get } }
extension Q1 { var id: String { "Q1" } }
struct T: Q1 { }

let q1 = T() as Q1
q1.id // "Q1"

Does id need to be dynamically dispatched?
Yes (see example 2).

If so, via the interface of what type or protocol?
Via P, because that is the protocol that declares the requirement within the hierarchy of Q1. Note that Q1 does not define a new, distinct requirement; it merely restates it.

Via that interface, which implementation is chosen?
Q1.id, because it is the "most specific" default implementation available for P.id.


Example 5

// Continued from example 4.
struct Generic<U>: P { }
extension Generic: Q where U: Equatable {
  var id: String { "Generic: Q where U: Equatable" }
}

let g = Generic<Int>()
g.id // "Generic: Q where U: Equatable"

Does id need to be dynamically dispatched?
No (see example 1).

If not, which implementation is statically chosen?
Generic<Int>.id, which is implemented in the extension Generic: Q where U: Equatable.


Example 6

// Continued from example 5.
// Recall the definition of `f`:
//   func f<T: P>(_ x: T) { print(x.id) }

f(g) // "P"

Does id need to be dynamically dispatched?
Yes (see example 3).

If so, via the interface of what type or protocol?
Via P (see example 3).

Via that interface, which implementation is chosen?
P.id, because it is the "most specific" implementation available for P.id for this T.

Note that Swift requires every T == Generic<U> to behave identically. We can think of it as dynamic dispatch looking only "one level deep" when it comes to generics: it can look through T to see Generic<U>, but it doesn't look through U to see Int.


Example 7

// Continued from example 6.
class C1: P { var id: String { "C1" } }
class D1: C1 { var id: String { "D1" } }

let d1 = D1()
d1.id // "D1"

Does id need to be dynamically dispatched?
Yes, because it is an overridable member of a (non-final) class.

If so, via the interface of what type or protocol?
Via C1 , because that is the (non-final) class that defines the overridable member. D1 does not define a new, distinct overridable member; it overrides a superclass member.

Via that interface, which implementation is chosen?
D1.id, because it is an override of C1.id.


Example 8

// Continued from example 7.
let c1 = d1 as C1
c1.id // "D1"

Does id need to be dynamically dispatched?
Yes, because we have dynamically cast d1 to C1, and C1.id is an overridable member of a (non-final) class.

If so, via the interface of what type or protocol?
Via C1 , because that is the (non-final) class that defines the overridable member within the hierarchy of C1 (to which we have dynamically cast the instance).

Via that interface, which implementation is chosen?
D1.id, because it an override of C1.id for this instance.


Example 9

// Continued from example 8.
class C: P { }
class D: C { var id: String { "D" } }

let d = D()
d.id // "D"

Does id need to be dynamically dispatched?
Yes, because it is an overridable member of a (non-final) class.

If so, via the interface of what type or protocol?
Via D, because that is the (non-final) class that defines the overridable member (see example 10).

Via that interface, which implementation is chosen?
D.id, because there is no more specific override.


Example 10

// Continued from example 9.
let c = d as C
c.id // "P"

Does id need to be dynamically dispatched?
Yes, because we have dynamically cast d to C, and C.id is a (theoretically) overridable member of a (non-final) class, even though it is impossible for subclasses actually to override it. So far as D is concerned, we're allowed to declare D.id because C.id can't be marked final, but D.id can only shadow but not override C.id because D.id can't be marked override. (See examples below for additional discussion.)

If so, via the interface of what type or protocol?
Via C, for the reasons given above.

Via that interface, which implementation is chosen?
C.id, which is to say P.id. For the reasons given above, D.id shadows but cannot override C.id.


Example 11

// Continued from example 10.
// Recall the definition of `f`:
//   func f<T: P>(_ x: T) { print(x.id) }
f(d) // "P"

Does id need to be dynamically dispatched?
Yes, because P.id is a protocol requirement (see example 3).

If so, via the interface of what type or protocol?
Via P, because that is the protocol that declares the requirement within the hierarchy of P.

Via that interface, which implementation is chosen?
P.id, because it is the "most specific" default implementation available for P.id. For the reasons given in example 10, D.id shadows but is not an implementation of P.id.


Example 12

// Continued from example 11.
func fcq<T: C & Q>(_ x: T) { print(x.id) }
fcq(d) // "Q"

Does id need to be dynamically dispatched?
Yes, because C.id is an overridable member of a non-final class, even though it is impossible for subclasses actually to override it. (See example 10.)

If so, via the interface of what type or protocol?
Via C, because that is the non-final class with the overridable member.

Via that interface, which implementation is chosen?
Q.id, because it is "more specific" than P.id, which would otherwise be the implementation for C.id.


Example 13

// Continued from example 12.
func fq<T: Q>(_ x: T) { print(x.id) }
fq(d) // "P"

Does id need to be dynamically dispatched?
Yes, but now because Q.id is a protocol requirement.

If so, via the interface of what type or protocol?
Via P, because that is the protocol that declares the requirement within the hierarchy of Q.

Via that interface, which implementation is chosen?
The non-overridable but shadowed C.id, which is to say the default implementation P.id, is ranked "more specific" than Q.id because it is inherited by the concrete type via subclassing, whereas Q.id is only a default implementation.

This is starting to get strange...


Example 14

// Continued from example **1**.
// Recall that we've defined the following protocols:
//   protocol P { var id: String { get } }
//   protocol Q: P { }
// ...and default implementations:
//   extension P { var id: String { "P" } }
//   extension Q { var id: String { "Q" } }

class C { }
class D: C, Q { var id: String { "D" } }
(D() as (AnyObject & Q)).id // "D"

Does id need to be dynamically dispatched?
Yes, because we have dynamically cast an instance to AnyObject & Q, and without a concrete implementation of (AnyObject & Q).id, Swift looks to the dynamically dispatched protocol requirement.

If so, via the interface of what type or protocol?
Via P, because that is the protocol that declares the requirement within the hierarchy of Q.

Via that interface, which implementation is chosen?
D.id, because it is the "most specific" implementation of P.id.


Example 15

// Continued from example **1**.
// Recall that we've defined the following protocols:
//   protocol P { var id: String { get } }
//   protocol Q: P { }
// ...and default implementations:
//   extension P { var id: String { "P" } }
//   extension Q { var id: String { "Q" } }

class C: P { } // Note the difference here from example 13.
class D: C, Q { var id: String { "D" } }
(D() as (AnyObject & Q)).id // "P"
(D() as Q).id // "P"

Does id need to be dynamically dispatched?
Yes, because we have dynamically cast an instance to AnyObject & Q or Q, and without a concrete implementation, Swift looks to the dynamically dispatched protocol requirement.

If so, via the interface of what type or protocol?
Via P, because that is the protocol that declares the requirement within the hierarchy of Q.

Via that interface, which implementation is chosen?
P.id (see example 13).

As discussed in example 4, Q.id is not a new requirement distinct from P.id, and D can conform to P in only one way because types can conform to protocols in only one way. That one way is inherited from C, even though D implements its own conformance to Q. Meanwhile, D.id can shadow C.id for the same reason as in example 10.

Without inheritance in the mix, Swift does consider Q.id as "more specific" than P.id:

class C2: Q { }
(C2() as Q).id // "Q"

struct S: Q { }
(S() as Q).id // "Q"

Example 16

// Continued from example 15.
(D() as (C & Q)).id // "Q"

Does id need to be dynamically dispatched?
Yes, because we have dynamically cast an instance to C & Q and C.id is an overridable member of a non-final class, even though it is impossible for subclasses actually to override it.

If so, via the interface of what type or protocol?
Via C (see examples 10 and 12).

Via that interface, which implementation is chosen?
Q.id (see examples 10 and 12 for reasoning).


Example 17

// Continued from example **4**.
// Recall that we've defined the following protocols:
//   protocol P { var id: String { get } }
//   protocol Q: P { }
//   protocol Q1: P { var id: String { get } }
// ...and default implementations:
//   extension P { var id: String { "P" } }
//   extension Q { var id: String { "Q" } }
//   extension Q1 { var id: String { "Q1" } }

class C: P { }
class D: C, Q, Q1 { var id: String { "D" } }
(D() as (C & Q)).id // "Q"

Does id need to be dynamically dispatched?
Yes (see example 16).

If so, via the interface of what type or protocol?
Via C (see example 16).

Via that interface, which implementation is chosen?
Q.id (see example 16).


Example 18

// Continued from example 16.
(D() as (C & Q1)).id // "P"

Does id need to be dynamically dispatched?
Yes (see example 16).

If so, via the interface of what type or protocol?
Via C (see example 16).

Via that interface, which implementation is chosen?
?!?!?!

9 Likes

There's a few separate questions here, so let me break this up.

At least for now, @_nonoverride is there for ABI reasons only; it's not intended to have a semantic effect, and still won't necessarily have the effect you want. (Though it can have a performance effect as well.) You still get different behavior whether you're invoking the id on P or the id on Q: once a witness is chosen for P.id for Y: P, it doesn't get re-chosen for Y: Q. That's a separate witness entirely.

Example
protocol P {
  var id: String { get }
}
extension P {
  var id: String { "P" }
  var idFromP: String { self.id }
}
protocol Q: P {
  @_nonoverride var id: String { get }
}
extension Q {
  var id: String { "Q" }
  var idFromQ: String { self.id }
}

struct Y<T> {}
extension Y: P {}
extension Y: Q where T: Equatable {}

print(Y<Int>().idFromP) // prints "P"
print(Y<Int>().idFromQ) // prints "Q"

The (compile-time) overload resolution in id2 (or idFromQ) isn't just choosing between P.id and Q.id; it's choosing from P.id (the requirement), P.id (the extension property), and Q.id (the extension property). The requirement gets precedent here, which is important for implementers like this:

struct Z: P, Q {
  var id: String { "Z" }
}

Here Z is unconditionally a P and also unconditionally a Q. But it also has its own id property, and that's the one that gets chosen to satisfy the requirement. If id2 said "well, I already know that Self is a Q, so I should use Q.id2", it would print "Q" instead of "Z" for this case.


I'm not exactly sure what's up with @Lantua's example. My guess is that it's related to the previous point, that requirements take precedent over default implementations, but I'd consider this a bug myself (that C & Q behaves differently from just Q). Class/protocol compositions get a little weird because classes are concrete types, and I suspect the compiler is using that to rule out protocol requirements as valid overloads, but that doesn't make sense for a composition.

EDIT: Okay, I think I get it. C conforms to P, so we know that the requirement P.id is going to be C's implementation (i.e. P.id) no matter how subclassed C is. (See SR-103 for complications around this.) That means that Q.id can be considered "more specific". However, C does not conform to Q1, so someone else might provide a different Q1.idā€¦except that (as noted above) that's not a separate requirement from P.id without @_nonoverride!

That's how the behavior falls out of the current rules. I don't think it's good behavior, but I'm not sure if there's a set of rules that avoids all weird edge cases.

I haven't read @xwu's latest yet. Maybe later today!

1 Like

I donā€™t see how this addresses Lantuaā€™s example. Splitting things up (and renaming Q1 to R) we have this:

protocol P { var id: String { get } }
protocol Q: P {}
protocol R: P { var id: String { get } }

extension P { var id: String { "P" } }
extension Q { var id: String { "Q" } }
extension R { var id: String { "R" } }

class C: P {}
class D: C, Q { var id: String { "D" } }
class E: C, R { var id: String { "E" } }

let d = D() as C & Q
let e = E() as C & R

print(d.id)   // Q
print(e.id)   // P

The the only difference between Q and R is that R restates the id requirement. But both Q and R inherit the id requirement from P, so the restatement of it in R shouldnā€™t make any difference, right?

Edit:

Hereā€™s something that might help shed a little light.

If we remove the implementations of id from D and E, and make E a subclass of D, then we get a compiler error:

class C: P {}
class D: C, Q {}
class E: D, R {}
// error: type 'E' does not conform to protocol 'R'
// note: multiple matching properties named 'id' with type 'String'
// note: candidate exactly matches
// extension P { var id: String { "P" } }
// extension Q { var id: String { "Q" } }
// extension R { var id: String { "R" } }

However, if we switch them around and make D a subclass of E, then it compiles without error:

class C: P {}
class E: C, R {}
class D: E, Q {}

let d = D() as C & Q
let e = E() as C & R

print(d.id)   // Q
print(e.id)   // P

Although if we then change the type of the existential d in certain ways, we get another compiler error:

let d = D() as D & Q
print(d.id)
// error: ambiguous use of 'id'
// (same 3 candidates as in previous error)

And:

let d = D() as E & Q
print(d.id)
// error: ambiguous use of 'id'
// (same 3 candidates as in previous error)

We do not get any error when changing the type of e, but the output is different:

let e = E() as E & R
print(e.id)   // R
1 Like

@xwu, that is an astounding effort. Thank you for making it. I've read and reasoned through it, in detail.

At a couple of points, I ran into typos and the like in the examples (e.g., a missing override keyword in Ex. 7). In some cases, I overcame those issues with obvious corrections that met your intent, but found myself floundering elsewhere. Still your points come through clearly.

I cannot comment comprehensively, right now, but will make some comments, as follows.

Amen. I applaud the effort you are making to develop semantic a set of semantic statements about the system and to cast them in ways that might be capable of harmonization. While I do hope this objective can be achieved, I am afraid it may devolve into an ad hoc set of rules that essentially mirror the implementation.

On my first pass through your post, I read it from the protocol system perspective, but I know understand that your focus is on the semantics of dynamic dispatch as it exists across all language features. That approach makes sense.

This one is a bit odd semantically. It is statically dispatched to the id method on the struct. However, if U was not Equatable, the id method would be invisible, and the call to id instead would be treated as a protocol requirement on P. Technically, it might not be dynamic dispatch, but that feels like a distinction without a difference.

Iā€™m not sure about the closing note. Isnā€™t that concept contradicted when the concrete type has its own implementation? Instead, one might view the id method on the conditional conformance to Q as being unavailable because we are dealing with an existential P. But, ultimately, that runs into the same problem as your explanation.

Fundamentally, what is the idea behind how we use existential P in Swift? We are limited to working solely with the P interface. Why does it matter whether the implementation of the interface is declared on P or on the concrete type or on some protocol (or superclass) in between? But, we ignore implementations in that in-between area. At least that is true in the case of value types. For classes, it feels like we might use part of the in-between area some of the time. (But, I'm jumping ahead.)

Very interesting!

However, when I read examples like this one, I wonder whether they are reasonable. Can the system reasonably be used in that fashion to any productive end? Is it ok if the system gives us strange results in corner cases that don't have good practical use? In the documentation, should we waive users away from those corner cases?

Ugh. I don't like where these class examples are headed.

Is it fair to describe it as follows: A protocol peers into a class hierarchy to ask the question of the class hierarchy, ā€œDo you have an id method that I could use to implement my requirement?ā€ Before the class hierarchy responds, the class systemā€™s rules are applied to determine who in the hierarchy answers that question and what they have available to answer it with?

Is it semantically accurate to say that a type can conform to a protocol in only one way? For example, here and in Ex. 16, D would seem to be "conforming" in multiple ways--sometimes producing a "P" or a "Q" or maybe even a "D". Of course, only one of those conformances is applicable in any given situation. I'm debating the semantics, because this post is about the semantics.

Oh, my.


Here, on the fringes, the system feels neither expressive nor safe.

Do we need to deal with the ragged edges of the road? Can we have a conversation about the middle of the road; declare some semantics; and then test those semantics against the ragged edges?

I do wish I could have done some more proofreading; sorry if any inadvertent errors were confusing!

Generics are not to be confused with existentials. It has been said that opaque types are semantically "generics in reverse"; by the same token, generics are "opaque types in reverse." The spelling we devised for that feature makes the distinction clear: we're dealing with some P here and not any P.

It is manifestly not reasonable, hence SR-103. This is partly where the rest of these examples start to go off the rails: we already know that how dynamic dispatch for class methods interacts with dynamic dispatch for protocol requirements is unsatisfactory, but the rest of these examples have to build on this unsatisfactory foundation. But again, we start with describing what is before we figure out what should be.

Well said, but Iā€™m still unsure of the premise that led to this exchange, wherein you said that Swift requires every T = Generic to behave identically. They donā€™t. If the concrete type for a given instance declares an implementation of a protocol requirement, and another does not, they behave differently even in that generic context. Right?

Generic can only conform to P in one way. Generic<Int> and Generic<String> can't each have their own conformances to P, and if you try to implemented id differently for each, they only shadow the protocol requirement:

// Continued from example 4.
struct Generic<U>: P { }
extension Generic where U == Int {
  var id: String { "Generic<Int>" }
}
extension Generic where U == String {
  var id: String { "Generic<String>" }
}

let g = Generic<Int>()
f(g) // "P"

(By the way, your point about example 5 is correct, I think. Without a concrete method, id is dynamically dispatched via the protocol.)

1 Like

Thank you, @xwu. You are correct.

I was confusing the overall operation of the generic function on type T: P (which I think of as meaning the function operates on existential P) versus the point you make about calling the function with differing instances of Generic<U>, which will all behave the same way. Your point is very well taken. While the underlying types of existential P can give us differing behavior, those that happen to be of varying types of Generic<U> will give us uniform behavior as among them.

I have I hard time grasping what ā€˜@_nonoverrideā€™ does. Could someone provide an example showing what that attribute does? For example, what does the attribute do in the following code:

protocol Foo { 
    var a: Int { get }
}

protocol Bar {
    @__nonoverride var a: Int { get }
}

Thanks in advance, Filip

Imagine if Bar.a were instead named Bar.b. It does that, even though the name of the requirement is the same as another.

3 Likes

Thanks, @jrose. As usual, great insights.


@_nonoverride

At least for now, @_nonoverride is there for ABI reasons only; it's not intended to have a semantic effect, and still won't necessarily have the effect you want. (Though it can have a performance effect as well.) You still get different behavior whether you're invoking the id on P or the id on Q: once a witness is chosen for P.id for Y: P , it doesn't get re -chosen for Y: Q . That's a separate witness entirely.

Yes, but it does make an actual difference in the behavior of protocol conformance. (I think you're saying that, but I'm not quite sure.) If the @_nonoverride attribute is removed from your example, the restatement of the id requirement is meaningless, and the call to the witness for id inside of idFromQ is dispatched to P.id instead of Q.id. Without @_nonoverride, is there a way to get idFromQ to call to Q.id?

Or, restated in a real world example, without @_nonoverride, is there a way to get the RandomAccessCollection.index(:offsetBy:limitedBy) -> Index? default implementation to call the RandomAccessCollection.distance(from:to:) -> Index.Stride default implementation? Without @_nonoverride, I believe the call dispatches to the default implementation on Collection, and then crashes if the offsetBy argument is negative.

excerpt from RandomAccessCollection.swift
/// Default implementation for random access collections.
extension RandomAccessCollection {
  /// Returns an index that is the specified distance from the given index,
  /// unless that distance is beyond a given limiting index.
  ///
  /// The following example obtains an index advanced four positions from an
  /// array's starting index and then prints the element at that position. The
  /// operation doesn't require going beyond the limiting `numbers.endIndex`
  /// value, so it succeeds.
  ///
  ///     let numbers = [10, 20, 30, 40, 50]
  ///     let i = numbers.index(numbers.startIndex, offsetBy: 4)
  ///     print(numbers[i])
  ///     // Prints "50"
  ///
  /// The next example attempts to retrieve an index ten positions from
  /// `numbers.startIndex`, but fails, because that distance is beyond the
  /// index passed as `limit`.
  ///
  ///     let j = numbers.index(numbers.startIndex,
  ///                           offsetBy: 10,
  ///                           limitedBy: numbers.endIndex)
  ///     print(j)
  ///     // Prints "nil"
  ///
  /// The value passed as `distance` must not offset `i` beyond the bounds of
  /// the collection, unless the index passed as `limit` prevents offsetting
  /// beyond those bounds.
  ///
  /// - Parameters:
  ///   - i: A valid index of the array.
  ///   - distance: The distance to offset `i`.
  ///   - limit: A valid index of the collection to use as a limit. If
  ///     `distance > 0`, `limit` should be greater than `i` to have any
  ///     effect. Likewise, if `distance < 0`, `limit` should be less than `i`
  ///     to have any effect.
  /// - Returns: An index offset by `distance` from the index `i`, unless that
  ///   index would be beyond `limit` in the direction of movement. In that
  ///   case, the method returns `nil`.
  ///
  /// - Complexity: O(1)
  @inlinable
  public func index(
    _ i: Index, offsetBy distance: Int, limitedBy limit: Index
  ) -> Index? {
    // FIXME: swift-3-indexing-model: tests.
    let l = self.distance(from: i, to: limit)
    if distance > 0 ? l >= 0 && l < distance : l <= 0 && distance < l {
      return nil
    }
    return index(i, offsetBy: distance)
  }
}

PWT Construction

Iā€™m not quite getting it. The PWT for Z: P includes the requirement/witness pair id/Z.id, and the PWT for Z: Q includes the requirement/witness pair id/Z.id. Right? And so:

print(Z().idFromP)  // prints "Z"
print(Z().idFromQ)  // prints ā€œZā€

Iā€™m not understanding what you mean by, ā€œThe requirement gets precedent here.ā€ Also, Iā€™m not seeing how this undesirable result:

If id2 said "well, I already know that Self is a Q, so I should use Q.id2 ", it would print "Q" instead of "Z" for this case.
[Annotation by @mattrips: I take this to be pointing back at my example and to be intending to refer to Q.id rather than Q.id2; is that correct, @jrose?]

would occur, because the construction of the PWT for Z: Q handles it. If Z doesnā€™t have its own implementation for id, then the PWT should point to Q.id, and if Z does have its own implementation, then the PWT points to Z.id.

In practice, the PWT for a conformance of a type to protocol Q, if unconditional, points to Q.id, but if conditional, doesn't point to Q.id and instead points to the PWT for the type's conformance to P. I think you are trying to help me understand why the PWT for the conditional conformance doesn't point to Q.id, but I'm still not getting it. Help me, please!

[EDIT: Good news. In a different thread, Dave gave me some more prodding that has opened my eyes. I now think I understand the why behind it. The essence of the why is buried in the details of the in-one-way rule of conformance. I'm not sure I agree with the merits of the why, but that is a different story.]

1 Like

override is implied by having the same requirement stated in a more-refined protocol as in a protocol it inherits from. It is allowed to be stated explicitly primarily to suppress the warning produced by -warn-implicit-overrides.

What you surmise is correct. When a protocol requirement "overrides" one from an inherited protocol, this requirement always gets the same witness as that of the inherited protocol. At the implementation level, there is no new entry for an override requirement in the witness table.

This is correct.

@_nonoverride says that the restated requirement does not have to have the same witness in a more-refined protocol as in the protocol it inherits that originally stated the requirement. In the implementation, this means that a @_nonoverride requirement introduces a new entry in the witness table. If you look at where @_nonoverride is used in the Standard Library, they are for cases where the semantics or complexity of operations (like distance(from:to:)) change in the protocol inheritance hierarchy: distance(from:to:) on a Collection is forward-only, on a BidirectionalCollection can go backwards, and on a RandomAccessCollection is

@_nonoverride is very, very important when dealing with conditional conformances, because it gives you more-capable witnesses in your conformances to protocols like BidirectionalCollection. Without it, for example, a wrapper around a Collection that had a conditional conformance to BidirectionalCollection would not be able to provide a more-specialized implementation of the wrapper semantics for bidirectional collections.

Note how this is the same problem we've been discussing, where we really want to be able to pick a more-specialized witness when we have a more capable type. The override / @_nonoverride dance was an implementation band-aid to get conditional conformances in the standard library to provide (mostly) the right behavior, using an ABI we could live with.

Doug

8 Likes

Thank you! IMO these are crucial pieces of the puzzle when trying to understand Swift's protocols and generics:

Are there any plans for a proper solution?

Nothing that's fully baked, no. There are three mostly-independent ways in which we could improve on this:

  • (Most relevant to current discussions) Change the witness-selection behavior to take into account the actual concrete types, even if those aren't available until runtime. For example, one could select the best witness amongst all of the potential witnesses that are visible at the point where the conformance is declared.
  • Fix associated type inference to eliminate the need for restating requirements in refined protocols just to get inference right
  • Turn @_nonoverride into a proper language feature (like C#'s new), to mean "no, I didn't mean to override something from an inherited protocol." This could also be meaningful (and is generally more useful) for classes!

To be clear, I don't know of anyone who is actively planning to do any of these.

Doug

1 Like

raises hand I would be quite happy to help with the implementation efforts :slight_smile: Making @_nonoverride available publicly might be a good first step.