Skip to content

Commit ff7e342

Browse files
oderskysmarter
authored andcommitted
Fix vararg overloading
Refine/clarify the rule when a method is applicable to a vararg parameter list. Test cases adapted from #9718 which used a different approach.
1 parent ac850b9 commit ff7e342

File tree

5 files changed

+100
-36
lines changed

5 files changed

+100
-36
lines changed

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

Lines changed: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1452,9 +1452,11 @@ trait Applications extends Compatibility {
14521452
/** Is alternative `alt1` with type `tp1` as specific as alternative
14531453
* `alt2` with type `tp2` ?
14541454
*
1455-
* 1. A method `alt1` of type (p1: T1, ..., pn: Tn)U is as specific as `alt2`
1456-
* if `alt2` is applicable to arguments (p1, ..., pn) of types T1,...,Tn
1457-
* or if `alt1` is nullary.
1455+
* 1. A method `alt1` of type `(p1: T1, ..., pn: Tn)U` is as specific as `alt2`
1456+
* if `alt1` is nullary or `alt2` is applicable to arguments (p1, ..., pn) of
1457+
* types T1,...,Tn. If the last parameter `pn` has a vararg type T*, then
1458+
* `alt1` must be applicable to arbitrary numbers of `T` parameters (which
1459+
* implies that it must be a varargs method as well).
14581460
* 2. A polymorphic member of type [a1 >: L1 <: U1, ..., an >: Ln <: Un]T is as
14591461
* specific as `alt2` of type `tp2` if T is as specific as `tp2` under the
14601462
* assumption that for i = 1,...,n each ai is an abstract type name bounded
@@ -1464,36 +1466,39 @@ trait Applications extends Compatibility {
14641466
* b. as specific as a member of any other type `tp2` if `tp1` is compatible
14651467
* with `tp2`.
14661468
*/
1467-
def isAsSpecific(alt1: TermRef, tp1: Type, alt2: TermRef, tp2: Type): Boolean = trace(i"isAsSpecific $tp1 $tp2", overload) { tp1 match {
1468-
case tp1: MethodType => // (1)
1469-
val formals1 =
1470-
if (tp1.isVarArgsMethod && tp2.isVarArgsMethod) tp1.paramInfos.map(_.repeatedToSingle)
1471-
else tp1.paramInfos
1472-
isApplicableMethodRef(alt2, formals1, WildcardType) ||
1473-
tp1.paramInfos.isEmpty && tp2.isInstanceOf[LambdaType]
1474-
case tp1: PolyType => // (2)
1475-
inContext(ctx.fresh.setExploreTyperState()) {
1476-
// Fully define the PolyType parameters so that the infos of the
1477-
// tparams created below never contain TypeRefs whose underling types
1478-
// contain uninstantiated TypeVars, this could lead to cycles in
1479-
// `isSubType` as a TypeVar might get constrained by a TypeRef it's
1480-
// part of.
1481-
val tp1Params = tp1.newLikeThis(tp1.paramNames, tp1.paramInfos, defn.AnyType)
1482-
fullyDefinedType(tp1Params, "type parameters of alternative", alt1.symbol.span)
1483-
1484-
val tparams = newTypeParams(alt1.symbol, tp1.paramNames, EmptyFlags, tp1.instantiateParamInfos(_))
1485-
isAsSpecific(alt1, tp1.instantiate(tparams.map(_.typeRef)), alt2, tp2)
1486-
}
1487-
case _ => // (3)
1488-
tp2 match {
1489-
case tp2: MethodType => true // (3a)
1490-
case tp2: PolyType if tp2.resultType.isInstanceOf[MethodType] => true // (3a)
1491-
case tp2: PolyType => // (3b)
1492-
explore(isAsSpecificValueType(tp1, constrained(tp2).resultType))
1493-
case _ => // (3b)
1494-
isAsSpecificValueType(tp1, tp2)
1495-
}
1496-
}}
1469+
def isAsSpecific(alt1: TermRef, tp1: Type, alt2: TermRef, tp2: Type): Boolean = trace(i"isAsSpecific $tp1 $tp2", overload) {
1470+
tp1 match
1471+
case tp1: MethodType => // (1)
1472+
tp1.paramInfos.isEmpty && tp2.isInstanceOf[LambdaType]
1473+
|| {
1474+
if tp1.isVarArgsMethod
1475+
tp2.isVarArgsMethod
1476+
&& isApplicableMethodRef(alt2, tp1.paramInfos.map(_.repeatedToSingle), WildcardType)
1477+
else
1478+
isApplicableMethodRef(alt2, tp1.paramInfos, WildcardType)
1479+
}
1480+
case tp1: PolyType => // (2)
1481+
inContext(ctx.fresh.setExploreTyperState()) {
1482+
// Fully define the PolyType parameters so that the infos of the
1483+
// tparams created below never contain TypeRefs whose underling types
1484+
// contain uninstantiated TypeVars, this could lead to cycles in
1485+
// `isSubType` as a TypeVar might get constrained by a TypeRef it's
1486+
// part of.
1487+
val tp1Params = tp1.newLikeThis(tp1.paramNames, tp1.paramInfos, defn.AnyType)
1488+
fullyDefinedType(tp1Params, "type parameters of alternative", alt1.symbol.span)
1489+
1490+
val tparams = newTypeParams(alt1.symbol, tp1.paramNames, EmptyFlags, tp1.instantiateParamInfos(_))
1491+
isAsSpecific(alt1, tp1.instantiate(tparams.map(_.typeRef)), alt2, tp2)
1492+
}
1493+
case _ => // (3)
1494+
tp2 match
1495+
case tp2: MethodType => true // (3a)
1496+
case tp2: PolyType if tp2.resultType.isInstanceOf[MethodType] => true // (3a)
1497+
case tp2: PolyType => // (3b)
1498+
explore(isAsSpecificValueType(tp1, constrained(tp2).resultType))
1499+
case _ => // 3b)
1500+
isAsSpecificValueType(tp1, tp2)
1501+
}
14971502

14981503
/** Test whether value type `tp1` is as specific as value type `tp2`.
14991504
* Let's abbreviate this to `tp1 <:s tp2`.

tests/neg/overload_repeated.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
object Test {
2+
def bar1(x: Any) = 1
3+
def bar1(x: String*) = 2
4+
5+
assert(bar1("") == 1) // error: ambiguous in Scala 2 and Scala 3
6+
}

tests/run/overload_repeated/A_1.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
class A_1 {
2+
public static int foo1(Object x) { return 1; }
3+
public static int foo1(String... x) { return 2; }
4+
5+
public static int foo2(Object x) { return 1; }
6+
public static int foo2(Object... x) { return 2; }
7+
8+
public static <T> int foo3(T x) { return 1; }
9+
public static <T> int foo3(T... x) { return 2; }
10+
11+
public static <T> int foo4(T x) { return 1; }
12+
public static <T> int foo4(T x, T... y) { return 2; }
13+
14+
public static boolean check() {
15+
// Java prefers non-varargs to varargs:
16+
// https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2
17+
return
18+
foo1("") == 1 &&
19+
foo2("") == 1 &&
20+
foo3("") == 1 &&
21+
foo4("") == 1;
22+
}
23+
}

tests/run/overload_repeated/B_2.scala

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
object Test {
2+
def bar1(x: Any) = 1
3+
def bar1(x: String*) = 2
4+
5+
def bar2(x: Any) = 1
6+
def bar2(x: Any*) = 2
7+
8+
def bar3[T](x: T): Int = 1
9+
def bar3[T](x: T*): Int = 2
10+
11+
def bar4[T](x: T): Int = 1
12+
def bar4[T](x: T, xs: T*): Int = 2
13+
14+
def main(args: Array[String]): Unit = {
15+
// In Java, varargs are always less specific than non-varargs (see
16+
// https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2),
17+
// this isn't true in Scala which leads to `foo1` being ambiguous.
18+
assert(A_1.check())
19+
// assert(A_1.foo1("") == 1) // Works in Java, ambiguous in Scala 2 and Dotty
20+
assert(A_1.foo2("") == 1) // Same as in Java and Scala 2
21+
assert(A_1.foo3("") == 1) // Same as in Java and Scala 2
22+
assert(A_1.foo4("") == 1) // Same as in Java and Scala 2
23+
24+
// Same with Scala varargs:
25+
// assert(bar1("") == 1) // Works in Java, ambiguous in Scala 2 and Dotty
26+
assert(bar2("") == 1) // same in Scala 2
27+
assert(bar3("") == 1) // same in Scala 2
28+
assert(bar4("") == 1) // same in Scala 2
29+
}
30+
}

tests/run/t8197.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ class Foo(val x: A = null) {
99
}
1010

1111
object Test extends App {
12-
// both constructors of `Foo` are applicable. Overloading resolution
13-
// will eliminate the alternative that uses a default argument, therefore
14-
// the vararg constructor is chosen.
12+
// both constructors of `Foo` are applicable and neither is more specific
13+
// than the other. As a fallback, overloading resolution will eliminate the
14+
// alternative that uses a default argument, therefore the vararg constructor is chosen.
1515
assert((new Foo).x != null)
1616
}

0 commit comments

Comments
 (0)