Skip to content

Commit f73483e

Browse files
authored
Merge pull request #2400 from dotty-staging/implement-phantom-types-part-3
Don't generate fields for BoxedUnit val and var
2 parents e9fd333 + c30b2db commit f73483e

30 files changed

+381
-35
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import dotty.tools.dotc.core.Types.Type
99
*
1010
* - Parameters/arguments are erased to BoxedUnit. The next step will remove the parameters
1111
* from the method definitions and calls (implemented in branch implement-phantom-types-part-2).
12-
* - Definitions of `def`, `val`, `lazy val` and `var` returning a phantom type to return a BoxedUnit. The next step
13-
* is to erase the fields for phantom types (implemented in branch implement-phantom-types-part-3)
12+
* - Definitions of `def`, `val`, `lazy val` and `var` returning a phantom type to return a BoxedUnit. Where fields
13+
* with BoxedUnit type are not memoized (see transform/Memoize.scala).
1414
* - Calls to Phantom.assume become calls to BoxedUnit. Intended to be optimized away by local optimizations.
1515
*
1616
* BoxedUnit is used as it fits perfectly and homogeneously in all locations where phantoms can be found.

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,14 @@ class FirstTransform extends MiniPhaseTransform with InfoTransformer with Annota
161161
} else ddef
162162
}
163163

164+
override def transformValDef(vdef: tpd.ValDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = {
165+
if (vdef.tpt.tpe.isPhantom) {
166+
if (vdef.symbol.is(Mutable)) ctx.error("var fields cannot have Phantom types", vdef.pos)
167+
else if (vdef.symbol.hasAnnotation(defn.VolatileAnnot)) ctx.error("Phantom fields cannot be @volatile", vdef.pos)
168+
}
169+
vdef
170+
}
171+
164172
override def transformStats(trees: List[Tree])(implicit ctx: Context, info: TransformerInfo): List[Tree] =
165173
ast.Trees.flatten(reorderAndComplete(trees)(ctx.withPhase(thisTransformer.next)))
166174

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

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,18 +98,40 @@ import Decorators._
9898

9999
val NoFieldNeeded = Lazy | Deferred | JavaDefined | (if (ctx.settings.YnoInline.value) EmptyFlags else Inline)
100100

101+
def isErasableBottomField(cls: Symbol): Boolean = {
102+
// TODO: For Scala.js, return false if this field is in a js.Object unless it was a Phantom before erasure.
103+
// Could time travel to detect phantom types or add an annotation before erasure.
104+
!field.isVolatile && ((cls eq defn.NothingClass) || (cls eq defn.NullClass) || (cls eq defn.BoxedUnitClass))
105+
}
106+
107+
def erasedBottomTree(sym: Symbol) = {
108+
if (sym eq defn.NothingClass) Throw(Literal(Constant(null)))
109+
else if (sym eq defn.NullClass) Literal(Constant(null))
110+
else {
111+
assert(sym eq defn.BoxedUnitClass)
112+
ref(defn.BoxedUnit_UNIT)
113+
}
114+
}
115+
101116
if (sym.is(Accessor, butNot = NoFieldNeeded))
102117
if (sym.isGetter) {
103118
var rhs = tree.rhs.changeOwnerAfter(sym, field, thisTransform)
104119
if (isWildcardArg(rhs)) rhs = EmptyTree
105120
val fieldDef = transformFollowing(ValDef(field, adaptToField(rhs)))
106-
val getterDef = cpy.DefDef(tree)(rhs = transformFollowingDeep(ref(field))(ctx.withOwner(sym), info))
121+
val rhsClass = tree.tpt.tpe.widenDealias.classSymbol
122+
val getterRhs =
123+
if (isErasableBottomField(rhsClass)) erasedBottomTree(rhsClass)
124+
else transformFollowingDeep(ref(field))(ctx.withOwner(sym), info)
125+
val getterDef = cpy.DefDef(tree)(rhs = getterRhs)
107126
Thicket(fieldDef, getterDef)
108127
} else if (sym.isSetter) {
109128
if (!sym.is(ParamAccessor)) { val Literal(Constant(())) = tree.rhs } // this is intended as an assertion
110129
field.setFlag(Mutable) // necessary for vals mixed in from Scala2 traits
111-
val initializer = Assign(ref(field), adaptToField(ref(tree.vparamss.head.head.symbol)))
112-
cpy.DefDef(tree)(rhs = transformFollowingDeep(initializer)(ctx.withOwner(sym), info))
130+
if (isErasableBottomField(tree.vparamss.head.head.tpt.tpe.classSymbol)) tree
131+
else {
132+
val initializer = Assign(ref(field), adaptToField(ref(tree.vparamss.head.head.symbol)))
133+
cpy.DefDef(tree)(rhs = transformFollowingDeep(initializer)(ctx.withOwner(sym), info))
134+
}
113135
}
114136
else tree // curiously, some accessors from Scala2 have ' ' suffixes. They count as
115137
// neither getters nor setters

tests/neg/phantom-var.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
class Foo {
3+
var foo = Boo.boo // error: var fields cannot have Phantom types
4+
}
5+
6+
object Boo extends Phantom {
7+
def boo = assume
8+
}

tests/neg/phantom-volitile.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
class Foo {
3+
@volatile var foo1 = Boo.boo // error: var fields cannot have Phantom types
4+
@volatile val foo2 = Boo.boo // error: Phantom fields cannot be @volatile
5+
}
6+
7+
object Boo extends Phantom {
8+
def boo = assume
9+
}

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.check

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

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
object Test {
3+
4+
def main(args: Array[String]): Unit = {
5+
val f = new Foo
6+
println(1)
7+
f.foo
8+
println(2)
9+
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+
import Boo._
21+
22+
lazy val foo = {
23+
println("foo")
24+
any
25+
}
26+
}
27+
28+
object Boo extends Phantom {
29+
type BooAny = this.Any
30+
def any: BooAny = assume
31+
}

tests/run/phantom-val-2.check

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

tests/run/phantom-val-2.scala

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

tests/run/phantom-var.scala

Lines changed: 0 additions & 30 deletions
This file was deleted.

tests/run/unit-lazy-val.check

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

tests/run/unit-lazy-val.scala

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
object Test {
3+
4+
def main(args: Array[String]): Unit = {
5+
val f = new Foo
6+
println(1)
7+
f.foo
8+
println(2)
9+
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: Unit = {
21+
println("foo")
22+
}
23+
}

0 commit comments

Comments
 (0)