Search code examples
swiftgenericsopaque-types

Is there any better way to get existential type's actual type in lldb


protocol Car {
    func printCar()
}

struct SportsCar : Car {
    func printCar() {

    }
}

func generateSportsCar() -> any Car {
    return SportsCar()
}

func test() {
    let obj = generateSportsCar()
    obj.printCar()
}

the generateSportsCar is return a existential type (any car) , But if I print the obj in the lldb :

Printing description of obj:
testSwiftProj.SportsCar()

It says the type is the concrete type SportsCar , not the existential type like any< Car > or something else , what 's the best way to get the existential type in lldb ?


Solution

  • lldb is just trying to be helpful here by "opening" the existential wrapper. and showing you the concrete type. After all, when debugging, knowing the concrete type that a variable stores is much more useful. You already know obj is an existential type at compile time, so it would not be useful for a debugger to tell you the same thing.

    and I can even call the method of SportsCar with obj

    I suppose you mean that you can call obj.printCar(). You can do that because printCar is declared in Car. On the other hand, if SportsCar declared extra methods of its own, you will not be able to call them.

    is there any better way to get its actual type in lldb?

    You can write some Python to inspect the actual value of obj, without opening the existential:

    (lldb) script
    Python Interactive Interpreter. To exit, type 'quit()', 'exit()'.
    >>> obj = lldb.frame.FindVariable("obj")
    >>> obj.GetStaticValue()
    (Foo.Car) obj = {
      payload_data_0 = 0x00000001ec707148 _swiftEmptySetSingleton
      payload_data_1 = 0x0000000014110001
      payload_data_2 = 0x0000000000000000
      metadata = Any.Type
      wtable = 0x0000000102a99768 Foo`protocol witness table for Foo.SportsCar : Foo.Car in Foo
    }
    

    Here you can clearly see that the type is Foo.Car (Foo is the module in which I have declared Car), and its contents contains a witness table for the protocol requirements. This is what the "guts" of an existential type look like.

    You can also find something similar with obj.GetType(). It shows the "opened", concrete type information, in addition to the existential type information.

    >>> obj.GetType()
    Dynamic:
    Source code info:
    struct SportsCar : Foo.Car {
      func printCar()
      init()
    }
    Static:
    Source code info:
    protocol Car {
      func printCar()
    }
    

    As an exercise, try running these functions when obj is declared as let obj = SportsCar(), and compare the differences.