Skip to content

Commit d0793e7

Browse files
committed
Add restrictions on phantom classes.
1 parent 59bbd4b commit d0793e7

File tree

6 files changed

+132
-28
lines changed

6 files changed

+132
-28
lines changed

src/dotty/tools/dotc/transform/PhantomDeclErasure.scala

+25-7
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import core.Types._
99
import core.Decorators._
1010
import dotty.tools.dotc.ast.{Trees, tpd}
1111
import ast.Trees._
12-
12+
import dotty.tools.dotc.core.StdNames._
1313
import dotty.tools.dotc.core.Flags
1414
import dotty.tools.dotc.transform.TreeTransforms.{MiniPhaseTransform, TransformerInfo, TreeTransform}
1515

@@ -57,13 +57,31 @@ class PhantomDeclErasure extends MiniPhaseTransform with InfoTransformer {
5757
case ValDef(_, tpt, _) =>
5858
!tpt.tpe.derivesFrom(defn.PhantomAnyClass)
5959

60-
case tree @ DefDef(_, _, _, tpt, _) =>
60+
case tree @ DefDef(name, _, _, tpt, _) =>
61+
val flags = tree.symbol.flags
6162
val isPhantom = tpt.tpe.derivesFrom(defn.PhantomAnyClass)
62-
if (isPhantom && tree.symbol.flags.is(Flags.Accessor)) {
63-
if (tree.symbol.flags.is(Flags.Mutable))
64-
ctx.error("Can not define var with phantom type.", tree.pos)
65-
else if (tree.symbol.flags.is(Flags.Lazy))
66-
ctx.error("Can not define lazy var with phantom type.", tree.pos)
63+
if (isPhantom) {
64+
if (flags.is(Flags.Mutable))
65+
ctx.error("Can not define 'var' with phantom type.", tree.pos)
66+
else if (flags.is(Flags.Lazy))
67+
ctx.error("Can not define 'lazy val' with phantom type.", tree.pos)
68+
} else if (name != nme.CONSTRUCTOR) {
69+
tree.symbol.owner match {
70+
case cls: ClassSymbol if cls.classSymbol.derivesFrom(defn.PhantomAnyClass) =>
71+
val defKey =
72+
if (!flags.is(Flags.Accessor)) "def"
73+
else if (flags.is(Flags.Mutable)) "var"
74+
else if (flags.is(Flags.Lazy)) "lazy val"
75+
else "val"
76+
val flags2 = tree.symbol.owner.flags
77+
val classKey =
78+
if (flags2.is(Flags.Trait)) "trait"
79+
else if (flags2.is(Flags.Abstract)) "abstract class"
80+
else "class"
81+
ctx.error(s"Can not define '$defKey' with non phantom type in a phantom '$classKey'.", tree.pos)
82+
83+
case _ =>
84+
}
6785
}
6886
!isPhantom
6987

src/dotty/tools/dotc/transform/PhantomRefErasure.scala

+40-16
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,15 @@ import core.Types._
99
import core.Decorators._
1010
import dotty.tools.dotc.ast.{Trees, tpd}
1111
import ast.Trees._
12-
12+
import dotty.tools.dotc.core.StdNames._
1313
import dotty.tools.dotc.core.Flags
1414
import dotty.tools.dotc.transform.TreeTransforms.{MiniPhaseTransform, TransformerInfo, TreeTransform}
15+
import dotty.tools.dotc.util.Positions.Position
1516

1617
import scala.annotation.tailrec
1718

1819
class PhantomRefErasure extends MiniPhaseTransform with InfoTransformer {
19-
thisTransformer =>
20-
21-
import dotty.tools.dotc.ast.tpd._
20+
import tpd._
2221

2322
override def phaseName: String = "phantomRefErasure"
2423

@@ -28,7 +27,7 @@ class PhantomRefErasure extends MiniPhaseTransform with InfoTransformer {
2827

2928
/** Check what the phase achieves, to be called at any point after it is finished.
3029
*/
31-
override def checkPostCondition(tree: tpd.Tree)(implicit ctx: Context): Unit = {
30+
override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = {
3231
def assertNotPhantom(tpe: Type): Unit =
3332
assert(!tpe.derivesFrom(defn.PhantomAnyClass), "All phantom type values should be erased in " + tree)
3433

@@ -42,17 +41,32 @@ class PhantomRefErasure extends MiniPhaseTransform with InfoTransformer {
4241

4342
// Transform trees
4443

45-
override def transformStats(trees: List[tpd.Tree])(implicit ctx: Context, info: TransformerInfo): List[tpd.Tree] = {
46-
trees.foreach {
47-
case tree @ Apply(fun, _) if tree.tpe.derivesFrom(defn.PhantomAnyClass) =>
48-
ctx.error(s"Functions returning a phantom type can not be in statement position.", fun.pos)
49-
case tree @ TypeApply(fun, _) if tree.tpe.derivesFrom(defn.PhantomAnyClass) =>
50-
ctx.error(s"Functions returning a phantom type can not be in statement position.", fun.pos)
51-
case tree: Select if tree.tpe.derivesFrom(defn.PhantomAnyClass) =>
52-
ctx.error(s"Fields containing a phantom type can not be accessed in statement position.", tree.pos)
44+
override def transformStats(trees: List[Tree])(implicit ctx: Context, info: TransformerInfo): List[Tree] = {
45+
ctx.owner match {
46+
case cls: ClassSymbol if cls.classDenot.classSymbol.derivesFrom(defn.PhantomAnyClass) =>
47+
trees.foreach {
48+
case tree: TypeDef =>
49+
case tree: DefDef =>
50+
case tree =>
51+
ctx.error(s"Phantom classes can not have expressions in statement position.", tree.pos)
52+
}
5353
case _ =>
54+
trees.foreach {
55+
case tree@Apply(fun, _) =>
56+
fun match {
57+
case Select(_: This, name) if name == nme.CONSTRUCTOR =>
58+
case _ if tree.tpe.derivesFrom(defn.PhantomAnyClass) =>
59+
ctx.error(s"Functions returning a phantom type can not be in statement position.", fun.pos)
60+
case _ =>
61+
}
62+
case tree@TypeApply(fun, _) if tree.tpe.derivesFrom(defn.PhantomAnyClass) =>
63+
ctx.error(s"Functions returning a phantom type can not be in statement position.", fun.pos)
64+
case tree: Select if tree.tpe.derivesFrom(defn.PhantomAnyClass) =>
65+
ctx.error(s"Fields containing a phantom type can not be accessed in statement position.", tree.pos)
66+
case _ =>
67+
}
5468
}
55-
super.transformStats(trees)
69+
trees
5670
}
5771

5872
override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = {
@@ -80,8 +94,18 @@ class PhantomRefErasure extends MiniPhaseTransform with InfoTransformer {
8094
def filterPhantom(vparamss: List[List[ValDef]]): List[List[ValDef]] = {
8195
vparamss.filter { vparams =>
8296
val phantoms = vparams.filter(_.tpt.typeOpt.derivesFrom(defn.PhantomAnyClass))
83-
if (phantoms.nonEmpty && phantoms.size != vparams.size)
84-
ctx.error("Lists of parameters with runtime and phantom types are not allowed.", vparams.head.pos)
97+
if (phantoms.size != vparams.size) {
98+
if (phantoms.nonEmpty)
99+
ctx.error("Lists of parameters with runtime and phantom types are not allowed.", vparams.head.pos)
100+
else if (ddef.name == nme.CONSTRUCTOR) {
101+
ctx.owner match {
102+
case cls: ClassSymbol if cls.classDenot.classSymbol.derivesFrom(defn.PhantomAnyClass) =>
103+
ctx.error("Phantom class constructors can not have non phantom parameters.", vparams.head.pos)
104+
case _ =>
105+
}
106+
}
107+
}
108+
85109
phantoms.isEmpty
86110
}
87111
}

tests/neg/phantomClass2.scala

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import dotty.phantom.PhantomAny
2+
3+
class Blinky extends PhantomAny {
4+
5+
def this(p: PhantomAny) = {
6+
this()
7+
}
8+
9+
def this(n: Int) = { // error
10+
this()
11+
}
12+
13+
new Blinky // error
14+
println() // error
15+
System.currentTimeMillis() // error
16+
}

tests/neg/phantomClass3.scala

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import dotty.phantom.PhantomAny
2+
3+
class Blinky extends PhantomAny {
4+
5+
def boo1() = new Blinky
6+
val boo2 = new Blinky
7+
8+
var boo3 = new Blinky // error
9+
lazy val boo4 = new Blinky // error
10+
11+
def foo1() = 42 // error
12+
val foo2 = 42 // error
13+
var foo3 = 42 // error
14+
lazy val foo4 = 42 // error
15+
16+
}
17+
18+
abstract class Inky extends PhantomAny {
19+
20+
def boo1() = new Blinky
21+
val boo2 = new Blinky
22+
23+
var boo3 = new Blinky // error
24+
lazy val boo4 = new Blinky // error
25+
26+
def foo1() = 42 // error
27+
val foo2 = 42 // error
28+
var foo3 = 42 // error
29+
lazy val foo4 = 42 // error
30+
31+
}
32+
33+
trait Pinky extends PhantomAny {
34+
35+
def boo1() = new Blinky
36+
val boo2 = new Blinky
37+
38+
var boo3 = new Blinky // error
39+
lazy val boo4 = new Blinky // error
40+
41+
def foo1() = 42 // error
42+
val foo2 = 42 // error
43+
var foo3 = 42 // error
44+
lazy val foo4 = 42 // error
45+
46+
}

tests/pos/phantomEq.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
/* This is a example of how to implement Eq using erasable phantom types.
33
*
44
* Run this test with
5-
* `run tests/pos/phantomEq.scala -Xprint-diff-del -Xprint:arrayConstructors,phantomErasure,erasure`
6-
* to see the the diffs after PhantomErasure and Erasure.
5+
* `run tests/pos/phantomEq.scala -Xprint-diff-del -Xprint:arrayConstructors,phantomRefErasure,phantomDeclErasure,erasure`
6+
* to see the the diffs after PhantomRefErasure, PhantomDeclErasure and Erasure.
77
*
88
*/
99
object PhantomEq {
@@ -20,7 +20,7 @@ object PhantomEq {
2020
(1: Number) === 1.toByte
2121
}
2222

23-
object PhantomEqUtil extends PhantomEqUtil {
23+
object PhantomEqUtil {
2424
class PhantomEq[-L, -R] extends dotty.phantom.PhantomAny
2525
type PhantomEqEq[T] = PhantomEq[T, T]
2626

tests/run/phantom.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* Run this test with
2-
* `run tests/run/phantom.scala -Xprint-diff-del -Xprint:arrayConstructors,phantomErasure,erasure`
3-
* to see the the diffs after PhantomErasure and Erasure.
2+
* `run tests/run/phantom.scala -Xprint-diff-del -Xprint:arrayConstructors,phantomRefErasure,phantomDeclErasure,erasure`
3+
* to see the the diffs after PhantomRefErasure, PhantomDeclErasure and Erasure.
44
*
55
*/
66
object Test {

0 commit comments

Comments
 (0)