Skip to content

Commit d829f7c

Browse files
Merge pull request #14971 from dotty-staging/fix-nothing-or-value-type-erasure
Erase `Int | Nothing` signatures types into `Int`
2 parents 2f0dcd0 + 9cd8ce8 commit d829f7c

File tree

7 files changed

+54
-11
lines changed

7 files changed

+54
-11
lines changed

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -363,13 +363,13 @@ object TypeErasure {
363363
* which leads to more predictable bytecode and (?) faster dynamic dispatch.
364364
*/
365365
def erasedLub(tp1: Type, tp2: Type)(using Context): Type = {
366-
// After erasure, C | {Null, Nothing} is just C, if C is a reference type.
367-
// We need to short-circuit this case here because the regular lub logic below
368-
// relies on the class hierarchy, which doesn't properly capture `Null`s subtyping
369-
// behaviour.
370-
if (tp1.isBottomTypeAfterErasure && tp2.derivesFrom(defn.ObjectClass)) return tp2
371-
if (tp2.isBottomTypeAfterErasure && tp1.derivesFrom(defn.ObjectClass)) return tp1
372-
tp1 match {
366+
// We need to short-circuit the following 2 case because the regular lub logic in the else relies on
367+
// the class hierarchy, which doesn't properly capture `Nothing`/`Null` subtyping behaviour.
368+
if tp1.isRef(defn.NothingClass) || (tp1.isRef(defn.NullClass) && tp2.derivesFrom(defn.ObjectClass)) then
369+
tp2 // After erasure, Nothing | T is just T and Null | C is just C, if C is a reference type.
370+
else if tp2.isRef(defn.NothingClass) || (tp2.isRef(defn.NullClass) && tp1.derivesFrom(defn.ObjectClass)) then
371+
tp1 // After erasure, T | Nothing is just T and C | Null is just C, if C is a reference type.
372+
else tp1 match {
373373
case JavaArrayType(elem1) =>
374374
import dotty.tools.dotc.transform.TypeUtils._
375375
tp2 match {

compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import scala.tools.asm.Opcodes
1313
import scala.jdk.CollectionConverters._
1414
import Opcodes._
1515

16-
class TestBCode extends DottyBytecodeTest {
16+
class DottyBytecodeTests extends DottyBytecodeTest {
1717
import ASMConverters._
1818
@Test def nullChecks = {
1919
val source = """
@@ -1556,6 +1556,27 @@ class TestBCode extends DottyBytecodeTest {
15561556
Label(9), Op(IRETURN)))
15571557
}
15581558
}
1559+
1560+
/** Check that erasure if `Int | Nothing` is `int` */
1561+
@Test def i14970 = {
1562+
val source =
1563+
s"""class Foo {
1564+
| def foo: Int | Nothing = 1
1565+
| def bar: Nothing | Int = 1
1566+
|}
1567+
""".stripMargin
1568+
1569+
checkBCode(source) { dir =>
1570+
val clsIn = dir.lookupName("Foo.class", directory = false).input
1571+
val clsNode = loadClassNode(clsIn)
1572+
def testSig(methodName: String, expectedSignature: String) = {
1573+
val signature = clsNode.methods.asScala.filter(_.name == methodName).map(_.signature)
1574+
assertEquals(List(expectedSignature), signature)
1575+
}
1576+
testSig("foo", "()I")
1577+
testSig("bar", "()I")
1578+
}
1579+
}
15591580
}
15601581

15611582
object invocationReceiversTestCode {

tests/neg/i5823.scala

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class A
77
class B
88

99
class Foo {
10-
10+
1111
// ok, because A and B are <: Object.
1212
def foo(a: A|Null): Unit = ()
1313
def foo(b: B|Null): Unit = ()
@@ -26,13 +26,15 @@ class Foo {
2626
def foo2(a: A|Nothing): Unit = ()
2727
def foo2(b: B|Nothing): Unit = ()
2828

29+
// ok because erased to primitive types
2930
def bar2(a: Int|Nothing): Unit = ()
30-
def bar2(b: Boolean|Nothing): Unit = () // error: signatures match
31+
def bar2(b: Boolean|Nothing): Unit = ()
3132

3233
// ok, T is erased to `String` and `Integer`, respectively
3334
def gen3[T <: String](s: T|Nothing): Unit = ()
3435
def gen3[T <: Integer](i: T|Nothing): Unit = ()
3536

37+
// ok because erased to primitive types
3638
def gen4[T <: Int](i: T|Nothing): Unit = ()
37-
def gen4[T <: Boolean](b: T|Nothing): Unit = () // error: signatures match
39+
def gen4[T <: Boolean](b: T|Nothing): Unit = ()
3840
}

tests/run/i14964.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@main def Test: Unit =
2+
val xs = (1, 2).toList
3+
val a1 = xs.toArray
4+
println((1, 2).toList.toArray)
5+
val a2 = (1, 2).toList.toArray

tests/run/i14964b.check

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

tests/run/i14964b.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@main def Test: Unit =
2+
println(summon[reflect.ClassTag[Int]]) // classOf[Int]
3+
println(summon[reflect.ClassTag[Int | Int]]) // classOf[Int]
4+
println(summon[reflect.ClassTag[Int | 1]]) // classOf[Int]
5+
println(summon[reflect.ClassTag[Int | Nothing]]) // classOf[Int]

tests/run/i14970.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
object Test {
2+
def main(args: Array[String]): Unit = {
3+
val x: Array[Int | Nothing] = Array()
4+
val y: Array[Int] = x
5+
}
6+
}

0 commit comments

Comments
 (0)