Skip to content

Commit d800e31

Browse files
committed
Apply optimisation on private[this] fields only
1 parent 78f83c9 commit d800e31

File tree

4 files changed

+39
-37
lines changed

4 files changed

+39
-37
lines changed

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

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,23 +34,19 @@ object CollectNullableFields {
3434
*
3535
* A field is nullable if all the conditions below hold:
3636
* - belongs to a non trait-class
37-
* - is private
37+
* - is private[this]
3838
* - is not lazy
39-
* - its type is nullable, or is an expression type (e.g. => Int)
40-
* - is on used in a lazy val initializer
39+
* - its type is nullable
40+
* - is only used in a lazy val initializer
4141
* - defined in the same class as the lazy val
4242
*/
4343
class CollectNullableFields extends MiniPhase {
4444
import tpd._
4545

4646
override def phaseName = CollectNullableFields.name
4747

48-
/** Running after `ElimByName` to see by names as nullable types.
49-
*
50-
* We don't necessary need to run after `Getters`, but the implementation
51-
* could be simplified if we were to run before.
52-
*/
53-
override def runsAfter = Set(Getters.name, ElimByName.name)
48+
/** Running after `ElimByName` to see by names as nullable types. */
49+
override def runsAfter = Set(ElimByName.name)
5450

5551
private[this] sealed trait FieldInfo
5652
private[this] case object NotNullable extends FieldInfo
@@ -65,14 +61,12 @@ class CollectNullableFields extends MiniPhase {
6561
}
6662

6763
private def recordUse(tree: Tree)(implicit ctx: Context): Tree = {
68-
def isField(sym: Symbol) =
69-
sym.isField || sym.isGetter // running after phase Getters
70-
7164
val sym = tree.symbol
7265
val isNullablePrivateField =
73-
isField(sym) &&
74-
sym.is(Private, butNot = Lazy) &&
66+
sym.isField &&
67+
!sym.is(Lazy) &&
7568
!sym.owner.is(Trait) &&
69+
sym.initial.is(PrivateLocal) &&
7670
sym.info.widenDealias.typeSymbol.isNullableClass
7771

7872
if (isNullablePrivateField)
@@ -82,8 +76,9 @@ class CollectNullableFields extends MiniPhase {
8276
case null => // not in the map
8377
val from = ctx.owner
8478
val isNullable =
85-
from.is(Lazy) && isField(from) && // used in lazy field initializer
86-
from.owner.eq(sym.owner) // lazy val and field defined in the same class
79+
from.is(Lazy, butNot = Module) && // is lazy val
80+
from.owner.isClass && // is field
81+
from.owner.eq(sym.owner) // is lazy val and field defined in the same class
8782
val info = if (isNullable) Nullable(from) else NotNullable
8883
nullability.put(sym, info)
8984
case _ =>

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ import ValueClasses._
4949
class Getters extends MiniPhase with SymTransformer {
5050
import ast.tpd._
5151

52-
override def phaseName = Getters.name
52+
override def phaseName = "getters"
5353

5454
override def transformSym(d: SymDenotation)(implicit ctx: Context): SymDenotation = {
5555
def noGetterNeeded =
@@ -74,7 +74,3 @@ class Getters extends MiniPhase with SymTransformer {
7474
override def transformAssign(tree: Assign)(implicit ctx: Context): Tree =
7575
if (tree.lhs.symbol is Method) tree.lhs.becomes(tree.rhs).withPos(tree.pos) else tree
7676
}
77-
78-
object Getters {
79-
val name = "getters"
80-
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
5555
/** A map of lazy values to the fields they should null after initialization. */
5656
private[this] var lazyValNullables: IdentityHashMap[Symbol, mutable.ListBuffer[Symbol]] = _
5757
private def nullableFor(sym: Symbol)(implicit ctx: Context) = {
58+
// optimisation: value only used once, we can remove the value from the map
5859
val nullables = lazyValNullables.remove(sym)
59-
if (nullables == null || sym.is(Flags.Module)) Nil
60+
if (nullables == null) Nil
6061
else nullables.toList
6162
}
6263

@@ -195,8 +196,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
195196

196197
private def nullOut(nullables: List[Symbol])(implicit ctx: Context): List[Tree] = {
197198
val nullConst = Literal(Constants.Constant(null))
198-
nullables.map { sym =>
199-
val field = if (sym.isGetter) sym.field else sym
199+
nullables.map { field =>
200200
assert(field.isField)
201201
field.setFlag(Flags.Mutable)
202202
ref(field).becomes(nullConst)

tests/run/i1692.scala

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,46 +4,46 @@ class VCString(val x: String) extends AnyVal
44
class LazyNullable(a: => Int) {
55
lazy val l0 = a // null out a
66

7-
private val b = "B"
7+
private[this] val b = "B"
88
lazy val l1 = b // null out b
99

10-
private val c = "C"
10+
private[this] val c = "C"
1111
@volatile lazy val l2 = c // null out c
1212

13-
private val d = "D"
13+
private[this] val d = "D"
1414
lazy val l3 = d + d // null out d (Scalac require single use?)
1515
}
1616

1717
object LazyNullable2 {
18-
private val a = "A"
18+
private[this] val a = "A"
1919
lazy val l0 = a // null out a
2020
}
2121

2222
class LazyNotNullable {
23-
private val a = 'A'.toInt // not nullable type
23+
private[this] val a = 'A'.toInt // not nullable type
2424
lazy val l0 = a
2525

26-
private val b = new VCInt('B'.toInt) // not nullable type
26+
private[this] val b = new VCInt('B'.toInt) // not nullable type
2727
lazy val l1 = b
2828

29-
private val c = new VCString("C") // should be nullable but is not??
29+
private[this] val c = new VCString("C") // should be nullable but is not??
3030
lazy val l2 = c
3131

32-
private lazy val d = "D" // not nullable because lazy
32+
private[this] lazy val d = "D" // not nullable because lazy
3333
lazy val l3 = d
3434

35-
val e = "E" // not nullable because not private
35+
private val e = "E" // not nullable because not private[this]
3636
lazy val l4 = e
3737

38-
private val f = "F" // not nullable because used in mutiple lazy vals
38+
private[this] val f = "F" // not nullable because used in mutiple lazy vals
3939
lazy val l5 = f
4040
lazy val l6 = f
4141

42-
private val g = "G" // not nullable because used outside a lazy val initializer
42+
private[this] val g = "G" // not nullable because used outside a lazy val initializer
4343
def foo = g
4444
lazy val l7 = g
4545

46-
private val h = "H" // not nullable because field and lazy val not defined in the same class
46+
private[this] val h = "H" // not nullable because field and lazy val not defined in the same class
4747
class Inner {
4848
lazy val l8 = h
4949
}
@@ -54,6 +54,13 @@ trait LazyTrait {
5454
lazy val l0 = a
5555
}
5656

57+
class Foo(val x: String)
58+
59+
class LazyNotNullable2(x: String) extends Foo(x) {
60+
lazy val y = x // not nullable. Here x is super.x
61+
}
62+
63+
5764
object Test {
5865
def main(args: Array[String]): Unit = {
5966
nullableTests()
@@ -115,11 +122,15 @@ object Test {
115122

116123
val inner = new lz.Inner
117124
assert(inner.l8 == "H")
118-
assertNotNull("h")
125+
assertNotNull("LazyNotNullable$$h") // fragile: test will break if compiler generated names change
119126

120127
val fromTrait = new LazyTrait {}
121128
assert(fromTrait.l0 == "A")
122129
assert(readField("LazyTrait$$a", fromTrait) != null) // fragile: test will break if compiler generated names change
130+
131+
val lz2 = new LazyNotNullable2("Hello")
132+
assert(lz2.y == "Hello")
133+
assert(lz2.x == "Hello")
123134
}
124135

125136
def readField(fieldName: String, target: Any): Any = {

0 commit comments

Comments
 (0)