Skip to content

Commit d9f5202

Browse files
committed
Prefer non-varargs methods in overload resolution when tiebreaker
Fixes #24072 Add a tiebreaker in the `compare` method to prefer non-varargs methods over varargs methods when both alternatives are equally specific. `isAsGood` function normally prefer varargs methods. However, it can fail to distinguish methods when wildcard types are involved with invariant type parameters. For example: ```scala def blub[T](a: Class[? <: T]): Unit // m1 def blub[T](a: Class[T], ints: Int*): Unit // m2 blub(classOf[Object]) ``` Here, `compare(m1, m2)` returns 0 (ambiguous) because both `winsType1` and `winsType2` are false: - `m2` is not as good as `m1` (this is correct: a varargs method can only be as good as another varargs method). - `m1` is NOT as good as the `m2` because Class[? <: T] is not a subtype of Class[T] (Class is invariant) The new tiebreaker resolves this ambiguities by preferring non-varargs methods as a final comparison step when owner hierarchy and type-based comparisons don't distinguish the alternatives.
1 parent c9309e7 commit d9f5202

File tree

2 files changed

+19
-1
lines changed

2 files changed

+19
-1
lines changed

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2176,7 +2176,20 @@ trait Applications extends Compatibility {
21762176
case 0 =>
21772177
if winsType1 != winsType2 then if winsType1 then 1 else -1
21782178
else if alt1.symbol == alt2.symbol then comparePrefixes
2179-
else 0
2179+
else
2180+
// Prefer non-varargs methods over varargs methods as a final tiebreaker.
2181+
// While `isAsGood` normally handles this, it can fail when wildcard types are involved.
2182+
// For example, when comparing:
2183+
// def m[T](a: Class[? <: T]): Unit
2184+
// def m[T](a: Class[T], ints: Int*): Unit
2185+
// Both winsType1 and winsType2 are false because Class[? <: T] is not
2186+
// a subtype of Class[T] (Class is invariant). This tiebreaker ensures the
2187+
// non-varargs method is preferred in such cases.
2188+
val alt1IsVarArgs = alt1.widen.isVarArgsMethod
2189+
val alt2IsVarArgs = alt2.widen.isVarArgsMethod
2190+
if alt1IsVarArgs == alt2IsVarArgs then 0
2191+
else if alt1IsVarArgs then -1 // alt2 (non-varargs) wins
2192+
else 1 // alt1 (non-varargs) wins
21802193
end compareWithTypes
21812194

21822195
if alt1.symbol.is(PhantomSymbol) && !alt2.symbol.is(PhantomSymbol) then -1

tests/run/overload_repeated/B_2.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ object Test {
1313
def bar4[T](x: T): Int = 1
1414
def bar4[T](x: T, xs: T*): Int = 2
1515

16+
// https://github.com/scala/scala3/issues/24072
17+
def bar5[T](a: Class[? <: T]): Int = 1
18+
def bar5[T](a: Class[T], ints: Int*): Int = 2
19+
1620
def main(args: Array[String]): Unit = {
1721
// In Java, varargs are always less specific than non-varargs (see
1822
// https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2),
@@ -28,5 +32,6 @@ object Test {
2832
assert(bar2("") == 1) // same in Scala 2
2933
assert(bar3("") == 1) // same in Scala 2
3034
assert(bar4("") == 1) // same in Scala 2
35+
assert(bar5(classOf[Object]) == 1) // same in Scala2
3136
}
3237
}

0 commit comments

Comments
 (0)