diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 14e8d6d17577..29b40670e8e3 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -363,13 +363,13 @@ object TypeErasure { * which leads to more predictable bytecode and (?) faster dynamic dispatch. */ def erasedLub(tp1: Type, tp2: Type)(using Context): Type = { - // After erasure, C | {Null, Nothing} is just C, if C is a reference type. - // We need to short-circuit this case here because the regular lub logic below - // relies on the class hierarchy, which doesn't properly capture `Null`s subtyping - // behaviour. - if (tp1.isBottomTypeAfterErasure && tp2.derivesFrom(defn.ObjectClass)) return tp2 - if (tp2.isBottomTypeAfterErasure && tp1.derivesFrom(defn.ObjectClass)) return tp1 - tp1 match { + // We need to short-circuit the following 2 case because the regular lub logic in the else relies on + // the class hierarchy, which doesn't properly capture `Nothing`/`Null` subtyping behaviour. + if tp1.isRef(defn.NothingClass) || (tp1.isRef(defn.NullClass) && tp2.derivesFrom(defn.ObjectClass)) then + tp2 // After erasure, Nothing | T is just T and Null | C is just C, if C is a reference type. + else if tp2.isRef(defn.NothingClass) || (tp2.isRef(defn.NullClass) && tp1.derivesFrom(defn.ObjectClass)) then + tp1 // After erasure, T | Nothing is just T and C | Null is just C, if C is a reference type. + else tp1 match { case JavaArrayType(elem1) => import dotty.tools.dotc.transform.TypeUtils._ tp2 match { diff --git a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala index a85c28a9f878..a8cf8d7ecc28 100644 --- a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala +++ b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala @@ -13,7 +13,7 @@ import scala.tools.asm.Opcodes import scala.jdk.CollectionConverters._ import Opcodes._ -class TestBCode extends DottyBytecodeTest { +class DottyBytecodeTests extends DottyBytecodeTest { import ASMConverters._ @Test def nullChecks = { val source = """ @@ -1234,6 +1234,27 @@ class TestBCode extends DottyBytecodeTest { Label(9), Op(IRETURN))) } } + + /** Check that erasure if `Int | Nothing` is `int` */ + @Test def i14970 = { + val source = + s"""class Foo { + | def foo: Int | Nothing = 1 + | def bar: Nothing | Int = 1 + |} + """.stripMargin + + checkBCode(source) { dir => + val clsIn = dir.lookupName("Foo.class", directory = false).input + val clsNode = loadClassNode(clsIn) + def testSig(methodName: String, expectedSignature: String) = { + val signature = clsNode.methods.asScala.filter(_.name == methodName).map(_.signature) + assertEquals(List(expectedSignature), signature) + } + testSig("foo", "()I") + testSig("bar", "()I") + } + } } object invocationReceiversTestCode { diff --git a/tests/neg/i5823.scala b/tests/neg/i5823.scala index 673cdfc65e86..28c27b0c747a 100644 --- a/tests/neg/i5823.scala +++ b/tests/neg/i5823.scala @@ -7,7 +7,7 @@ class A class B class Foo { - + // ok, because A and B are <: Object. def foo(a: A|Null): Unit = () def foo(b: B|Null): Unit = () @@ -26,13 +26,15 @@ class Foo { def foo2(a: A|Nothing): Unit = () def foo2(b: B|Nothing): Unit = () + // ok because erased to primitive types def bar2(a: Int|Nothing): Unit = () - def bar2(b: Boolean|Nothing): Unit = () // error: signatures match + def bar2(b: Boolean|Nothing): Unit = () // ok, T is erased to `String` and `Integer`, respectively def gen3[T <: String](s: T|Nothing): Unit = () def gen3[T <: Integer](i: T|Nothing): Unit = () + // ok because erased to primitive types def gen4[T <: Int](i: T|Nothing): Unit = () - def gen4[T <: Boolean](b: T|Nothing): Unit = () // error: signatures match + def gen4[T <: Boolean](b: T|Nothing): Unit = () } diff --git a/tests/run/i14964.scala b/tests/run/i14964.scala new file mode 100644 index 000000000000..33970caccf1e --- /dev/null +++ b/tests/run/i14964.scala @@ -0,0 +1,5 @@ +@main def Test: Unit = + val xs = (1, 2).toList + val a1 = xs.toArray + println((1, 2).toList.toArray) + val a2 = (1, 2).toList.toArray diff --git a/tests/run/i14964b.check b/tests/run/i14964b.check new file mode 100644 index 000000000000..f55e93967e38 --- /dev/null +++ b/tests/run/i14964b.check @@ -0,0 +1,4 @@ +Int +Int +Int +Int diff --git a/tests/run/i14964b.scala b/tests/run/i14964b.scala new file mode 100644 index 000000000000..2da4898ddb45 --- /dev/null +++ b/tests/run/i14964b.scala @@ -0,0 +1,5 @@ +@main def Test: Unit = + println(summon[reflect.ClassTag[Int]]) // classOf[Int] + println(summon[reflect.ClassTag[Int | Int]]) // classOf[Int] + println(summon[reflect.ClassTag[Int | 1]]) // classOf[Int] + println(summon[reflect.ClassTag[Int | Nothing]]) // classOf[Int] diff --git a/tests/run/i14970.scala b/tests/run/i14970.scala new file mode 100644 index 000000000000..e468e4a1a1b8 --- /dev/null +++ b/tests/run/i14970.scala @@ -0,0 +1,6 @@ +object Test { + def main(args: Array[String]): Unit = { + val x: Array[Int | Nothing] = Array() + val y: Array[Int] = x + } +}