Skip to content

Commit 4375aff

Browse files
committed
Erasure: properly handle null in value classes
This fixes the issues reported in SI-5866 and SI-8097
1 parent 977301a commit 4375aff

File tree

3 files changed

+50
-9
lines changed

3 files changed

+50
-9
lines changed

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

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -170,15 +170,27 @@ 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+
// See tests/pos/vcnull.scala for cases where this might happen.
173180
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
181+
if (tree.tpe isRef defn.NullClass)
182+
adaptToType(tree, underlying)
183+
else if (!(tree.tpe <:< clazz.typeRef)) {
184+
assert(!(tree.tpe.typeSymbol.isPrimitiveValueClass))
185+
val nullTree = Literal(Constant(null))
186+
val unboxedNull = adaptToType(nullTree, underlying)
187+
evalOnce(tree) { t =>
188+
If(t.select(defn.Object_eq).appliedTo(nullTree),
189+
unboxedNull,
190+
unboxedTree(t))
191+
}
192+
} else unboxedTree(tree)
193+
182194
cast(tree1, pt)
183195
case _ =>
184196
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, isSemi = false)

tests/pos/vcnull.scala

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// These issues were originally reported in SI-5866 and SI-8097
2+
3+
case class Foo(d: Double) extends AnyVal {
4+
override def toString = s"Foo($d)"
5+
}
6+
case class Bar(s: String) extends AnyVal {
7+
override def toString = s"Bar($s)"
8+
}
9+
10+
object VCNull {
11+
def testDirect(): Unit = {
12+
val fooDirect: Foo = null.asInstanceOf[Foo]
13+
val barDirect: Bar = null.asInstanceOf[Bar]
14+
}
15+
16+
def testIndirect(): Unit = {
17+
val fooIndirect: Foo = { val n: Any = null; n.asInstanceOf[Foo] }
18+
val barIndirect: Bar = { val n: Any = null; n.asInstanceOf[Bar] }
19+
}
20+
21+
def nullOf[T]: T = null.asInstanceOf[T]
22+
def testGeneric(): Unit = {
23+
val fooGeneric: Foo = nullOf[Foo]
24+
val barGeneric: Bar = nullOf[Bar]
25+
}
26+
}

0 commit comments

Comments
 (0)