Skip to content

Commit c3cb92d

Browse files
committed
Properly handle generic arrays coming from Java sources
Treat them like generic arrays coming from Java classfiles: `T[]` needs to be typed as `Array[T & Object]` since it will not accept primitive arrays.
1 parent dbb98b3 commit c3cb92d

File tree

8 files changed

+58
-13
lines changed

8 files changed

+58
-13
lines changed

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,28 @@ class TypeApplications(val self: Type) extends AnyVal {
408408
def translateToRepeated(from: ClassSymbol)(using Context): Type =
409409
translateParameterized(from, defn.RepeatedParamClass)
410410

411+
/** Translate `T` by `T & Object` in the situations where an `Array[T]`
412+
* coming from Java would need to be interpreted as an `Array[T & Object]`
413+
* to be erased correctly.
414+
*
415+
* This is necessary because a fully generic Java array erases to an array of Object,
416+
* whereas a fully generic Java array erases to Object to allow primitive arrays
417+
* as subtypeS.
418+
*
419+
* Note: According to
420+
* <http://cr.openjdk.java.net/~briangoetz/valhalla/sov/02-object-model.html>,
421+
* in the future the JVM will consider that:
422+
*
423+
* int[] <: Integer[] <: Object[]
424+
*
425+
* So hopefully our grand-children will not have to deal with this non-sense!
426+
*/
427+
def translateJavaArrayElementType(using Context): Type =
428+
if self.typeSymbol.isAbstractOrParamType && !self.derivesFrom(defn.ObjectClass) then
429+
AndType(self, defn.ObjectType)
430+
else
431+
self
432+
411433
/** If this is an encoding of a (partially) applied type, return its arguments,
412434
* otherwise return Nil.
413435
* Existential types in arguments are returned as TypeBounds instances.

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

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -395,16 +395,8 @@ class ClassfileParser(
395395
tpe
396396
case ARRAY_TAG =>
397397
while ('0' <= sig(index) && sig(index) <= '9') index += 1
398-
var elemtp = sig2type(tparams, skiptvs)
399-
// make unbounded Array[T] where T is a type variable into Array[T with Object]
400-
// (this is necessary because such arrays have a representation which is incompatible
401-
// with arrays of primitive types.
402-
// NOTE that the comparison to Object only works for abstract types bounded by classes that are strict subclasses of Object
403-
// if the bound is exactly Object, it will have been converted to Any, and the comparison will fail
404-
// see also RestrictJavaArraysMap (when compiling java sources directly)
405-
if (elemtp.typeSymbol.isAbstractOrParamType && !(elemtp.derivesFrom(defn.ObjectClass)))
406-
elemtp = AndType(elemtp, defn.ObjectType)
407-
defn.ArrayOf(elemtp)
398+
val elemtp = sig2type(tparams, skiptvs)
399+
defn.ArrayOf(elemtp.translateJavaArrayElementType)
408400
case '(' =>
409401
// we need a method symbol. given in line 486 by calling getType(methodSym, ..)
410402
val paramtypes = new ListBuffer[Type]()

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1641,6 +1641,15 @@ class Typer extends Namer
16411641
else if (tpt1.symbol == defn.orType)
16421642
checkedArgs = checkedArgs.mapconserve(arg =>
16431643
checkSimpleKinded(checkNoWildcard(arg)))
1644+
else if (ctx.compilationUnit.isJava)
1645+
if (tpt1.symbol eq defn.ArrayClass) || (tpt1.symbol eq defn.RepeatedParamClass) then
1646+
checkedArgs match {
1647+
case List(arg) =>
1648+
val elemtp = arg.tpe.translateJavaArrayElementType
1649+
if (elemtp ne arg.tpe)
1650+
checkedArgs = List(TypeTree(elemtp).withSpan(arg.span))
1651+
case _ =>
1652+
}
16441653
assignType(cpy.AppliedTypeTree(tree)(tpt1, checkedArgs), tpt1, checkedArgs)
16451654
}
16461655
}

tests/explicit-nulls/run/generic-java-array-src/Test.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ object Test {
44
// then on the Scala side we'll need to pass a nullable array.
55
// i.e. with explicit nulls the previously-implicit cast becomes an explicit
66
// type annotation.
7-
val x = new Array[Int|Null](1)
7+
val x = new Array[Integer|Null](1)
88
x(0) = 10
99
println(JA.get(x))
1010

tests/neg/i533/JA.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class JA {
2+
public static <T> T get(T[] arr) {
3+
return arr[0];
4+
}
5+
6+
public static <T> T getVarargs(T ...arr) {
7+
return arr[0];
8+
}
9+
}

tests/neg/i533/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+
val x = new Array[Int](1)
4+
x(0) = 10
5+
println(JA.get(x)) // error
6+
println(JA.getVarargs(x: _*)) // error
7+
}
8+
}

tests/run/i533/JA.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,8 @@ class JA {
22
public static <T> T get(T[] arr) {
33
return arr[0];
44
}
5-
}
5+
6+
public static <T> T getVarargs(T ...arr) {
7+
return arr[0];
8+
}
9+
}

tests/run/i533/Test.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
object Test {
22
def main(args: Array[String]): Unit = {
3-
val x = new Array[Int](1)
3+
val x = new Array[Integer](1)
44
x(0) = 10
55
println(JA.get(x))
6+
println(JA.getVarargs(x: _*))
67
}
78
}

0 commit comments

Comments
 (0)