Skip to content

Commit 089fe04

Browse files
committed
Erasure: properly handle null in value classes
This fixes the issues reported in SI-5866 and SI-8097
1 parent 67f2854 commit 089fe04

File tree

4 files changed

+54
-9
lines changed

4 files changed

+54
-9
lines changed

src/dotty/tools/dotc/transform/Erasure.scala

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -170,15 +170,29 @@ object Erasure extends TypeTestsCasts{
170170
def unbox(tree: Tree, pt: Type)(implicit ctx: Context): Tree = ctx.traceIndented(i"unboxing ${tree.showSummary}: ${tree.tpe} as a $pt") {
171171
pt match {
172172
case ErasedValueType(clazz, underlying) =>
173+
def unboxedTree(t: Tree) =
174+
adaptToType(t, clazz.typeRef)
175+
.select(valueClassUnbox(clazz))
176+
.appliedToNone
177+
178+
// Null unboxing needs to be treated separately since we cannot call a method on null.
179+
// "Unboxing" null to underlying is equivalent to doing null.asInstanceOf[underlying]
180+
// See tests/pos/valueclasses/nullAsInstanceOfVC.scala for cases where this might happen.
173181
val tree1 =
174-
if ((tree.tpe isRef defn.NullClass) && underlying.isPrimitiveValueType)
175-
// convert `null` directly to underlying type, as going
176-
// via the unboxed type would yield a NPE (see SI-5866)
177-
unbox(tree, underlying)
178-
else
179-
adaptToType(tree, clazz.typeRef)
180-
.select(valueClassUnbox(clazz))
181-
.appliedToNone
182+
if (tree.tpe isRef defn.NullClass)
183+
adaptToType(tree, underlying)
184+
else if (!(tree.tpe <:< clazz.typeRef)) {
185+
assert(!(tree.tpe.typeSymbol.isPrimitiveValueClass))
186+
val nullTree = Literal(Constant(null))
187+
val unboxedNull = adaptToType(nullTree, underlying)
188+
189+
evalOnce(tree) { t =>
190+
If(t.select(defn.Object_eq).appliedTo(nullTree),
191+
unboxedNull,
192+
unboxedTree(t))
193+
}
194+
} else unboxedTree(tree)
195+
182196
cast(tree1, pt)
183197
case _ =>
184198
val cls = pt.widen.classSymbol

src/dotty/tools/dotc/transform/TypeTestsCasts.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import typer.ErrorReporting._
1414
import ast.Trees._
1515
import Erasure.Boxing._
1616
import core.TypeErasure._
17+
import ValueClasses._
1718

1819
/** This transform normalizes type tests and type casts,
1920
* also replacing type tests with singleton argument type with reference equality check
@@ -90,7 +91,9 @@ trait TypeTestsCasts {
9091
}
9192
else if (argCls.isPrimitiveValueClass)
9293
unbox(qual.ensureConforms(defn.ObjectType), argType)
93-
else
94+
else if (isDerivedValueClass(argCls)) {
95+
qual // adaptToType in Erasure will do the necessary type adaptation
96+
} else
9497
derivedTree(qual, defn.Any_asInstanceOf, argType)
9598
}
9699
def erasedArg = erasure(tree.args.head.tpe, semiEraseVCs = false)

test/dotc/tests.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ class tests extends CompilerTest {
7979
@Test def pos_overloadedAccess = compileFile(posDir, "overloadedAccess", twice)
8080
@Test def pos_approximateUnion = compileFile(posDir, "approximateUnion", twice)
8181
@Test def pos_tailcall = compileDir(posDir, "tailcall", twice)
82+
@Test def pos_valueclasses = compileDir(posDir, "valueclasses", twice)
8283
@Test def pos_nullarify = compileFile(posDir, "nullarify", args = "-Ycheck:nullarify" :: Nil)
8384
@Test def pos_subtyping = compileFile(posDir, "subtyping", twice)
8485
@Test def pos_t2613 = compileFile(posSpecialDir, "t2613")(allowDeepSubtypes)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// These issues were originally reported in SI-5866 and SI-8097
2+
// FIXME: Make this a run test once we have run tests.
3+
4+
object VCNull {
5+
case class Foo(d: Double) extends AnyVal {
6+
override def toString = s"Foo($d)"
7+
}
8+
case class Bar(s: String) extends AnyVal {
9+
override def toString = s"Bar($s)"
10+
}
11+
12+
def testDirect(): Unit = {
13+
val fooDirect: Foo = null.asInstanceOf[Foo]
14+
val barDirect: Bar = null.asInstanceOf[Bar]
15+
}
16+
17+
def testIndirect(): Unit = {
18+
val fooIndirect: Foo = { val n: Any = null; n.asInstanceOf[Foo] }
19+
val barIndirect: Bar = { val n: Any = null; n.asInstanceOf[Bar] }
20+
}
21+
22+
def nullOf[T]: T = null.asInstanceOf[T]
23+
def testGeneric(): Unit = {
24+
val fooGeneric: Foo = nullOf[Foo]
25+
val barGeneric: Bar = nullOf[Bar]
26+
}
27+
}

0 commit comments

Comments
 (0)