Skip to content

Commit 0ed4e3c

Browse files
committed
Add erasable phantom types.
Phantom type are types for which values have no runtime use. For this purpose the phase PhantomRefErasure will remove all term references that derive from PantomAny. PhantomDeclErasure will remove all classes/traits that extends PhantomAny and members that return a Phantom type. The lattice 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. Conversely if Nothing <: T <: Any then PhantomNothing <: T <: PhantomAny is false. PhantomRefErasure 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. PhantomDeclErasure: * Value or method definitions that return a value of a subtype of PhantomAny. * Classes and traits that extend PhantomAny. Additional restrictions: * In a curried function groups of parameters are either all Any or all PhantomAny. * Expressions of phantom types can not be used in statement position. * Expressions of phantom types can not be if branching expressions if/match/try. * Cannot use 'return' on phantom objects. * `var` and `lazy val` cannot be phantom types. * Instantiation of anonymous phantom classes is disallowed.
1 parent a9dd92e commit 0ed4e3c

34 files changed

+1031
-25
lines changed

src/dotty/tools/dotc/Compiler.scala

+2
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ 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 PhantomRefErasure), // Rewrite trees erasing all value references of phantom types.
76+
List(new PhantomDeclErasure), // Rewrite trees erasing all phantom types declarations.
7577
List(new Erasure), // Rewrite types to JVM model, erasing all type parameters, abstract types and refinements.
7678
List(new ElimErasedValueType, // Expand erased value types to their underlying implmementation types
7779
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

+16-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,16 @@ 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+
783798
}

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 myErasedRefPhantoms = 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 erasedRefPhantoms = myErasedRefPhantoms // Phase is after phantom reference 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+
myErasedRefPhantoms = prev.getClass == classOf[PhantomRefErasure] || prev.erasedRefPhantoms
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

+3
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

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 isPhantomType(tp: Type): Boolean = tp.dealias match {
537+
case tp: TypeRef => tp.symbol.isPhantomClass
538+
case tp: RefinedOrRecType => isPhantomType(tp.parent)
539+
case AndType(tp1, tp2) => isPhantomType(tp1) || isPhantomType(tp2)
540+
case OrType(tp1, tp2) => isPhantomType(tp1) || isPhantomType(tp2)
541+
case _ => false
542+
}
543+
if (tp1.symbol eq NothingClass) tp2.isInstanceOf[ValueType] && !isPhantomType(tp2)
544+
else if (tp1.symbol eq NullClass) isNullable(tp2) && !isPhantomType(tp2)
545+
else if (tp1.symbol eq PhantomNothingClass) tp2.isInstanceOf[ValueType] && isPhantomType(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/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[PhantomDeclErasure], 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,89 @@
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+
import dotty.tools.dotc.core.StdNames._
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 PhantomDeclErasure extends MiniPhaseTransform with InfoTransformer {
19+
thisTransformer =>
20+
21+
import dotty.tools.dotc.ast.tpd._
22+
23+
override def phaseName: String = "phantomDeclErasure"
24+
25+
/** List of names of phases that should precede this phase */
26+
override def runsAfter: Set[Class[_ <: Phase]] =
27+
Set(classOf[PhantomRefErasure])
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 declarations should be erased in " + tree)
34+
35+
tree match {
36+
case _: TypeTree =>
37+
case ValDef(_, tpt, _) => assertNotPhantom(tpt.typeOpt)
38+
case DefDef(_, _, _, tpt, _) => assertNotPhantom(tpt.typeOpt)
39+
case tree: Trees.TypeDef[_] =>
40+
tree.symbol match {
41+
case sym: ClassSymbol =>
42+
assertNotPhantom(sym.info)
43+
assert(!sym.classInfo.decls.exists(sym2 => isPhantomMethodType(sym2.info)),
44+
"All phantom type declarations should be erased in " + sym.classInfo)
45+
case _ =>
46+
}
47+
case _ =>
48+
}
49+
50+
super.checkPostCondition(tree)
51+
}
52+
53+
/* Tree transform */
54+
55+
override def transformStats(trees: List[tpd.Tree])(implicit ctx: Context, info: TransformerInfo): List[tpd.Tree] = {
56+
val newTrees = trees.filter {
57+
case tree: ValOrDefDef => !tree.tpt.tpe.derivesFrom(defn.PhantomAnyClass)
58+
case tree: TypeDef => !tree.tpe.derivesFrom(defn.PhantomAnyClass)
59+
case _ => true
60+
}
61+
super.transformStats(newTrees)
62+
}
63+
64+
/* Symbol transform */
65+
66+
def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = erasedPhantoms(tp)
67+
68+
private def erasedPhantoms(tp: Type)(implicit ctx: Context): Type = {
69+
val flags = tp.typeSymbol.flags
70+
tp match {
71+
case tp: ClassInfo if !flags.is(Flags.Package) && !tp.typeRef.derivesFrom(defn.PhantomAnyClass) =>
72+
val newDecls = tp.decls.filteredScope(sym => !isPhantomMethodType(sym.info) && !isPhantomClassType(sym.info))
73+
ClassInfo(tp.prefix, tp.cls, tp.classParents, newDecls, tp.selfInfo)
74+
75+
case _ => tp
76+
}
77+
}
78+
79+
@tailrec private def isPhantomMethodType(tpe: Type)(implicit ctx: Context): Boolean = tpe match {
80+
case tpe: MethodicType => tpe.resultType.derivesFrom(defn.PhantomAnyClass) || isPhantomMethodType(tpe.resultType)
81+
case _ => false
82+
}
83+
84+
private def isPhantomClassType(tp: Type)(implicit ctx: Context): Boolean = tp match {
85+
case tp: ClassInfo => tp.cls.derivesFrom(defn.PhantomAnyClass)
86+
case _ => false
87+
}
88+
89+
}

0 commit comments

Comments
 (0)