Skip to content

Commit 182847c

Browse files
committed
Aligns | Nothing with & Any and Nothing | with Any & for value types.
This also fixes `summon[ClassTag[Int | Nothing]]` as it is now equivalent to `summon[ClassTag[Int]]`. Fixes #14970 Fixes #14964 > ⚠️ This is a binary breaking change. > the Previous version generated the `def f: Int` and `def f: Object` variants of the method. > In the new one, we only generate the `def f: Int` version. > Unfortunately, the previous version called the boxed version of the interface that does not exist anymore. > > Is this something that we encounter in practice?
1 parent 7db4121 commit 182847c

File tree

6 files changed

+50
-5
lines changed

6 files changed

+50
-5
lines changed

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,10 +365,18 @@ object TypeErasure {
365365
def erasedLub(tp1: Type, tp2: Type)(using Context): Type = {
366366
// After erasure, C | {Null, Nothing} is just C, if C is a reference type.
367367
// 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
368+
// relies on the class hierarchy, which doesn't properly capture `Null`/`Nothing`s subtyping
369369
// behaviour.
370370
if (tp1.isBottomTypeAfterErasure && tp2.derivesFrom(defn.ObjectClass)) return tp2
371371
if (tp2.isBottomTypeAfterErasure && tp1.derivesFrom(defn.ObjectClass)) return tp1
372+
373+
// After erasure, A | Nothing is just A, if A is a value type.
374+
// We need to short-circuit this case here because the regular lub logic below
375+
// relies on the class hierarchy, which doesn't properly capture `Nothing`s subtyping
376+
// behaviour.
377+
if (tp1.isExactlyNothing && tp2.derivesFrom(defn.AnyValClass)) return valueErasure(tp2)
378+
if (tp2.isExactlyNothing && tp1.derivesFrom(defn.AnyValClass)) return valueErasure(tp1)
379+
372380
tp1 match {
373381
case JavaArrayType(elem1) =>
374382
import dotty.tools.dotc.transform.TypeUtils._

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 = """
@@ -1234,6 +1234,27 @@ class TestBCode extends DottyBytecodeTest {
12341234
Label(9), Op(IRETURN)))
12351235
}
12361236
}
1237+
1238+
/** Check that erasure if `Int | Nothing` is `int` */
1239+
@Test def i14970 = {
1240+
val source =
1241+
s"""class Foo {
1242+
| def foo: Int | Nothing = 1
1243+
| def bar: Nothing | Int = 1
1244+
|}
1245+
""".stripMargin
1246+
1247+
checkBCode(source) { dir =>
1248+
val clsIn = dir.lookupName("Foo.class", directory = false).input
1249+
val clsNode = loadClassNode(clsIn)
1250+
def testSig(methodName: String, expectedSignature: String) = {
1251+
val signature = clsNode.methods.asScala.filter(_.name == methodName).map(_.signature)
1252+
assertEquals(List(expectedSignature), signature)
1253+
}
1254+
testSig("foo", "()I")
1255+
testSig("bar", "()I")
1256+
}
1257+
}
12371258
}
12381259

12391260
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[Object]

0 commit comments

Comments
 (0)