Skip to content

Commit 6e35c6f

Browse files
committed
Handle Java varargs T... where T is a class parameter
Previously, ElimRepeated correctly handled Java varargs of the form `Object...` and `T...` where `T` is a method parameter, in both cases we erased them to `Array[? <: Object]` in the denotation transformer and handled any adaptation in the tree transformer of ElimRepeated. Unfortunately, the denotation transformer logic failed to account for class type parameters, and fixing it introduced issues in override checking (RefChecks happens after ElimRepeated). So this commit gives up and delay the adaptation of primitive arrays into reference arrays until Erasure. This is less efficient in some situations but is closer to what Scala 2 does so hopefully means we won't run into more pitfalls. Fixes #13645.
1 parent 307fcc4 commit 6e35c6f

File tree

6 files changed

+65
-73
lines changed

6 files changed

+65
-73
lines changed

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -376,18 +376,18 @@ class Definitions {
376376
* <T> void meth8(T... args) {}
377377
*
378378
* // B.scala
379-
* meth7(1) // OK
380-
* meth8(1) // OK
379+
* meth7(1) // OK (creates a reference array)
380+
* meth8(1) // OK (creates a primitive array and copies it into a reference array at Erasure)
381381
* val ai = Array[Int](1)
382-
* meth7(ai: _*) // OK (will copy the array)
383-
* meth8(ai: _*) // OK (will copy the array)
382+
* meth7(ai: _*) // OK (will copy the array at Erasure)
383+
* meth8(ai: _*) // OK (will copy the array at Erasure)
384384
*
385385
* Java repeated arguments are erased to arrays, so it would be safe to treat
386386
* them in the same way: add an `& Object` to the parameter type to disallow
387387
* passing primitives, but that would be very inconvenient as it is common to
388388
* want to pass a primitive to an Object repeated argument (e.g.
389389
* `String.format("foo: %d", 1)`). So instead we type them _without_ adding the
390-
* `& Object` and let `ElimRepeated` take care of doing any necessary adaptation
390+
* `& Object` and let `ElimRepeated` and `Erasure` take care of doing any necessary adaptation
391391
* (note that adapting a primitive array to a reference array requires
392392
* copying the whole array, so this transformation only preserves semantics
393393
* if the callee does not try to mutate the varargs array which is a reasonable

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,8 @@ object Types {
299299
loop(this)
300300
}
301301

302-
def isFromJavaObject(using Context): Boolean = typeSymbol eq defn.FromJavaObjectSymbol
302+
def isFromJavaObject(using Context): Boolean =
303+
isRef(defn.ObjectClass) && (typeSymbol eq defn.FromJavaObjectSymbol)
303304

304305
def containsFromJavaObject(using Context): Boolean = this match
305306
case tp: OrType => tp.tp1.containsFromJavaObject || tp.tp2.containsFromJavaObject

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

Lines changed: 18 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -114,22 +114,7 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase =>
114114
if lastIdx >= 0 then
115115
val last = paramTypes(lastIdx)
116116
if last.isRepeatedParam then
117-
// We need to be careful when handling Java repeated parameters
118-
// of the form `Object...` or `T...` where `T` is unbounded:
119-
// in both cases, `Object` will have been translated to `FromJavaObject`
120-
// to allow passing primitives as repeated arguments, but we can't
121-
// pass a primitive array as argument to such a method since the
122-
// parameter will be erased to `Object[]`. To handle this correctly we
123-
// drop usage of `FromJavaObject` as an element type here, the
124-
// tree transformer of this phase is then responsible for handling
125-
// mismatches by emitting the correct adaptation (cf `adaptToArray`).
126-
// See also the documentation of `FromJavaObjectSymbol`.
127-
val last1 =
128-
if isJava && last.elemType.isFromJavaObject then
129-
defn.ArrayOf(TypeBounds.upper(defn.ObjectType))
130-
else
131-
last.translateFromRepeated(toArray = isJava)
132-
paramTypes.updated(lastIdx, last1)
117+
paramTypes.updated(lastIdx, last.translateFromRepeated(toArray = isJava))
133118
else paramTypes
134119
else paramTypes
135120
tp.derivedLambdaType(paramNames, paramTypes1, resultType1)
@@ -144,8 +129,7 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase =>
144129
val isJavaDefined = tree.fun.symbol.is(JavaDefined)
145130
val tpe = arg.expr.tpe
146131
if isJavaDefined then
147-
val pt = tree.fun.tpe.widen.firstParamTypes.last
148-
adaptToArray(arg.expr, pt.elemType.bounds.hi)
132+
adaptToArray(arg.expr)
149133
else if tpe.derivesFrom(defn.ArrayClass) then
150134
arrayToSeq(arg.expr)
151135
else
@@ -154,58 +138,25 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase =>
154138
}
155139
cpy.Apply(tree)(tree.fun, args)
156140

157-
/** Convert sequence argument to Java array */
158-
private def seqToArray(tree: Tree)(using Context): Tree = tree match
141+
private def adaptToArray(tree: Tree)(implicit ctx: Context): Tree = tree match
159142
case SeqLiteral(elems, elemtpt) =>
160-
JavaSeqLiteral(elems, elemtpt)
143+
JavaSeqLiteral(elems, elemtpt).withSpan(tree.span)
161144
case _ =>
162-
val elemType = tree.tpe.elemType
163-
var elemClass = erasure(elemType).classSymbol
164-
if defn.NotRuntimeClasses.contains(elemClass) then
165-
elemClass = defn.ObjectClass
166-
end if
167-
ref(defn.DottyArraysModule)
168-
.select(nme.seqToArray)
169-
.appliedToType(elemType)
170-
.appliedTo(tree, clsOf(elemClass.typeRef))
171-
172-
/** Adapt a Seq or Array tree to be a subtype of `Array[_ <: $elemPt]`.
173-
*
174-
* @pre `elemPt` must either be a super type of the argument element type or `Object`.
175-
* The special handling of `Object` is required to deal with the translation
176-
* of generic Java varargs in `elimRepeated`.
177-
*/
178-
private def adaptToArray(tree: Tree, elemPt: Type)(implicit ctx: Context): Tree =
179-
val elemTp = tree.tpe.elemType
180-
val elemTpMatches = elemTp <:< elemPt
181-
val treeIsArray = tree.tpe.derivesFrom(defn.ArrayClass)
182-
if elemTpMatches && treeIsArray then
183-
tree // No adaptation necessary
184-
else tree match
185-
case SeqLiteral(elems, elemtpt) =>
186-
// By the precondition, we only have mismatches if elemPt is Object, in
187-
// that case we use `FromJavaObject` as the element type to allow the
188-
// sequence literal to typecheck no matter the types of the elements,
189-
// Erasure will take care of any necessary boxing (see documentation
190-
// of `FromJavaObjectSymbol` for more information).
191-
val adaptedElemTpt = if elemTpMatches then elemtpt else TypeTree(defn.FromJavaObjectType)
192-
JavaSeqLiteral(elems, adaptedElemTpt).withSpan(tree.span)
193-
case _ =>
194-
if treeIsArray then
195-
// Convert an Array[T] to an Array[Object]
196-
ref(defn.ScalaRuntime_toObjectArray)
197-
.appliedTo(tree)
198-
else if elemTpMatches then
199-
// Convert a Seq[T] to an Array[$elemPt]
200-
ref(defn.DottyArraysModule)
201-
.select(nme.seqToArray)
202-
.appliedToType(elemPt)
203-
.appliedTo(tree, clsOf(elemPt))
145+
val elemTp = tree.tpe.elemType
146+
val adapted =
147+
if tree.tpe.derivesFrom(defn.ArrayClass) then
148+
tree
204149
else
205-
// Convert a Seq[T] to an Array[Object]
206-
ref(defn.ScalaRuntime_toArray)
207-
.appliedToType(elemTp)
208-
.appliedTo(tree)
150+
ref(defn.DottyArraysModule)
151+
.select(nme.seqToArray)
152+
.appliedToType(elemTp)
153+
.appliedTo(tree, clsOf(elemTp))
154+
// This seemingly redundant type ascription is needed because the result
155+
// type of `adapted` might be erased to `Object`, but we need to keep
156+
// the precise result type at erasure for `Erasure.Boxing.cast` to adapt
157+
// a primitive array into a reference array if needed.
158+
// Test case in tests/run/t1360.scala.
159+
Typed(adapted, TypeTree(defn.ArrayOf(elemTp)))
209160

210161
/** Convert an Array into a scala.Seq */
211162
private def arrayToSeq(tree: Tree)(using Context): Tree =

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,13 @@ object Erasure {
325325
assert(!pt.isInstanceOf[SingletonType], pt)
326326
if (pt isRef defn.UnitClass) unbox(tree, pt)
327327
else (tree.tpe.widen, pt) match {
328+
// Convert primitive arrays into reference arrays, this path is only
329+
// needed to handle repeated arguments, see
330+
// `Definitions#FromJavaObjectSymbol` and `ElimRepeated#adaptToArray`.
331+
case (JavaArrayType(treeElem), JavaArrayType(ptElem))
332+
if treeElem.widen.isPrimitiveValueType && !ptElem.isPrimitiveValueType =>
333+
cast(ref(defn.ScalaRuntime_toObjectArray).appliedTo(tree), pt)
334+
328335
// When casting between two EVTs, we need to check which one underlies the other to determine
329336
// whether u2evt or evt2u should be used.
330337
case (tp1 @ ErasedValueType(tycon1, underlying1), tp2 @ ErasedValueType(tycon2, underlying2)) =>

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
class A<S> {
2+
public void gen(S... args) {
3+
}
4+
5+
public <T extends S> void gen2(S... args) {
6+
}
7+
}
8+
class B<S extends java.io.Serializable> {
9+
public void gen(S... args) {
10+
}
11+
12+
public <T extends S> void gen2(S... args) {
13+
}
14+
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
object Test {
2+
def main(args: Array[String]): Unit = {
3+
val ai = new A[Int]
4+
ai.gen(1)
5+
ai.gen2(1)
6+
ai.gen(Array(1)*)
7+
ai.gen2(Array(1)*)
8+
ai.gen(Seq(1)*)
9+
ai.gen2(Seq(1)*)
10+
11+
val b = new B[String]
12+
b.gen("")
13+
b.gen2("")
14+
b.gen(Array("")*)
15+
b.gen2(Array("")*)
16+
b.gen(Seq("")*)
17+
b.gen2(Seq("")*)
18+
}
19+
}

0 commit comments

Comments
 (0)