Why is this property setter appearing to lose the type information?

Understanding this behavior is pretty key to using overloading and generics correctly in Swift.

First, you don't unlock any magic by giving two functions the same name. Overloading simply means that Swift has to figure out, at compile time, which of the two functions the user means to use, at the point where that name is mentioned. (Both of these italicized points are essential to keep in mind.)

At the end of the day, however, we're just working with two distinct functions. So, let's do this manually as though we're the compiler. First, let's give your two functions distinct names so that we can refer to them unambiguously:

  static func saveVehicle(_ value: Vehicle, key: String) {
    print("Saving Vehicle")
  }
  
  static func saveAny(_ value: Any?, key: String) {
    print("Saving Any?")
  }

In reality, for a module Example containing your original code, the Swift compiler mangles the names of these two functions named save(_:key:), not as saveVehicle and saveAny, but as $s7Example11PreferencesO4save_3keyyAA7VehicleO_SStFZ and $s7Example11PreferencesO4save_3keyyypSg_SStFZ.

Next, let's figure out which one the user means to use, at compile time and at the point where the name is mentioned:

  var vehicle: T {
    get { /* Ignore */ Preferences.storage["vehicle"] as! T }
    set { Preferences.saveAny(newValue, key: "vehicle") }
  }

Could the user instead write saveVehicle here? Impossible: that would not typecheck, because you cannot call saveVehicle for an arbitrary value of type T.

You can check that the Swift compiler is actually doing the equivalent of this when it resolves overloads: simply paste your original code into a file and compile it with the -emit-sil -module-name="Example" options, and you can see that the setter includes the following:

  // function_ref static Preferences.save(_:key:)
  %16 = function_ref @$s7Example11PreferencesO4save_3keyyypSg_SStFZ : $@convention(method) (@in_guaranteed Optional<Any>, @guaranteed String, @thin Preferences.Type) -> () // user: %17
  %17 = apply %16(%5, %15, %4) : $@convention(method) (@in_guaranteed Optional<Any>, @guaranteed String, @thin Preferences.Type) -> ()

This behavior is to be distinguished from dynamic dispatch, where Swift chooses the most specific overload at runtime. In Swift, there are only three ways to get type-based dynamic dispatch:

19 Likes