A loophole in the initialization process?

I'm experimenting with some designs for an API where I need to initialize the most bottom type of a class hierarchy and also access some API while abstracting away the type because of generics. While doing so I tried a slightly unconventional initialization and found a very interesting behavior.

Since the bottom type is abstracted away the compiler only knows the designated initializer from the super type and the required init from the protocol. For some reasons calling the designated init is permitted and creates an instance of the super type instead of the subtype while still accepting the instance as if it had the type of the existential. In that situation accessing protocol members will simply crash the program at runtime.

I'm aware that this is a bug, but I would like to know which part should be statically disallowed, because I would like to use a similar approach to initialize the bottom type through the required init.

class A {
  init(a: Int) {
    print("A")
  }
}

protocol P {
  init(b: Int)

  func foo()
}

class B : A, P {
  required init(b: Int) {
    super.init(a: b)
    print("B")
  }

  func foo() {
    print("works")
  }
}

let b_type = B.self

typealias E = A & P

let e_type: E.Type = b_type
let new_a: E = e_type.init(a: 42) // why is this even permitted?
let new_b: E = e_type.init(b: 42)

type(of: new_a) == A.self // true - this is a little unexpected
type(of: new_b) == B.self // true

new_b.foo()

// this compiles, but logically this should trap at runtime.
// error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=2, address=0x600001bc0a20).
new_a.foo()

cc @Slava_Pestov @jrose Do you know if this is already fixed in Swift 5? (I currently don't have the toolchain installed to test it.)

1 Like

It does seem to be fixed, though I couldn't find the bug offhand.

<stdin>:29:23: error: constructing an object of class type 'E' (aka 'A & P') with a metatype value must use a 'required' initializer
let new_a: E = e_type.init(a: 42) // why is this even permitted?
               ~~~~~~ ^
2 Likes

The error message also answers my other questions. :) I can safely use any required init.

Should I still file a bug report then?

No, I think we're good. I found the fix (by Slava) and it already has some tests. Protocols with superclass constraints - part 3 by slavapestov · Pull Request #17816 · apple/swift · GitHub

3 Likes