Skip to content

Commit cc6322f

Browse files
committed
Avoid erasing @volitile fields.
1 parent ca8477b commit cc6322f

File tree

5 files changed

+59
-9
lines changed

5 files changed

+59
-9
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,12 @@ 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.symbol.hasAnnotation(defn.VolatileAnnot) && vdef.tpt.tpe.isPhantom)
166+
ctx.error("Phantom fields can not be @volitile", vdef.pos)
167+
vdef
168+
}
169+
164170
override def transformStats(trees: List[Tree])(implicit ctx: Context, info: TransformerInfo): List[Tree] =
165171
ast.Trees.flatten(reorderAndComplete(trees)(ctx.withPhase(thisTransformer.next)))
166172

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

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -98,27 +98,37 @@ 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)))
106121
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)))
122+
val getterRhs =
123+
if (isErasableBottomField(rhsClass)) erasedBottomTree(rhsClass)
111124
else transformFollowingDeep(ref(field))(ctx.withOwner(sym), info)
112-
}
113125
val getterDef = cpy.DefDef(tree)(rhs = getterRhs)
114126
Thicket(fieldDef, getterDef)
115127
} else if (sym.isSetter) {
116128
if (!sym.is(ParamAccessor)) { val Literal(Constant(())) = tree.rhs } // this is intended as an assertion
117129
field.setFlag(Mutable) // necessary for vals mixed in from Scala2 traits
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 {
130+
if (isErasableBottomField(tree.vparamss.head.head.tpt.tpe.classSymbol)) tree
131+
else {
122132
val initializer = Assign(ref(field), adaptToField(ref(tree.vparamss.head.head.symbol)))
123133
cpy.DefDef(tree)(rhs = transformFollowingDeep(initializer)(ctx.withOwner(sym), info))
124134
}

tests/neg/phantom-volitile.scala

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

tests/run/unit-volatile-var.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+
f.foo
6+
f.foo
7+
f.foo = {
8+
println("foo3")
9+
}
10+
f.foo
11+
assert(f.getClass.getDeclaredFields.exists(_.getName.startsWith("foo")), "@volatile field foo erased")
12+
}
13+
}
14+
15+
class Foo {
16+
@volatile var foo: Unit = {
17+
println("foo")
18+
}
19+
20+
foo = {
21+
println("foo2")
22+
}
23+
}

tests/run/unit-volitile-var.check

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

0 commit comments

Comments
 (0)