Skip to content

Commit 9d5d83e

Browse files
committed
Translate Java varargs ...T into T* instead of (T & Object)*
This is much more convenient for users but is more complicated for the compiler since we still need to translate the varargs into an `Object[]` in bytecode. Since ElimRepeated was already responsible for doing some adaptation of repeated arguments, it now also takes care of this (this differs from Scala 2 which handles this at Erasure). Fixes #9439.
1 parent 0d0cdef commit 9d5d83e

File tree

9 files changed

+95
-14
lines changed

9 files changed

+95
-14
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,8 @@ class Definitions {
397397
def runtimeMethodRef(name: PreName): TermRef = ScalaRuntimeModule.requiredMethodRef(name)
398398
def ScalaRuntime_drop: Symbol = runtimeMethodRef(nme.drop).symbol
399399
@tu lazy val ScalaRuntime__hashCode: Symbol = ScalaRuntimeModule.requiredMethod(nme._hashCode_)
400+
@tu lazy val ScalaRuntime_toArray: Symbol = ScalaRuntimeModule.requiredMethod(nme.toArray)
401+
@tu lazy val ScalaRuntime_toObjectArray: Symbol = ScalaRuntimeModule.requiredMethod(nme.toObjectArray)
400402

401403
@tu lazy val BoxesRunTimeModule: Symbol = requiredModule("scala.runtime.BoxesRunTime")
402404
@tu lazy val BoxesRunTimeModule_externalEquals: Symbol = BoxesRunTimeModule.info.decl(nme.equals_).suchThat(toDenot(_).info.firstParamTypes.size == 2).symbol

compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,8 @@ class ClassfileParser(
423423
if isRepeatedParam(index) then
424424
index += 1
425425
val elemType = sig2type(tparams, skiptvs)
426-
defn.RepeatedParamType.appliedTo(elemType.translateJavaArrayElementType)
426+
// `ElimRepeated` is responsible for correctly erasing this.
427+
defn.RepeatedParamType.appliedTo(elemType)
427428
else
428429
objToAny(sig2type(tparams, skiptvs))
429430
}

compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ object ElimRepeated {
2020
val name: String = "elimRepeated"
2121
}
2222

23-
/** A transformer that removes repeated parameters (T*) from all types, replacing
24-
* them with Seq types.
23+
/** A transformer that eliminates repeated parameters (T*) from all types, replacing
24+
* them with Seq or Array types and adapting repeated arguments to conform to
25+
* the transformed type if needed.
2526
*/
2627
class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase =>
2728
import ast.tpd._
@@ -54,11 +55,22 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase =>
5455
private def elimRepeated(tp: Type)(using Context): Type = tp.stripTypeVar match
5556
case tp @ MethodTpe(paramNames, paramTypes, resultType) =>
5657
val resultType1 = elimRepeated(resultType)
57-
val paramTypes1 =
58-
if paramTypes.nonEmpty && paramTypes.last.isRepeatedParam then
59-
val last = paramTypes.last.translateFromRepeated(toArray = tp.isJavaMethod)
60-
paramTypes.init :+ last
61-
else paramTypes
58+
val paramTypes1 = paramTypes match
59+
case init :+ last if last.isRepeatedParam =>
60+
val isJava = tp.isJavaMethod
61+
// A generic Java varargs `T...` is erased to `Object[]` in bytecode,
62+
// we directly translate such a type to `Array[_ <: Object]` instead
63+
// of `Array[_ <: T]` here. This allows the tree transformer of this phase
64+
// to emit the correct adaptation for repeated arguments without
65+
// relying on any cast (cf `adaptToArray`).
66+
val last1 =
67+
if isJava && last.elemType.isInstanceOf[TypeParamRef] then
68+
defn.ArrayOf(TypeBounds.upper(defn.ObjectType))
69+
else
70+
last.translateFromRepeated(toArray = isJava)
71+
init :+ last1
72+
case _ =>
73+
paramTypes
6274
tp.derivedLambdaType(paramNames, paramTypes1, resultType1)
6375
case tp: PolyType =>
6476
tp.derivedLambdaType(tp.paramNames, tp.paramInfos, elimRepeated(tp.resultType))
@@ -82,9 +94,10 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase =>
8294
case arg: Typed if isWildcardStarArg(arg) =>
8395
val isJavaDefined = tree.fun.symbol.is(JavaDefined)
8496
val tpe = arg.expr.tpe
85-
if isJavaDefined && tpe.derivesFrom(defn.SeqClass) then
86-
seqToArray(arg.expr)
87-
else if !isJavaDefined && tpe.derivesFrom(defn.ArrayClass)
97+
if isJavaDefined then
98+
val pt = tree.fun.tpe.widen.firstParamTypes.last
99+
adaptToArray(arg.expr, pt.elemType.bounds.hi)
100+
else if tpe.derivesFrom(defn.ArrayClass) then
88101
arrayToSeq(arg.expr)
89102
else
90103
arg.expr
@@ -107,7 +120,39 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase =>
107120
.appliedToType(elemType)
108121
.appliedTo(tree, clsOf(elemClass.typeRef))
109122

110-
/** Convert Java array argument to Scala Seq */
123+
/** Adapt a Seq or Array tree to be a subtype of `Array[_ <: $elemPt]`.
124+
*
125+
* @pre `elemPt` must either be a super type of the argument element type or `Object`.
126+
* The special handling of `Object` is required to deal with the translation
127+
* of generic Java varargs in `elimRepeated`.
128+
*/
129+
private def adaptToArray(tree: Tree, elemPt: Type)(implicit ctx: Context): Tree =
130+
val elemTp = tree.tpe.elemType
131+
val treeIsArray = tree.tpe.derivesFrom(defn.ArrayClass)
132+
if elemTp <:< elemPt then
133+
if treeIsArray then
134+
tree // no adaptation needed
135+
else
136+
tree match
137+
case SeqLiteral(elems, elemtpt) =>
138+
JavaSeqLiteral(elems, elemtpt).withSpan(tree.span)
139+
case _ =>
140+
// Convert a Seq[T] to an Array[$elemPt]
141+
ref(defn.DottyArraysModule)
142+
.select(nme.seqToArray)
143+
.appliedToType(elemPt)
144+
.appliedTo(tree, clsOf(elemPt))
145+
else if treeIsArray then
146+
// Convert an Array[T] to an Array[Object]
147+
ref(defn.ScalaRuntime_toObjectArray)
148+
.appliedTo(tree)
149+
else
150+
// Convert a Seq[T] to an Array[Object]
151+
ref(defn.ScalaRuntime_toArray)
152+
.appliedToType(elemTp)
153+
.appliedTo(tree)
154+
155+
/** Convert an Array into a scala.Seq */
111156
private def arrayToSeq(tree: Tree)(using Context): Tree =
112157
tpd.wrapArray(tree, tree.tpe.elemType)
113158

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1723,7 +1723,7 @@ class Typer extends Namer
17231723
checkedArgs = checkedArgs.mapconserve(arg =>
17241724
checkSimpleKinded(checkNoWildcard(arg)))
17251725
else if (ctx.compilationUnit.isJava)
1726-
if (tpt1.symbol eq defn.ArrayClass) || (tpt1.symbol eq defn.RepeatedParamClass) then
1726+
if (tpt1.symbol eq defn.ArrayClass) then
17271727
checkedArgs match {
17281728
case List(arg) =>
17291729
val elemtp = arg.tpe.translateJavaArrayElementType

tests/neg/i533/Test.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ object Test {
33
val x = new Array[Int](1)
44
x(0) = 10
55
println(JA.get(x)) // error
6-
println(JA.getVarargs(x: _*)) // error
6+
println(JA.getVarargs(x: _*)) // now OK.
77
}
88
}

tests/pos/arrays2.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ one warning found
2424
// #2461
2525
object arrays3 {
2626
def apply[X <: AnyRef](xs : X*) : java.util.List[X] = java.util.Arrays.asList(xs: _*)
27+
def apply2[X](xs : X*) : java.util.List[X] = java.util.Arrays.asList(xs: _*)
2728
}

tests/run/i9439.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
object Test {
2+
// First example with a concrete type <: AnyVal
3+
def main(args: Array[String]): Unit = {
4+
val coll = new java.util.ArrayList[Int]()
5+
java.util.Collections.addAll(coll, 5, 6)
6+
println(coll.size())
7+
8+
foo(5, 6)
9+
}
10+
11+
// Second example with an abstract type not known to be <: AnyRef
12+
def foo[A](a1: A, a2: A): Unit = {
13+
val coll = new java.util.ArrayList[A]()
14+
java.util.Collections.addAll(coll, a1, a2)
15+
println(coll.size())
16+
}
17+
}

tests/run/java-varargs-2/A.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class A {
2+
public static void foo(int... args) {
3+
}
4+
5+
public static <T> void gen(T... args) {
6+
}
7+
}

tests/run/java-varargs-2/Test.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
object Test {
2+
def main(args: Array[String]): Unit = {
3+
A.foo(Array(1): _*)
4+
A.foo(Seq(1): _*)
5+
A.gen(Array(1): _*)
6+
A.gen(Seq(1): _*)
7+
}
8+
}

0 commit comments

Comments
 (0)