Skip to content

Commit 7fc880b

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 1c98bbb commit 7fc880b

28 files changed

+833
-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
@@ -631,7 +631,8 @@ class Definitions {
631631

632632
val StaticRootImportFns = List[() => TermRef](
633633
() => JavaLangPackageVal.termRef,
634-
() => ScalaPackageVal.termRef
634+
() => ScalaPackageVal.termRef,
635+
() => PhantomPackageVal.termRef
635636
)
636637

637638
val PredefImportFns = List[() => TermRef](
@@ -755,6 +756,8 @@ class Definitions {
755756
AnyValClass,
756757
NullClass,
757758
NothingClass,
759+
PhantomAnyClass,
760+
PhantomNothingClass,
758761
SingletonClass,
759762
EqualsPatternClass,
760763
EmptyPackageVal,
@@ -774,4 +777,22 @@ class Definitions {
774777
_isInitialized = true
775778
}
776779
}
780+
781+
lazy val PhantomPackageVal = ctx.requiredPackage("dotty.phantom")
782+
lazy val PhantomPackageClass = PhantomPackageVal.moduleClass.asClass
783+
784+
lazy val PhantomAnyClass: ClassSymbol =
785+
completeClass(newCompleteClassSymbol(PhantomPackageClass, tpnme.PhantomAny, Abstract, Nil))
786+
def PhantomAnyType = PhantomAnyClass.typeRef
787+
788+
lazy val PhantomNothingClass: ClassSymbol =
789+
newCompleteClassSymbol(PhantomPackageClass, tpnme.PhantomNothing, AbstractFinal, List(PhantomAnyType))
790+
def PhantomNothingType = PhantomNothingClass.typeRef
791+
792+
lazy val Phantasmic: TermSymbol = {
793+
def paramBoundsExp(gt: GenericType) = List(TypeBounds(PhantomNothingType, PhantomAnyType))
794+
def resultTypeExp(gt: GenericType) = gt.paramRefs.head
795+
val info = PolyType(List(tpnme.PhantasmicParam))(paramBoundsExp, resultTypeExp)
796+
newMethod(PhantomPackageClass, nme.Phantasmic, info, Flags.Method)
797+
}
777798
}

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,153 @@
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 ValDef(_, tpt, _) => assertNotPhantom(tpt.typeOpt)
39+
case DefDef(_, _, _, tpt, _) => assertNotPhantom(tpt.typeOpt)
40+
case _ => assertNotPhantom(tree.tpe)
41+
}
42+
super.checkPostCondition(tree)
43+
}
44+
45+
// Transform trees
46+
47+
override def transformStats(trees: List[tpd.Tree])(implicit ctx: Context, info: TransformerInfo): List[tpd.Tree] = {
48+
val newTrees = trees.filter {
49+
case ValDef(_, tpt, _) =>
50+
!tpt.tpe.derivesFrom(defn.PhantomAnyClass)
51+
52+
case tree @ DefDef(_, _, _, tpt, _) =>
53+
val isPhantom = tpt.tpe.derivesFrom(defn.PhantomAnyClass)
54+
if (isPhantom && tree.symbol.flags.is(Flags.Accessor)) {
55+
if (tree.symbol.flags.is(Flags.Mutable))
56+
ctx.error("Can not define var with phantom type.", tree.pos)
57+
else if (tree.symbol.flags.is(Flags.Lazy))
58+
ctx.error("Can not define lazy var with phantom type.", tree.pos)
59+
}
60+
!isPhantom
61+
62+
case tree @ Apply(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+
false
65+
case tree @ TypeApply(fun, _) if tree.tpe.derivesFrom(defn.PhantomAnyClass) =>
66+
ctx.error(s"Functions returning a phantom type can not be in statement position.", fun.pos)
67+
false
68+
case tree: Select if tree.tpe.derivesFrom(defn.PhantomAnyClass) =>
69+
ctx.error(s"Fields containing a phantom type can not be accessed in statement position.", tree.pos)
70+
false
71+
72+
case _ =>
73+
true
74+
}
75+
super.transformStats(newTrees)
76+
}
77+
78+
override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = {
79+
val paramTypes = {
80+
tree.fun.typeOpt match {
81+
case tpe: TermRef =>
82+
tpe.info match {
83+
case tpe2: MethodType => tpe2.paramTypes
84+
case _ => Nil
85+
}
86+
87+
case tpe: MethodType => tpe.paramTypes
88+
case _ => Nil
89+
}
90+
}
91+
92+
val newTree =
93+
if (paramTypes.nonEmpty || tree.args.isEmpty) tree
94+
else cpy.Apply(tree)(tree.fun, Nil)
95+
96+
super.transformApply(newTree)
97+
}
98+
99+
override def transformDefDef(ddef: DefDef)(implicit ctx: Context, info: TransformerInfo): Tree = {
100+
def filterPhantom(vparamss: List[List[ValDef]]): List[List[ValDef]] = {
101+
vparamss.filter { vparams =>
102+
val phantoms = vparams.filter(_.tpt.typeOpt.derivesFrom(defn.PhantomAnyClass))
103+
if (phantoms.nonEmpty && phantoms.size != vparams.size)
104+
ctx.error("Lists of parameters with runtime and phantom types are not allowed.", vparams.head.pos)
105+
phantoms.isEmpty
106+
}
107+
}
108+
val newVparamss = filterPhantom(ddef.vparamss)
109+
val ddef1 =
110+
if (ddef.vparamss.length == newVparamss.length) ddef
111+
else cpy.DefDef(ddef)(vparamss = newVparamss)
112+
super.transformDefDef(ddef1)
113+
}
114+
115+
// Transform symbols
116+
117+
def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = erasedPhantoms(tp)
118+
119+
private def erasedPhantoms(tp: Type)(implicit ctx: Context): Type = {
120+
val flags = tp.typeSymbol.flags
121+
tp match {
122+
case tp: ClassInfo if !flags.is(Flags.Package) =>
123+
val newDecls = tp.decls.filteredScope(sym => !isPhantomMethodType(sym.info))
124+
ClassInfo(tp.prefix, tp.cls, tp.classParents, newDecls, tp.selfInfo)
125+
126+
case tp: JavaMethodType => tp
127+
case tp: MethodType => erasedMethodPhantoms(tp)
128+
129+
case tp: PolyType =>
130+
PolyType(tp.paramNames)(_ => tp.paramBounds, _ => erasedPhantoms(tp.resType))
131+
132+
case _ => tp
133+
}
134+
}
135+
136+
private def erasedMethodPhantoms(tp: MethodType)(implicit ctx: Context): MethodType = {
137+
val (erasedParamNames, erasedParamTypes) =
138+
if (tp.paramTypes.isEmpty || tp.paramTypes.head.derivesFrom(defn.PhantomAnyClass)) (Nil, Nil)
139+
else (tp.paramNames, tp.paramTypes)
140+
val erasedResultType = erasedPhantoms(tp.resultType)
141+
tp match {
142+
case _: ImplicitMethodType =>
143+
ImplicitMethodType(erasedParamNames, erasedParamTypes, erasedResultType)
144+
case _ =>
145+
MethodType(erasedParamNames, erasedParamTypes, erasedResultType)
146+
}
147+
}
148+
149+
@tailrec private def isPhantomMethodType(tpe: Type)(implicit ctx: Context): Boolean = tpe match {
150+
case tpe: MethodicType => tpe.resultType.derivesFrom(defn.PhantomAnyClass) || isPhantomMethodType(tpe.resultType)
151+
case _ => false
152+
}
153+
}

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)