Possibly incorrect behavior with forced downcast to Self

Hello everyone,

I stumbled upon a puzzling situation while fooling around with the type system. Let's say we define the following protocol to describe immutable stack-like types:

protocol Stack {

  associatedtype Element
  func push(element: Element) -> Self

}

Now let's imagine I want to conform to this protocol with a simple linked list implementation:

class List<Element>: Stack {

  var element: Element
  var next: List?

  init(element: Element, next: List? = nil) {
    self.element = element
    self.next = next
  }

  func push(element: Element) -> List {
    return List(element: element, next: self)
  }

}

This code won't compile with a pretty clear error message. The problem is that Stack requires the method push to return Self, but I'm returning List<Element> in a non-final class. Therefore the method requirements won't be fulfilled in any subclass of List.

The compiler offers to switch the return type of push with Self and then to force downcast the return expression to Self, in order to respect the now modified return type. While I understand why this type checks, I don't understand why the program doesn't crash at runtime. More concretely, why does this code run fine:

class List<Element>: Stack {

  // ...

  func push(element: Element) -> Self {
    // This line should fail at runtime if the receiver is a subclass of `List<T>`
    return List(element: element, next: self) as! Self
  }

}

class IntList: List<Int> {}

var i = IntList(element: 0)
i = i.push(element: 1) // This method call should crash

After scratching my head for a while, I modified IntList to contain an additional stored property and printed it at the end of the program to confirm my suspicions.

class IntList: List<Int> {
  let foo: String
  // ...
}

var i = IntList(foo: "bar", element: 0)
i = i.push(element: 1)
print(i.foo) // This line does crash the program

While I perfectly understand why the last line crashes, my understanding is that the forced downcast should have already crashed earlier. Am I missing something?

1 Like