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.)