Why is so weird result in playground? And what is the mechanism of so weird dispatching?
class A {
func execute(param: Int = 123) {
print("A: \(param)")
}
}
class B: A {
override func execute(param: Int = 456) {
print("B: \(param)")
}
}
let instance: A = B()
instance.execute()
// print B: 123
I watched SIL files but it looks ok. Vtables looks right too.
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @riddle.instance : riddle.A // id: %2
%3 = global_addr @riddle.instance : riddle.A : $*A // users: %9, %8
%4 = metatype $@thick B.Type // user: %6
// function_ref B.__allocating_init()
%5 = function_ref @riddle.B.__allocating_init() -> riddle.B : $@convention(method) (@thick B.Type) -> @owned B // user: %6
%6 = apply %5(%4) : $@convention(method) (@thick B.Type) -> @owned B // user: %7
%7 = upcast %6 : $B to $A // user: %8
store %7 to %3 : $*A // id: %8
%9 = load %3 : $*A // users: %12, %13
// function_ref default argument 0 of A.execute(param:)
%10 = function_ref @default argument 0 of riddle.A.execute(param: Swift.Int) -> () : $@convention(thin) () -> Int // user: %11
%11 = apply %10() : $@convention(thin) () -> Int // user: %13
%12 = class_method %9 : $A, #A.execute : (A) -> (Int) -> (), $@convention(method) (Int, @guaranteed A) -> () // user: %13
%13 = apply %12(%11, %9) : $@convention(method) (Int, @guaranteed A) -> ()
%14 = integer_literal $Builtin.Int32, 0 // user: %15
%15 = struct $Int32 (%14 : $Builtin.Int32) // user: %16
return %15 : $Int32 // id: %16
} // end sil function 'main'
Why is so strange behaviuor?
This is a very old, controversial behavior. The short answer is: don't do that. There is quite a bit of debate as to whether this is a bug or a design choice.
My personal opinion is that this should be a compiler error, or at the very least a warning. IMO, the func in B is a different func, and therefore does not override A (so
overrideshould be an error). Or it is the "same" func with a different signature somehow, which should also be an error. There at least is an open bug to make it a warning (well, something similar, but it's in the same ballpark).But it is what it is, and I doubt there is going to be much interest in changing it. It would be a breaking change, so I wouldn't expect it before Swift 6 in any case. And since inheritance is not the preferred approach in Swift, most new features focus on more preferred approaches using structs, enums, or actors. So I don't really expect it in Swift 6. Of course, if it's important to you, I would bring it up on the forums, and make a pitch. At least making the warning might be a very nice starter bug if that interest you.
As with most issues involving defaults, the solution is always to remember that they're really shorthand for a more explicit syntax:
Or you can use a technique I've been finding useful with default parameters. Perfer them to be Optional, and assign their default value in the implementation:
This is a technique I've been finding I often need for
actormethods with defaults, if those defaults might access a@MainActorproperty. It works in this case, too.(But you're right to be surprised. Folks have been rightly surprised about this for years.)
You asked in the comments about what is really happening, and that's a good question, too. The answer, as you expected, is in the SIL (commentary added):
The default parameter is actually a function:
So, since
instanceis statically of typeA(which is all the compiler knows), the codeinstance.execute()is transformed into:All the choices are made at the point of the caller, not at the point of the implementation. So the default value is entirely dependent on statically known type information.
Working the way you probably expect here would be a major change to how Swift works, and I don't think that's plausible, or even particularly desirable, due to how it may reduce opportunities for inlining and other optimizations. But, IMO, the fact that this situation is allowed is a mistake.