Skip to content

Commit 617f127

Browse files
authored
Merge pull request #12603 from dotty-staging/java-intf-and-erasure
Fix erasure of Java intersection without a class
2 parents 1d83d0f + 8a96cde commit 617f127

File tree

6 files changed

+46
-21
lines changed

6 files changed

+46
-21
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,8 @@ class TypeApplications(val self: Type) extends AnyVal {
466466
def translateJavaArrayElementType(using Context): Type =
467467
// A type parameter upper-bounded solely by `FromJavaObject` has `ObjectClass` as its classSymbol
468468
if self.typeSymbol.isAbstractOrParamType && (self.classSymbol eq defn.ObjectClass) then
469-
AndType(self, defn.ObjectType)
469+
// The order is important here since Java intersections erase to their first operand
470+
AndType(defn.ObjectType, self)
470471
else
471472
self
472473

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -427,10 +427,6 @@ object TypeErasure {
427427
* This operation has the following the properties:
428428
* - Associativity and commutativity, because this method acts as the minimum
429429
* of the total order induced by `compareErasedGlb`.
430-
* - Java compatibility: intersections that would be valid in Java code are
431-
* erased like javac would erase them (a Java intersection is composed of
432-
* exactly one class and one or more interfaces and always erases to the
433-
* class).
434430
*/
435431
def erasedGlb(tp1: Type, tp2: Type)(using Context): Type =
436432
if compareErasedGlb(tp1, tp2) <= 0 then tp1 else tp2
@@ -609,7 +605,9 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
609605
case tp: TypeProxy =>
610606
this(tp.underlying)
611607
case tp @ AndType(tp1, tp2) =>
612-
if sourceLanguage.isScala2 then
608+
if sourceLanguage.isJava then
609+
this(tp1)
610+
else if sourceLanguage.isScala2 then
613611
this(Scala2Erasure.intersectionDominator(Scala2Erasure.flattenedParents(tp)))
614612
else
615613
erasedGlb(this(tp1), this(tp2))

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

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,21 +81,14 @@ object GenericSignatures {
8181
val (repr :: _, others) = splitIntersection(bounds)
8282
builder.append(':')
8383

84-
// According to the Java spec
85-
// (https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.4),
86-
// intersections erase to their first member and must start with a class.
87-
// So, if our intersection erases to a trait, in theory we should emit
88-
// just that trait in the generic signature even if the intersection type
89-
// is composed of multiple traits. But in practice Scala 2 has always
90-
// ignored this restriction as intersections of traits seem to be handled
91-
// correctly by javac, we do the same here since type soundness seems
92-
// more important than adhering to the spec.
84+
// In Java, intersections always erase to their first member, so put
85+
// whatever parent erases to the Scala intersection erasure first in the
86+
// signature.
9387
if repr.classSymbol.is(Trait) then
88+
// An initial ':' is needed if the intersection starts with an interface
89+
// (cf https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-TypeParameter)
9490
builder.append(':')
95-
boxedSig(repr)
96-
// If we wanted to be compliant with the spec, we would `return` here.
97-
else
98-
boxedSig(repr)
91+
boxedSig(repr)
9992
others.filter(_.classSymbol.is(Trait)).foreach { tp =>
10093
builder.append(':')
10194
boxedSig(tp)

tests/run/java-intersection.check

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
1
2+
2
3+
1
4+
Sub1
5+
2
6+
Sub2

tests/run/java-intersection/A_1.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
import java.io.Serializable;
2+
13
public class A_1 {
2-
public <T extends Object & java.io.Serializable> void foo(T x) {}
4+
public <T extends Object & Serializable> void foo(T x) {
5+
System.out.println("1");
6+
}
7+
8+
public <T extends Cloneable & Serializable> void foo(T x) {
9+
System.out.println("2");
10+
}
311
}
Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,26 @@
1+
import java.io.Serializable
2+
3+
class Sub extends A_1 {
4+
override def foo[T <: Object & Serializable](x: T) =
5+
super.foo(x)
6+
println("Sub1")
7+
8+
override def foo[T <: Cloneable & Serializable](x: T) =
9+
super.foo(x)
10+
println("Sub2")
11+
}
12+
113
object Test {
214
def main(args: Array[String]): Unit = {
15+
val x: Object & Serializable = new Serializable {}
16+
val y: Cloneable & Serializable = new Cloneable with java.io.Serializable {}
17+
318
val a = new A_1
4-
val x: java.io.Serializable = new java.io.Serializable {}
519
a.foo(x)
20+
a.foo(y)
21+
22+
val s = new Sub
23+
s.foo(x)
24+
s.foo(y)
625
}
726
}

0 commit comments

Comments
 (0)