swiftinheritancedynamicpolymorphismdynamictype

Problem with swift polymorphism and dynamic type


I just encountered a strange behavior in swift's inheritance handling, when it comes to polymorphism and dynamic types. The following code shows the problem I encounter, which basically is: The dynamic type is recognized correctly (printed by print("type(of: self) = \(classType)")), but the generic function testGeneric uses the wrong type.

class Global {
    static func testGeneric<T: TestSuperClass>(of type: T.Type) {
        print("T.Type = \(T.self)")
    }
}

class TestSuperClass {
    func run() {
        let classType = type(of: self)
        print("type(of: self) = \(classType)")
        Global.testGeneric(of: classType)
    }
}

class TestClass: TestSuperClass {

}

class TestClass2: TestSuperClass {
    override func run() {
        let classType = type(of: self)
        print("type(of: self) = \(classType)")
        Global.testGeneric(of: classType)
    }
}

let testClass = TestClass()
let testClass2 = TestClass2()

testClass.run()
testClass2.run()

the printed output is

type(of: self) = TestClass
T.Type = TestSuperClass
type(of: self) = TestClass2
T.Type = TestClass2

So basically when calling testClass.run(), type(of: self) yields TestClass, which I would expect. The problem then is that the generic function testGeneric, which is called immediately afterwards, somehow does not work with type TestClass, but uses TestSuperClass instead.

What I personally would expect is

type(of: self) = TestClass
T.Type = TestClass
type(of: self) = TestClass2
T.Type = TestClass2

i.e., the generic function testGeneric using the type TestClass instead of TestSuperClass when called via testClass.run().

Questions:
- Do you have an explanation for that?
- How can I obtain the behavior I had in mind?


Solution

  • In Swift, the compiler want's to know at compile time which generic type to "infer". Therefore, the type system will bind to the static type. There is no such thing as dynamic type inference.

    Therefore the compiler generates the following (see comments):

    class TestSuperClass {
        func run() {
            let classType = type(of: self)  // static MetaType TestSuperClass.Type
            print("type(of: self) = \(classType)") // dynamic type: TestClass
            Global.testGeneric(of: classType)  // infer to static type, i.e. testGeneric<TestSuperClass>
        }
    }
    

    As a result, T.self is TestSuperClass in your case, because that's what the compiler is able to see:

    static func testGeneric<T: TestSuperClass>(of type: T.Type) {
        print("T.Type = \(T.self)")
    }
    

    What you maybe want is the following:

    static func testGeneric<T: TestSuperClass>(of type: T.Type) {
        print("T.Type = \(type)")
    }
    

    Here, you do not print the type of T, but the (dynamic) value of the parameter type, which in your case is TestClass