Fun with Swift's Type System

Working on Tailor over the last few months has given me great admiration for Swift's type system. Still, it's not always the easiest thing to work with when writing an ORM. I ran into a bizarre behavior when writing the query builder, and I wanted to write up my findings here.

Let's say you have a generic type that wants to initialize a record of its type parameter. There are some scenarios where it can initialize it as the wrong type. The bug crops up when:

  • You are using a required initializer
  • The type parameter for your generic type is constrained to have a given base class
  • The actual type parameter you've specified is a subclass of that base class

I've attached a playground that demonstrates the problem. At first I thought I was out of luck, but there is a very strange workaround. You have a class called GenericInitializer, which has a HatType parameter. If you try to use HatType.self.init, it will always initialize it as a Hat, even if HatType is defined to be TopHat. Right before the initializer is called, it acknowledges that HatType is TopHat, but it will spit out a plain old Hat. The type system will think it's a TopHat, but if you try to access TopHat's fields it will go haywire. If, on the other hand, you store HatType.self in a local variable called type, and then call type.init, it will correctly initialize it as a TopHat. I'm not sure how storing an expression in a local variable before calling it changes the semantics, but Swift's compiler is clever like that.

You can download the playground and try it for yourself.