Wizard
Wizard

Reputation: 305

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?

Upvotes: 1

Views: 461

Answers (2)

Andreas Oetjen
Andreas Oetjen

Reputation: 10199

To answer the second question: You will not be able to change the dynamic type of the retured array; it will always be [TestSuperClass] - although it will contain TestClass objects:

class Global {
    static func testGeneric<T: TestSuperClass>(of type: T.Type) {
        print("T.Type = \(T.self)")
    }
    static func returnObjects<T: TestSuperClass>(of theType: T.Type) -> [T] {
        let newObj = theType.init()
        let newObjType = type(of:newObj)
        print("type(of: newObj) = \(newObjType)")
        return [newObj]
    }
}

class TestSuperClass {
    required init() {
        print ("TestSuperClass.init")
    }

    func run() {
        let classType = type(of: self)
        print("type(of: self) = \(classType)")
        Global.testGeneric(of: classType)
        let array = Global.returnObjects(of: classType)
        let arrayType = type(of:array)
        print("type(of: self) = \(arrayType)")

        print (array)
    }
}

class TestClass: TestSuperClass {
    required init() {
        super.init()
        print("TestClass.init")
    }

}

let testClass = TestClass()
testClass.run()

TestSuperClass.init
TestClass.init
type(of: self) = TestClass
T.Type = TestSuperClass
TestSuperClass.init
TestClass.init
type(of: newObj) = TestClass
type(of: self) = Array < TestSuperClass >
[__lldb_expr_21.TestClass]

Upvotes: 1

Andreas Oetjen
Andreas Oetjen

Reputation: 10199

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

Upvotes: 2

Related Questions