Skip to content

Commit 0a89c6f

Browse files
authored
Merge pull request #13718 from dotty-staging/java-varargs-erasure-3
Handle Java varargs `T...` where `T` is a class parameter
2 parents 1f34888 + 6e35c6f commit 0a89c6f

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)