Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't generate fields for BoxedUnit val and var #2400

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/PhantomErasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import dotty.tools.dotc.core.Types.Type
*
* - Parameters/arguments are erased to BoxedUnit. The next step will remove the parameters
* from the method definitions and calls (implemented in branch implement-phantom-types-part-2).
* - Definitions of `def`, `val`, `lazy val` and `var` returning a phantom type to return a BoxedUnit. The next step
* is to erase the fields for phantom types (implemented in branch implement-phantom-types-part-3)
* - Definitions of `def`, `val`, `lazy val` and `var` returning a phantom type to return a BoxedUnit. Where fields
* with BoxedUnit type are not memoized (see transform/Memoize.scala).
* - Calls to Phantom.assume become calls to BoxedUnit. Intended to be optimized away by local optimizations.
*
* BoxedUnit is used as it fits perfectly and homogeneously in all locations where phantoms can be found.
Expand Down
8 changes: 8 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/FirstTransform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,14 @@ class FirstTransform extends MiniPhaseTransform with InfoTransformer with Annota
} else ddef
}

override def transformValDef(vdef: tpd.ValDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = {
if (vdef.tpt.tpe.isPhantom) {
if (vdef.symbol.is(Mutable)) ctx.error("var fields cannot have Phantom types", vdef.pos)
else if (vdef.symbol.hasAnnotation(defn.VolatileAnnot)) ctx.error("Phantom fields cannot be @volatile", vdef.pos)
}
vdef
}

override def transformStats(trees: List[Tree])(implicit ctx: Context, info: TransformerInfo): List[Tree] =
ast.Trees.flatten(reorderAndComplete(trees)(ctx.withPhase(thisTransformer.next)))

Expand Down
28 changes: 25 additions & 3 deletions compiler/src/dotty/tools/dotc/transform/Memoize.scala
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,40 @@ import Decorators._

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

def isErasableBottomField(cls: Symbol): Boolean = {
// TODO: For Scala.js, return false if this field is in a js.Object unless it was a Phantom before erasure.
// Could time travel to detect phantom types or add an annotation before erasure.
!field.isVolatile && ((cls eq defn.NothingClass) || (cls eq defn.NullClass) || (cls eq defn.BoxedUnitClass))
}

def erasedBottomTree(sym: Symbol) = {
if (sym eq defn.NothingClass) Throw(Literal(Constant(null)))
else if (sym eq defn.NullClass) Literal(Constant(null))
else {
assert(sym eq defn.BoxedUnitClass)
ref(defn.BoxedUnit_UNIT)
}
}

if (sym.is(Accessor, butNot = NoFieldNeeded))
if (sym.isGetter) {
var rhs = tree.rhs.changeOwnerAfter(sym, field, thisTransform)
if (isWildcardArg(rhs)) rhs = EmptyTree
val fieldDef = transformFollowing(ValDef(field, adaptToField(rhs)))
val getterDef = cpy.DefDef(tree)(rhs = transformFollowingDeep(ref(field))(ctx.withOwner(sym), info))
val rhsClass = tree.tpt.tpe.widenDealias.classSymbol
val getterRhs =
if (isErasableBottomField(rhsClass)) erasedBottomTree(rhsClass)
else transformFollowingDeep(ref(field))(ctx.withOwner(sym), info)
val getterDef = cpy.DefDef(tree)(rhs = getterRhs)
Thicket(fieldDef, getterDef)
} else if (sym.isSetter) {
if (!sym.is(ParamAccessor)) { val Literal(Constant(())) = tree.rhs } // this is intended as an assertion
field.setFlag(Mutable) // necessary for vals mixed in from Scala2 traits
val initializer = Assign(ref(field), adaptToField(ref(tree.vparamss.head.head.symbol)))
cpy.DefDef(tree)(rhs = transformFollowingDeep(initializer)(ctx.withOwner(sym), info))
if (isErasableBottomField(tree.vparamss.head.head.tpt.tpe.classSymbol)) tree
else {
val initializer = Assign(ref(field), adaptToField(ref(tree.vparamss.head.head.symbol)))
cpy.DefDef(tree)(rhs = transformFollowingDeep(initializer)(ctx.withOwner(sym), info))
}
}
else tree // curiously, some accessors from Scala2 have ' ' suffixes. They count as
// neither getters nor setters
Expand Down
8 changes: 8 additions & 0 deletions tests/neg/phantom-var.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

class Foo {
var foo = Boo.boo // error: var fields cannot have Phantom types
}

object Boo extends Phantom {
def boo = assume
}
9 changes: 9 additions & 0 deletions tests/neg/phantom-volitile.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

class Foo {
@volatile var foo1 = Boo.boo // error: var fields cannot have Phantom types
@volatile val foo2 = Boo.boo // error: Phantom fields cannot be @volatile
}

object Boo extends Phantom {
def boo = assume
}
4 changes: 4 additions & 0 deletions tests/run/nothing-lazy-val.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
0
1
foo
???
28 changes: 28 additions & 0 deletions tests/run/nothing-lazy-val.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

object Test {
def main(args: Array[String]): Unit = {
try {
println("0")
val f = new Foo
println("1")
println(f.foo)
println("2")
println(f.foo)
} catch {
case e: NotImplementedError => println("???")
}

// TODO: Erase
// Currently not erasing fields for lazy vals
assert(classOf[Foo].getDeclaredFields.exists(_.getName.startsWith("foo")), "Field foo erased. Optimized accidentally?")
}


}

class Foo {
lazy val foo: Nothing = {
println("foo")
???
}
}
3 changes: 3 additions & 0 deletions tests/run/nothing-val.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
0
foo
???
24 changes: 24 additions & 0 deletions tests/run/nothing-val.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

object Test {
def main(args: Array[String]): Unit = {
try {
println("0")
val f = new Foo
println("1")
println(f.foo)
} catch {
case e: NotImplementedError => println("???")
}

assert(!classOf[Foo].getDeclaredFields.exists(_.getName.startsWith("foo")), "field foo not erased")
}


}

class Foo {
val foo: Nothing = {
println("foo")
???
}
}
3 changes: 3 additions & 0 deletions tests/run/nothing-var.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
0
foo
???
35 changes: 35 additions & 0 deletions tests/run/nothing-var.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@

object Test {
def main(args: Array[String]): Unit = {
try {
println("0")
val f = new Foo
println("1")
f.foo
f.foo
f.foo = {
println("foo3")
???
}
println(f.foo)
} catch {
case e: NotImplementedError => println("???")
}

assert(!classOf[Foo].getDeclaredFields.exists(_.getName.startsWith("foo")), "field foo not erased")
}


}

class Foo {
var foo: Nothing = {
println("foo")
???
}

foo = {
println("foo2")
???
}
}
5 changes: 5 additions & 0 deletions tests/run/null-lazy-val.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
1
foo
null
2
null
24 changes: 24 additions & 0 deletions tests/run/null-lazy-val.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

object Test {

def main(args: Array[String]): Unit = {
val f = new Foo
println(1)
println(f.foo)
println(2)
println(f.foo)

// TODO: Erase
// Currently not erasing fields for lazy vals
assert(f.getClass.getDeclaredFields.exists(_.getName.startsWith("foo")), "Field foo erased. Optimized accidentally?")
}


}

class Foo {
lazy val foo: Null = {
println("foo")
null
}
}
3 changes: 3 additions & 0 deletions tests/run/null-val.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
foo
null
null
16 changes: 16 additions & 0 deletions tests/run/null-val.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

object Test {
def main(args: Array[String]): Unit = {
val f = new Foo
println(f.foo)
println(f.foo)
assert(!f.getClass.getDeclaredFields.exists(_.getName.startsWith("foo")), "field foo not erased")
}
}

class Foo {
val foo: Null = {
println("foo")
null
}
}
5 changes: 5 additions & 0 deletions tests/run/null-var.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
foo
foo2
null
foo3
null
26 changes: 26 additions & 0 deletions tests/run/null-var.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

object Test {
def main(args: Array[String]): Unit = {
val f = new Foo
println(f.foo)
f.foo
f.foo = {
println("foo3")
null
}
println(f.foo)
assert(!f.getClass.getDeclaredFields.exists(_.getName.startsWith("foo")), "field foo not erased")
}
}

class Foo {
var foo: Null = {
println("foo")
null
}

foo = {
println("foo2")
null
}
}
3 changes: 3 additions & 0 deletions tests/run/phantom-lazy-val-2.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
1
foo
2
31 changes: 31 additions & 0 deletions tests/run/phantom-lazy-val-2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

object Test {

def main(args: Array[String]): Unit = {
val f = new Foo
println(1)
f.foo
println(2)
f.foo

// TODO: Erase
// Currently not erasing fields for lazy vals
assert(f.getClass.getDeclaredFields.exists(_.getName.startsWith("foo")), "Field foo erased. Optimized accidentally?")
}


}

class Foo {
import Boo._

lazy val foo = {
println("foo")
any
}
}

object Boo extends Phantom {
type BooAny = this.Any
def any: BooAny = assume
}
2 changes: 2 additions & 0 deletions tests/run/phantom-val-2.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
foo
1
23 changes: 23 additions & 0 deletions tests/run/phantom-val-2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

object Test {
def main(args: Array[String]): Unit = {
val f = new Foo
println(1)
f.foo
f.foo
assert(!f.getClass.getDeclaredFields.exists(_.getName.startsWith("foo")), "field foo not erased")
}
}

class Foo {
import Boo._
val foo = {
println("foo")
any
}
}

object Boo extends Phantom {
type BooAny = this.Any
def any: BooAny = assume
}
30 changes: 0 additions & 30 deletions tests/run/phantom-var.scala

This file was deleted.

3 changes: 3 additions & 0 deletions tests/run/unit-lazy-val.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
1
foo
2
23 changes: 23 additions & 0 deletions tests/run/unit-lazy-val.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

object Test {

def main(args: Array[String]): Unit = {
val f = new Foo
println(1)
f.foo
println(2)
f.foo

// TODO: Erase
// Currently not erasing fields for lazy vals
assert(f.getClass.getDeclaredFields.exists(_.getName.startsWith("foo")), "Field foo erased. Optimized accidentally?")
}


}

class Foo {
lazy val foo: Unit = {
println("foo")
}
}
Loading