To answer your question we need to have a look at what happens when the Scala compiler has to perform overloading resolution. This is described in SLS 6.23.3 (for Scala 2.9).
Let's take a slightly simpler version of your example:
object Test {
def apply[T](x1: T) = "one arg" // A
def apply[T](x1: T, x2: T) = "two args" // B
def apply[T](elems: T*) = "var-args: " + elems.size // C
}
And look at these three calls:
Test(1) // fails, ambiguous reference, A and C both match arguments
Test[Int](1) // returns "one arg"
Test(1,2) // returns "two args", not "var-args: 2"
Let's start with the first one. First, the compiler looks at the shape of each argument, which is a type that describes, basically, if the argument is a value or a function. Here, no difficulty, 1
is a very normal, boring value and its shape is the type Nothing
.
Now it has a single argument 1
, of type Nothing
, and finds all alternatives that are applicable to it. It finds two of them:
apply[T](x1: T)
: it takes a single argument of unbounded type, so it can receive a argument of type Nothing
,
apply[T](elems: T*)
: it can be applied to any number (0
included) of arguments of the same unbounded type, so it can receive a single element of type Nothing
.
It there were only one, it would stop there and choose that one.
The second step is identical to the above, except this time it types each argument with an undefined expected type. Basically here it looks at the two alternatives left and finds out if they are applicable to the argument 1
of type A <: Int
. No luck, they both are.
If you were two change apply[T](x1: T)
to apply(x1: String)
and leave the other one alone, here there would only be one applicable alternative left and it would succeed and stop.
Then the compiler computes the relative weight
of each alternative left over each other. The SLS states that
The relative weight of an alternative A over an alternative B is a number from 0 to 2,
defined as the sum of
- 1 if A is as specific as B , 0 otherwise, and
- 1 if A is defined in a class or object which is derived from the class or object
defining B , 0 otherwise.
At this point, there must be one alternative that has a higher score than all others, or there is an ambiguity error. We can ignore the "defined" part, they are defined in the same place.
A
is as specific as C
because you can always call C
with the single argument of A
,
C
is as specific as A
because of type inference: you can always call A
with the argument of C
since A
can take anything (its parameter's type can be inferred to whatever we want). C
's parameters is seen as a Seq[A]
so T
is inferred as Seq[A]
in A
and it can call it. So C
is as specific as A
.
This can be seen if you change A
to apply[T <: Int](x: T)
: it goes all the way to looking for the most specific one, but this time type inference cannot find a way to make A
applicable to C
's argument (a Seq
) because it isn't a subtype of Int
, so A
is the most specific. Obviously, the same thing happens if you change A
to apply(x: Int)
, type inference can't even do anything.
This also explains why Test[Int](1)
succeeds in calling the one argument version. The two final alternatives are identical, but A
's type parameter has been bound to Int
and type inference cannot change it to fit C
's argument anymore.
Finally, applying the same logic gives you why Test(1,2)
works fine:
B
is as specific as C
: you can always call C
with B
's arguments,
- but
C
is not as specific as B
: no amount of type inference will manage to fit a single Seq
into a method that takes two parameters.
So apply[T](x1: T, x2: T)
is the most specific and there are no errors.
Basically for a var-arg and a normal method to produce an ambiguity, you they need to have the same number of arguments and a way to trick type inference on (at least) the last argument:
def apply[T](x1: T)
def apply[T](x: T*)
Or
def apply[T](x1: Int, x2:T)
def apply[T](x1: Int, x: T*)
And so on...
Edit: I wasn't sure at first whether the repeated parameter was seen as a Seq[A]
or a TupleX[...]
when looking for specificity. It definitely isn't a tuple, and auto-tupling has nothing to do with any of this.