Skip to content

Commit 38c486c

Browse files
committed
Erase fields for Null/Nothing val and var
Getters for Null fields return trivially `null`. Getter for Nothing will `throw null`. This is only be reachable if the field is accesed in the costructor before the field is initialized. In this case a `NullPointerException` is thrown as before.
1 parent 16756e4 commit 38c486c

15 files changed

+193
-8
lines changed

compiler/src/dotty/tools/dotc/transform/Memoize.scala

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,17 +103,22 @@ import Decorators._
103103
var rhs = tree.rhs.changeOwnerAfter(sym, field, thisTransform)
104104
if (isWildcardArg(rhs)) rhs = EmptyTree
105105
val fieldDef = transformFollowing(ValDef(field, adaptToField(rhs)))
106-
val isUnitField = tree.tpt.tpe.widenDealias =:= defn.BoxedUnitType
107-
val getterRhs =
108-
if (isUnitField) ref(defn.BoxedUnit_UNIT)
106+
val rhsClass = tree.tpt.tpe.widenDealias.classSymbol
107+
val getterRhs = {
108+
if (rhsClass eq defn.BoxedUnitClass) ref(defn.BoxedUnit_UNIT)
109+
else if (rhsClass eq defn.NullClass) Literal(Constant(null))
110+
else if (rhsClass eq defn.NothingClass) Throw(Literal(Constant(null)))
109111
else transformFollowingDeep(ref(field))(ctx.withOwner(sym), info)
112+
}
110113
val getterDef = cpy.DefDef(tree)(rhs = getterRhs)
111114
Thicket(fieldDef, getterDef)
112115
} else if (sym.isSetter) {
113116
if (!sym.is(ParamAccessor)) { val Literal(Constant(())) = tree.rhs } // this is intended as an assertion
114117
field.setFlag(Mutable) // necessary for vals mixed in from Scala2 traits
115-
if (tree.vparamss.head.head.tpt.tpe =:= defn.BoxedUnitType) tree
116-
else {
118+
val rhsCls = tree.vparamss.head.head.tpt.tpe.classSymbol
119+
if ((rhsCls eq defn.BoxedUnitClass) || (rhsCls eq defn.NullClass) || (rhsCls eq defn.NothingClass)) {
120+
tree
121+
} else {
117122
val initializer = Assign(ref(field), adaptToField(ref(tree.vparamss.head.head.symbol)))
118123
cpy.DefDef(tree)(rhs = transformFollowingDeep(initializer)(ctx.withOwner(sym), info))
119124
}

tests/run/nothing-lazy-val.check

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

tests/run/nothing-lazy-val.scala

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
object Test {
3+
def main(args: Array[String]): Unit = {
4+
try {
5+
println("0")
6+
val f = new Foo
7+
println("1")
8+
println(f.foo)
9+
println("2")
10+
println(f.foo)
11+
} catch {
12+
case e: NotImplementedError => println("???")
13+
}
14+
15+
// TODO: Erase
16+
// Currently not erasing fields for lazy vals
17+
assert(classOf[Foo].getDeclaredFields.exists(_.getName.startsWith("foo")), "Field foo erased. Optimized accidentally?")
18+
}
19+
20+
21+
}
22+
23+
class Foo {
24+
lazy val foo: Nothing = {
25+
println("foo")
26+
???
27+
}
28+
}

tests/run/nothing-val.check

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

tests/run/nothing-val.scala

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
2+
object Test {
3+
def main(args: Array[String]): Unit = {
4+
try {
5+
println("0")
6+
val f = new Foo
7+
println("1")
8+
println(f.foo)
9+
} catch {
10+
case e: NotImplementedError => println("???")
11+
}
12+
13+
assert(!classOf[Foo].getDeclaredFields.exists(_.getName.startsWith("foo")), "field foo not erased")
14+
}
15+
16+
17+
}
18+
19+
class Foo {
20+
val foo: Nothing = {
21+
println("foo")
22+
???
23+
}
24+
}

tests/run/nothing-var.check

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

tests/run/nothing-var.scala

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
2+
object Test {
3+
def main(args: Array[String]): Unit = {
4+
try {
5+
println("0")
6+
val f = new Foo
7+
println("1")
8+
f.foo
9+
f.foo
10+
f.foo = {
11+
println("foo3")
12+
???
13+
}
14+
println(f.foo)
15+
} catch {
16+
case e: NotImplementedError => println("???")
17+
}
18+
19+
assert(!classOf[Foo].getDeclaredFields.exists(_.getName.startsWith("foo")), "field foo not erased")
20+
}
21+
22+
23+
}
24+
25+
class Foo {
26+
var foo: Nothing = {
27+
println("foo")
28+
???
29+
}
30+
31+
foo = {
32+
println("foo2")
33+
???
34+
}
35+
}

tests/run/null-lazy-val.check

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

tests/run/null-lazy-val.scala

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
2+
object Test {
3+
4+
def main(args: Array[String]): Unit = {
5+
val f = new Foo
6+
println(1)
7+
println(f.foo)
8+
println(2)
9+
println(f.foo)
10+
11+
// TODO: Erase
12+
// Currently not erasing fields for lazy vals
13+
assert(f.getClass.getDeclaredFields.exists(_.getName.startsWith("foo")), "Field foo erased. Optimized accidentally?")
14+
}
15+
16+
17+
}
18+
19+
class Foo {
20+
lazy val foo: Null = {
21+
println("foo")
22+
null
23+
}
24+
}

tests/run/null-val.check

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

tests/run/null-val.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
object Test {
3+
def main(args: Array[String]): Unit = {
4+
val f = new Foo
5+
println(f.foo)
6+
println(f.foo)
7+
assert(!f.getClass.getDeclaredFields.exists(_.getName.startsWith("foo")), "field foo not erased")
8+
}
9+
}
10+
11+
class Foo {
12+
val foo: Null = {
13+
println("foo")
14+
null
15+
}
16+
}

tests/run/null-var.check

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
foo
2+
foo2
3+
null
4+
foo3
5+
null

tests/run/null-var.scala

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
2+
object Test {
3+
def main(args: Array[String]): Unit = {
4+
val f = new Foo
5+
println(f.foo)
6+
f.foo
7+
f.foo = {
8+
println("foo3")
9+
null
10+
}
11+
println(f.foo)
12+
assert(!f.getClass.getDeclaredFields.exists(_.getName.startsWith("foo")), "field foo not erased")
13+
}
14+
}
15+
16+
class Foo {
17+
var foo: Null = {
18+
println("foo")
19+
null
20+
}
21+
22+
foo = {
23+
println("foo2")
24+
null
25+
}
26+
}

tests/run/phantom-lazy-val-2.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ object Test {
77
f.foo
88
println(2)
99
f.foo
10-
// Currently not erasing fields for lazy phantom vals
11-
// assert(!f.getClass.getDeclaredFields.exists(_.getName.startsWith("foo")), "field foo not erased")
10+
11+
// TODO: Erase
12+
// Currently not erasing fields for lazy vals
13+
assert(f.getClass.getDeclaredFields.exists(_.getName.startsWith("foo")), "Field foo erased. Optimized accidentally?")
1214
}
1315

1416

tests/run/unit-lazy-val.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ object Test {
77
f.foo
88
println(2)
99
f.foo
10+
11+
// TODO: Erase
1012
// Currently not erasing fields for lazy vals
11-
// assert(!f.getClass.getDeclaredFields.exists(_.getName.startsWith("foo")), "field foo not erased")
13+
assert(f.getClass.getDeclaredFields.exists(_.getName.startsWith("foo")), "Field foo erased. Optimized accidentally?")
1214
}
1315

1416

0 commit comments

Comments
 (0)