Default parameters cause function call to wrong overload?

Consider the following example:

protocol A {}
class A1: A {}
class A2: A {}

func foo(_ a: A1) {
    print("A1")
}

func foo(_ a: A2, x: Bool = false) {
    print("A2")
}

foo(A1()) // calls foo(A1)
foo(A2()) // calls foo(A2, Bool)

This program behaves as one would expect, printing A1 and A2.


However, if I add the following function

func foo(_ a: A) {
    print("A")
}

the behaviour changes to the following:

foo(A1()) // calls foo(A1)
foo(A2()) // calls foo(A)

From how I understand it, the second call should still resolve to the foo(A2, Bool) overload.
Is there something I'm missing here?

(I found this bug report, but I'm not sure it's the same issue, since I'm not using generics)

1 Like

I am far from an expert in Swift, but, I think the answer is that when matching the signatures, the fact that 'x' has a default value is ignored so that the closest match in type signature is foo(A()), not foo(A2(),x) for for(A2()), since you do not explicitly include a Bool argument in the call. It works the first time because there is not conflict, there is not 'func foo(_ a: A)' defined. Whether this is intended, or a deficiency in the overload resolution rules, others will need to comment on that.

even if the Bool argument is ignored, wouldn't foo(A2) still be a closer match than foo(A)?

No because there is no initializer with just A2(). The compiler has to consider these two options:

foo(A())
foo(A2(),bool) --> ignoring fact the bool is defaulted.

for a call to foo(A2()), the best match is foo(A()) by signature. Similar to situations in C++ with function/operator overloads and base classes. Again, not sure if this is intended, or if a deficiency in overload resolution rules.

This is similar to SR-1876 but not quite the same. It's true that there are two ranking rules in play here:

  • Concrete types are better than protocol types.
  • Not having to provide default arguments is better than having to provide them.

So the question is whether the interaction between these two rules is correct. It's possible to explicitly select either overload:

foo(A2() as A)
foo(A2(), x: false)

which is usually the first test for whether one rule must take priority over another. But you can't always do the same thing with generics (well, not as compactly), and so that's one point in favor of the current behavior being preferable.