diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index 4e360224a18f..aa380b574b98 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala @@ -466,7 +466,8 @@ class TypeApplications(val self: Type) extends AnyVal { def translateJavaArrayElementType(using Context): Type = // A type parameter upper-bounded solely by `FromJavaObject` has `ObjectClass` as its classSymbol if self.typeSymbol.isAbstractOrParamType && (self.classSymbol eq defn.ObjectClass) then - AndType(self, defn.ObjectType) + // The order is important here since Java intersections erase to their first operand + AndType(defn.ObjectType, self) else self diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index ab5c4fa5bf07..8f0a8c9df985 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -427,10 +427,6 @@ object TypeErasure { * This operation has the following the properties: * - Associativity and commutativity, because this method acts as the minimum * of the total order induced by `compareErasedGlb`. - * - Java compatibility: intersections that would be valid in Java code are - * erased like javac would erase them (a Java intersection is composed of - * exactly one class and one or more interfaces and always erases to the - * class). */ def erasedGlb(tp1: Type, tp2: Type)(using Context): Type = if compareErasedGlb(tp1, tp2) <= 0 then tp1 else tp2 @@ -609,7 +605,9 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst case tp: TypeProxy => this(tp.underlying) case tp @ AndType(tp1, tp2) => - if sourceLanguage.isScala2 then + if sourceLanguage.isJava then + this(tp1) + else if sourceLanguage.isScala2 then this(Scala2Erasure.intersectionDominator(Scala2Erasure.flattenedParents(tp))) else erasedGlb(this(tp1), this(tp2)) diff --git a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala index 4348bee27ff5..9024296eae89 100644 --- a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala +++ b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala @@ -81,21 +81,14 @@ object GenericSignatures { val (repr :: _, others) = splitIntersection(bounds) builder.append(':') - // According to the Java spec - // (https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.4), - // intersections erase to their first member and must start with a class. - // So, if our intersection erases to a trait, in theory we should emit - // just that trait in the generic signature even if the intersection type - // is composed of multiple traits. But in practice Scala 2 has always - // ignored this restriction as intersections of traits seem to be handled - // correctly by javac, we do the same here since type soundness seems - // more important than adhering to the spec. + // In Java, intersections always erase to their first member, so put + // whatever parent erases to the Scala intersection erasure first in the + // signature. if repr.classSymbol.is(Trait) then + // An initial ':' is needed if the intersection starts with an interface + // (cf https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-TypeParameter) builder.append(':') - boxedSig(repr) - // If we wanted to be compliant with the spec, we would `return` here. - else - boxedSig(repr) + boxedSig(repr) others.filter(_.classSymbol.is(Trait)).foreach { tp => builder.append(':') boxedSig(tp) diff --git a/tests/run/java-intersection.check b/tests/run/java-intersection.check new file mode 100644 index 000000000000..51c75c6b280b --- /dev/null +++ b/tests/run/java-intersection.check @@ -0,0 +1,6 @@ +1 +2 +1 +Sub1 +2 +Sub2 diff --git a/tests/run/java-intersection/A_1.java b/tests/run/java-intersection/A_1.java index ebe834a74cce..f54862e8adc2 100644 --- a/tests/run/java-intersection/A_1.java +++ b/tests/run/java-intersection/A_1.java @@ -1,3 +1,11 @@ +import java.io.Serializable; + public class A_1 { - public void foo(T x) {} + public void foo(T x) { + System.out.println("1"); + } + + public void foo(T x) { + System.out.println("2"); + } } diff --git a/tests/run/java-intersection/Test_2.scala b/tests/run/java-intersection/Test_2.scala index cbc39988340a..a3d7b11fce12 100644 --- a/tests/run/java-intersection/Test_2.scala +++ b/tests/run/java-intersection/Test_2.scala @@ -1,7 +1,26 @@ +import java.io.Serializable + +class Sub extends A_1 { + override def foo[T <: Object & Serializable](x: T) = + super.foo(x) + println("Sub1") + + override def foo[T <: Cloneable & Serializable](x: T) = + super.foo(x) + println("Sub2") +} + object Test { def main(args: Array[String]): Unit = { + val x: Object & Serializable = new Serializable {} + val y: Cloneable & Serializable = new Cloneable with java.io.Serializable {} + val a = new A_1 - val x: java.io.Serializable = new java.io.Serializable {} a.foo(x) + a.foo(y) + + val s = new Sub + s.foo(x) + s.foo(y) } }