Skip to content

Commit 5beafb8

Browse files
committed
Add erasable phantom types.
Phantom type are types for which values have no runtime use. For this purose the phase PhantomErasure will remove all terms that derive from PantomAny. The latice bounded by PhantomAny and PhantomNothing is completely disjoint of the runtime types (Any/Nothing). Therefore if PhantomNothing <: P <: PhantomAny then Nothing <: P <: Any is false. Conversly if Nothing <: T <: Any then PhantomNothing <: T <: PhantomAny is false. PhantomErasure will remove: * Parameters that are subtype of PhantomAny. * Arguments that are subtype of PhantomAny. * Calls to functions that return a value of subtype of PhantomAny. * Value or method definitions that return a value of a subtype of PhantomAny. Additional restrictions: * In a curried function groups of parameters are either all Any or all PhantomAny. * Expressions of phantom types cant be used in statement position. * `var` and `lazy val` can not be phantom types. * Mixing phantom types with types of `Any` using type bounds, `&` or `|` is not allowed.
1 parent 88ff215 commit 5beafb8

28 files changed

+834
-25
lines changed

src/dotty/tools/dotc/Compiler.scala

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class Compiler {
7272
new AugmentScala2Traits, // Expand traits defined in Scala 2.11 to simulate old-style rewritings
7373
new ResolveSuper, // Implement super accessors and add forwarders to trait methods
7474
new ArrayConstructors), // Intercept creation of (non-generic) arrays and intrinsify.
75+
List(new PhantomErasure), // Rewrite trees erasing all values of phantom types.
7576
List(new Erasure), // Rewrite types to JVM model, erasing all type parameters, abstract types and refinements.
7677
List(new ElimErasedValueType, // Expand erased value types to their underlying implmementation types
7778
new VCElideAllocations, // Peep-hole optimization to eliminate unnecessary value class allocations

src/dotty/tools/dotc/ast/Desugar.scala

-10
Original file line numberDiff line numberDiff line change
@@ -591,16 +591,6 @@ object desugar {
591591
tree
592592
}
593593

594-
/** EmptyTree in lower bound ==> Nothing
595-
* EmptyTree in upper bounds ==> Any
596-
*/
597-
def typeBoundsTree(tree: TypeBoundsTree)(implicit ctx: Context): TypeBoundsTree = {
598-
val TypeBoundsTree(lo, hi) = tree
599-
val lo1 = if (lo.isEmpty) untpd.TypeTree(defn.NothingType) else lo
600-
val hi1 = if (hi.isEmpty) untpd.TypeTree(defn.AnyType) else hi
601-
cpy.TypeBoundsTree(tree)(lo1, hi1)
602-
}
603-
604594
/** Make closure corresponding to function.
605595
* params => body
606596
* ==>

src/dotty/tools/dotc/core/Definitions.scala

+22-1
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,8 @@ class Definitions {
637637

638638
val StaticRootImportFns = List[() => TermRef](
639639
() => JavaLangPackageVal.termRef,
640-
() => ScalaPackageVal.termRef
640+
() => ScalaPackageVal.termRef,
641+
() => PhantomPackageVal.termRef
641642
)
642643

643644
val PredefImportFns = List[() => TermRef](
@@ -761,6 +762,8 @@ class Definitions {
761762
AnyValClass,
762763
NullClass,
763764
NothingClass,
765+
PhantomAnyClass,
766+
PhantomNothingClass,
764767
SingletonClass,
765768
EqualsPatternClass,
766769
EmptyPackageVal,
@@ -780,4 +783,22 @@ class Definitions {
780783
_isInitialized = true
781784
}
782785
}
786+
787+
lazy val PhantomPackageVal = ctx.requiredPackage("dotty.phantom")
788+
lazy val PhantomPackageClass = PhantomPackageVal.moduleClass.asClass
789+
790+
lazy val PhantomAnyClass: ClassSymbol =
791+
completeClass(newCompleteClassSymbol(PhantomPackageClass, tpnme.PhantomAny, Abstract, Nil))
792+
def PhantomAnyType = PhantomAnyClass.typeRef
793+
794+
lazy val PhantomNothingClass: ClassSymbol =
795+
newCompleteClassSymbol(PhantomPackageClass, tpnme.PhantomNothing, AbstractFinal, List(PhantomAnyType))
796+
def PhantomNothingType = PhantomNothingClass.typeRef
797+
798+
lazy val Phantasmic: TermSymbol = {
799+
def paramBoundsExp(gt: GenericType) = List(TypeBounds(PhantomNothingType, PhantomAnyType))
800+
def resultTypeExp(gt: GenericType) = gt.paramRefs.head
801+
val info = PolyType(List(tpnme.PhantasmicParam))(paramBoundsExp, resultTypeExp)
802+
newMethod(PhantomPackageClass, nme.Phantasmic, info, Flags.Method)
803+
}
783804
}

src/dotty/tools/dotc/core/Phases.scala

+3
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ object Phases {
302302

303303
private var myPeriod: Period = Periods.InvalidPeriod
304304
private var myBase: ContextBase = null
305+
private var myErasedPhantoms = false
305306
private var myErasedTypes = false
306307
private var myFlatClasses = false
307308
private var myRefChecked = false
@@ -319,6 +320,7 @@ object Phases {
319320
def start = myPeriod.firstPhaseId
320321
def end = myPeriod.lastPhaseId
321322

323+
final def erasedPhantoms = myErasedPhantoms // Phase is after phantom erasure
322324
final def erasedTypes = myErasedTypes // Phase is after erasure
323325
final def flatClasses = myFlatClasses // Phase is after flatten
324326
final def refChecked = myRefChecked // Phase is after RefChecks
@@ -330,6 +332,7 @@ object Phases {
330332
assert(myPeriod == Periods.InvalidPeriod, s"phase $this has already been used once; cannot be reused")
331333
myBase = base
332334
myPeriod = Period(NoRunId, start, end)
335+
myErasedPhantoms = prev.getClass == classOf[PhantomErasure] || prev.erasedPhantoms
333336
myErasedTypes = prev.getClass == classOf[Erasure] || prev.erasedTypes
334337
myFlatClasses = prev.getClass == classOf[Flatten] || prev.flatClasses
335338
myRefChecked = prev.getClass == classOf[RefChecks] || prev.refChecked

src/dotty/tools/dotc/core/StdNames.scala

+6
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,9 @@ object StdNames {
224224
final val SourceFileATTR: N = "SourceFile"
225225
final val SyntheticATTR: N = "Synthetic"
226226

227+
final val PhantomAny: N = "PhantomAny"
228+
final val PhantomNothing: N = "PhantomNothing"
229+
227230
// ----- Term names -----------------------------------------
228231

229232
// Compiler-internal
@@ -302,6 +305,9 @@ object StdNames {
302305
val _21 : N = "_21"
303306
val _22 : N = "_22"
304307

308+
val Phantasmic: N = "phantasmic"
309+
val PhantasmicParam: N = "Phantasmic"
310+
305311
val ??? = encode("???")
306312

307313
val genericWrapArray: N = "genericWrapArray"

src/dotty/tools/dotc/core/SymDenotations.scala

+10-1
Original file line numberDiff line numberDiff line change
@@ -613,9 +613,18 @@ object SymDenotations {
613613
// after Erasure and to avoid cyclic references caused by forcing denotations
614614
}
615615

616+
/** Is this symbol a class that extends `PhantomAny`? */
617+
final def isPhantomClass(implicit ctx: Context): Boolean = {
618+
val di = this.initial.asSymDenotation
619+
di.isClass &&
620+
di.derivesFrom(defn.PhantomAnyClass)(ctx.withPhase(di.validFor.firstPhaseId))
621+
// We call derivesFrom at the initial phase both because PhantomAny does not exist
622+
// after PhantomErasure and to avoid cyclic references caused by forcing denotations
623+
}
624+
616625
/** Is this symbol a class references to which that are supertypes of null? */
617626
final def isNullableClass(implicit ctx: Context): Boolean =
618-
isClass && !isValueClass && !(this is ModuleClass) && symbol != defn.NothingClass
627+
isClass && !isValueClass && !isPhantomClass && !(this is ModuleClass) && symbol != defn.NothingClass
619628

620629
/** Is this definition accessible as a member of tree with type `pre`?
621630
* @param pre The type of the tree from which the selection is made

src/dotty/tools/dotc/core/TypeComparer.scala

+31-2
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,12 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
4949
private var myNothingClass: ClassSymbol = null
5050
private var myNullClass: ClassSymbol = null
5151
private var myObjectClass: ClassSymbol = null
52+
private var myPhantomAnyClass: ClassSymbol = null
53+
private var myPhantomNothingClass: ClassSymbol = null
5254
private var myAnyType: TypeRef = null
5355
private var myNothingType: TypeRef = null
56+
private var myPhantomAnyType: TypeRef = null
57+
private var myPhantomNothingType: TypeRef = null
5458

5559
def AnyClass = {
5660
if (myAnyClass == null) myAnyClass = defn.AnyClass
@@ -68,6 +72,14 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
6872
if (myObjectClass == null) myObjectClass = defn.ObjectClass
6973
myObjectClass
7074
}
75+
def PhantomAnyClass = {
76+
if (myPhantomAnyClass == null) myPhantomAnyClass = defn.PhantomAnyClass
77+
myPhantomAnyClass
78+
}
79+
def PhantomNothingClass = {
80+
if (myPhantomNothingClass == null) myPhantomNothingClass = defn.PhantomNothingClass
81+
myPhantomNothingClass
82+
}
7183
def AnyType = {
7284
if (myAnyType == null) myAnyType = AnyClass.typeRef
7385
myAnyType
@@ -76,6 +88,14 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
7688
if (myNothingType == null) myNothingType = NothingClass.typeRef
7789
myNothingType
7890
}
91+
def PhantomAnyType = {
92+
if (myPhantomAnyType == null) myPhantomAnyType = PhantomAnyClass.typeRef
93+
myPhantomAnyType
94+
}
95+
def PhantomNothingType = {
96+
if (myPhantomNothingType == null) myPhantomNothingType = PhantomNothingClass.typeRef
97+
myPhantomNothingType
98+
}
7999

80100
// Subtype testing `<:<`
81101

@@ -513,8 +533,17 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
513533
case OrType(tp1, tp2) => isNullable(tp1) || isNullable(tp2)
514534
case _ => false
515535
}
516-
(tp1.symbol eq NothingClass) && tp2.isInstanceOf[ValueType] ||
517-
(tp1.symbol eq NullClass) && isNullable(tp2)
536+
def isPhantom(tp: Type): Boolean = tp.dealias match {
537+
case tp: TypeRef => tp.symbol.isPhantomClass
538+
case tp: RefinedOrRecType => isPhantom(tp.parent)
539+
case AndType(tp1, tp2) => isPhantom(tp1) && isPhantom(tp2)
540+
case OrType(tp1, tp2) => isPhantom(tp1) || isPhantom(tp2)
541+
case _ => false
542+
}
543+
if (tp1.symbol eq NothingClass) tp2.isInstanceOf[ValueType] && !isPhantom(tp2)
544+
else if (tp1.symbol eq NullClass) isNullable(tp2) && !isPhantom(tp2)
545+
else if (tp1.symbol eq PhantomNothingClass) tp2.isInstanceOf[ValueType] && isPhantom(tp2)
546+
else false
518547
}
519548
case tp1: SingletonType =>
520549
/** if `tp2 == p.type` and `p: q.type` then try `tp1 <:< q.type` as a last effort.*/

src/dotty/tools/dotc/core/Types.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ object Types {
160160
case _ =>
161161
false
162162
}
163-
cls == defn.AnyClass || loop(this)
163+
loop(this)
164164
}
165165

166166
/** Is this type guaranteed not to have `null` as a value?

src/dotty/tools/dotc/core/tasty/TreePickler.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ class TreePickler(pickler: TastyPickler) {
258258
case tpe: MethodType if richTypes =>
259259
writeByte(METHODtype)
260260
pickleMethodic(tpe.resultType, tpe.paramNames, tpe.paramTypes)
261-
case tpe: PolyType if richTypes =>
261+
case tpe: PolyType if richTypes || tpe.paramNames.contains("Phantasmic".toTypeName) => // TODO: is this the solution for phantasmic[_]?
262262
writeByte(POLYtype)
263263
pickleMethodic(tpe.resultType, tpe.paramNames, tpe.paramBounds)
264264
case tpe: PolyParam =>

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class Erasure extends Phase with DenotTransformer { thisTransformer =>
3232
override def phaseName: String = "erasure"
3333

3434
/** List of names of phases that should precede this phase */
35-
override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[InterceptedMethods], classOf[Splitter], classOf[ElimRepeated])
35+
override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[PhantomErasure], classOf[InterceptedMethods], classOf[Splitter], classOf[ElimRepeated])
3636

3737
def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref match {
3838
case ref: SymDenotation =>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import core.Phases._
5+
import core.DenotTransformers._
6+
import core.Symbols._
7+
import core.Contexts._
8+
import core.Types._
9+
import core.Decorators._
10+
import dotty.tools.dotc.ast.{Trees, tpd}
11+
import ast.Trees._
12+
13+
import dotty.tools.dotc.core.Flags
14+
import dotty.tools.dotc.transform.TreeTransforms.{MiniPhaseTransform, TransformerInfo, TreeTransform}
15+
16+
import scala.annotation.tailrec
17+
18+
class PhantomErasure extends MiniPhaseTransform with InfoTransformer {
19+
thisTransformer =>
20+
21+
import dotty.tools.dotc.ast.tpd._
22+
23+
override def phaseName: String = "phantomErasure"
24+
25+
/** List of names of phases that should precede this phase */
26+
override def runsAfter: Set[Class[_ <: Phase]] =
27+
Set(classOf[InterceptedMethods], classOf[Splitter], classOf[ElimRepeated])
28+
29+
/** Check what the phase achieves, to be called at any point after it is finished.
30+
*/
31+
override def checkPostCondition(tree: tpd.Tree)(implicit ctx: Context): Unit = {
32+
def assertNotPhantom(tpe: Type): Unit =
33+
assert(!tpe.derivesFrom(defn.PhantomAnyClass), "All phantom type values should be erased in " + tree)
34+
35+
tree match {
36+
case _: TypeTree =>
37+
case _: Trees.TypeDef[_] =>
38+
case tree: TypeDef => tree.symbol.asClass.classInfo.decls // TODO check decls
39+
case ValDef(_, tpt, _) => assertNotPhantom(tpt.typeOpt)
40+
case DefDef(_, _, _, tpt, _) => assertNotPhantom(tpt.typeOpt)
41+
case _ => assertNotPhantom(tree.tpe)
42+
}
43+
super.checkPostCondition(tree)
44+
}
45+
46+
// Transform trees
47+
48+
override def transformStats(trees: List[tpd.Tree])(implicit ctx: Context, info: TransformerInfo): List[tpd.Tree] = {
49+
val newTrees = trees.filter {
50+
case ValDef(_, tpt, _) =>
51+
!tpt.tpe.derivesFrom(defn.PhantomAnyClass)
52+
53+
case tree @ DefDef(_, _, _, tpt, _) =>
54+
val isPhantom = tpt.tpe.derivesFrom(defn.PhantomAnyClass)
55+
if (isPhantom && tree.symbol.flags.is(Flags.Accessor)) {
56+
if (tree.symbol.flags.is(Flags.Mutable))
57+
ctx.error("Can not define var with phantom type.", tree.pos)
58+
else if (tree.symbol.flags.is(Flags.Lazy))
59+
ctx.error("Can not define lazy var with phantom type.", tree.pos)
60+
}
61+
!isPhantom
62+
63+
case tree @ Apply(fun, _) if tree.tpe.derivesFrom(defn.PhantomAnyClass) =>
64+
ctx.error(s"Functions returning a phantom type can not be in statement position.", fun.pos)
65+
false
66+
case tree @ TypeApply(fun, _) if tree.tpe.derivesFrom(defn.PhantomAnyClass) =>
67+
ctx.error(s"Functions returning a phantom type can not be in statement position.", fun.pos)
68+
false
69+
case tree: Select if tree.tpe.derivesFrom(defn.PhantomAnyClass) =>
70+
ctx.error(s"Fields containing a phantom type can not be accessed in statement position.", tree.pos)
71+
false
72+
73+
case _ =>
74+
true
75+
}
76+
super.transformStats(newTrees)
77+
}
78+
79+
override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = {
80+
val paramTypes = {
81+
tree.fun.typeOpt match {
82+
case tpe: TermRef =>
83+
tpe.info match {
84+
case tpe2: MethodType => tpe2.paramTypes
85+
case _ => Nil
86+
}
87+
88+
case tpe: MethodType => tpe.paramTypes
89+
case _ => Nil
90+
}
91+
}
92+
93+
val newTree =
94+
if (paramTypes.nonEmpty || tree.args.isEmpty) tree
95+
else cpy.Apply(tree)(tree.fun, Nil)
96+
97+
super.transformApply(newTree)
98+
}
99+
100+
override def transformDefDef(ddef: DefDef)(implicit ctx: Context, info: TransformerInfo): Tree = {
101+
def filterPhantom(vparamss: List[List[ValDef]]): List[List[ValDef]] = {
102+
vparamss.filter { vparams =>
103+
val phantoms = vparams.filter(_.tpt.typeOpt.derivesFrom(defn.PhantomAnyClass))
104+
if (phantoms.nonEmpty && phantoms.size != vparams.size)
105+
ctx.error("Lists of parameters with runtime and phantom types are not allowed.", vparams.head.pos)
106+
phantoms.isEmpty
107+
}
108+
}
109+
val newVparamss = filterPhantom(ddef.vparamss)
110+
val ddef1 =
111+
if (ddef.vparamss.length == newVparamss.length) ddef
112+
else cpy.DefDef(ddef)(vparamss = newVparamss)
113+
super.transformDefDef(ddef1)
114+
}
115+
116+
// Transform symbols
117+
118+
def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = erasedPhantoms(tp)
119+
120+
private def erasedPhantoms(tp: Type)(implicit ctx: Context): Type = {
121+
val flags = tp.typeSymbol.flags
122+
tp match {
123+
case tp: ClassInfo if !flags.is(Flags.Package) =>
124+
val newDecls = tp.decls.filteredScope(sym => !isPhantomMethodType(sym.info))
125+
ClassInfo(tp.prefix, tp.cls, tp.classParents, newDecls, tp.selfInfo)
126+
127+
case tp: JavaMethodType => tp
128+
case tp: MethodType => erasedMethodPhantoms(tp)
129+
130+
case tp: PolyType =>
131+
PolyType(tp.paramNames)(_ => tp.paramBounds, _ => erasedPhantoms(tp.resType))
132+
133+
case _ => tp
134+
}
135+
}
136+
137+
private def erasedMethodPhantoms(tp: MethodType)(implicit ctx: Context): MethodType = {
138+
val (erasedParamNames, erasedParamTypes) =
139+
if (tp.paramTypes.isEmpty || tp.paramTypes.head.derivesFrom(defn.PhantomAnyClass)) (Nil, Nil)
140+
else (tp.paramNames, tp.paramTypes)
141+
val erasedResultType = erasedPhantoms(tp.resultType)
142+
tp match {
143+
case _: ImplicitMethodType =>
144+
ImplicitMethodType(erasedParamNames, erasedParamTypes, erasedResultType)
145+
case _ =>
146+
MethodType(erasedParamNames, erasedParamTypes, erasedResultType)
147+
}
148+
}
149+
150+
@tailrec private def isPhantomMethodType(tpe: Type)(implicit ctx: Context): Boolean = tpe match {
151+
case tpe: MethodicType => tpe.resultType.derivesFrom(defn.PhantomAnyClass) || isPhantomMethodType(tpe.resultType)
152+
case _ => false
153+
}
154+
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ class TreeChecker extends Phase with SymTransformer {
304304
mapOver(tp)
305305
definedBinders -= tp
306306
case tp: ParamType =>
307-
assert(definedBinders.contains(tp.binder), s"orphan param: $tp")
307+
assert(definedBinders.contains(tp.binder) || (tp.derivesFrom(defn.PhantomAnyClass) && ctx.phase.erasedPhantoms), s"orphan param: $tp")
308308
case tp: TypeVar =>
309309
apply(tp.underlying)
310310
case _ =>

0 commit comments

Comments
 (0)