Skip to content

Commit a13638a

Browse files
committed
Implement erasable phantom types.
1 parent 0e2e1a3 commit a13638a

File tree

85 files changed

+1493
-29
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

85 files changed

+1493
-29
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import typer.{FrontEnd, Typer, ImportInfo, RefChecks}
1111
import reporting.{Reporter, ConsoleReporter}
1212
import Phases.Phase
1313
import transform._
14+
import transform.phantom._
1415
import util.FreshNameCreator
1516
import transform.TreeTransforms.{TreeTransform, TreeTransformer}
1617
import core.DenotTransformers.DenotTransformer
@@ -74,6 +75,7 @@ class Compiler {
7475
new ResolveSuper, // Implement super accessors and add forwarders to trait methods
7576
new PrimitiveForwarders, // Add forwarders to trait methods that have a mismatch between generic and primitives
7677
new ArrayConstructors), // Intercept creation of (non-generic) arrays and intrinsify.
78+
List(new PhantomTypeErasure), // Erases phantom types to ErasedPhantom
7779
List(new Erasure), // Rewrite types to JVM model, erasing all type parameters, abstract types and refinements.
7880
List(new ElimErasedValueType, // Expand erased value types to their underlying implmementation types
7981
new VCElideAllocations, // Peep-hole optimization to eliminate unnecessary value class allocations

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

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -626,16 +626,6 @@ object desugar {
626626
tree
627627
}
628628

629-
/** EmptyTree in lower bound ==> Nothing
630-
* EmptyTree in upper bounds ==> Any
631-
*/
632-
def typeBoundsTree(tree: TypeBoundsTree)(implicit ctx: Context): TypeBoundsTree = {
633-
val TypeBoundsTree(lo, hi) = tree
634-
val lo1 = if (lo.isEmpty) untpd.TypeTree(defn.NothingType) else lo
635-
val hi1 = if (hi.isEmpty) untpd.TypeTree(defn.AnyType) else hi
636-
cpy.TypeBoundsTree(tree)(lo1, hi1)
637-
}
638-
639629
/** Make closure corresponding to function.
640630
* params => body
641631
* ==>

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

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -149,17 +149,18 @@ class Definitions {
149149
}
150150

151151
private def enterPolyMethod(cls: ClassSymbol, name: TermName, typeParamCount: Int,
152-
resultTypeFn: PolyType => Type, flags: FlagSet = EmptyFlags) = {
152+
resultTypeFn: PolyType => Type, flags: FlagSet = EmptyFlags,
153+
bounds: TypeName => TypeBounds = _ => TypeBounds.empty) = {
153154
val tparamNames = tpnme.syntheticTypeParamNames(typeParamCount)
154-
val tparamBounds = tparamNames map (_ => TypeBounds.empty)
155+
val tparamBounds = tparamNames map bounds
155156
val ptype = PolyType(tparamNames, tparamNames.map(alwaysZero))(_ => tparamBounds, resultTypeFn)
156157
enterMethod(cls, name, ptype, flags)
157158
}
158159

159-
private def enterT1ParameterlessMethod(cls: ClassSymbol, name: TermName, resultTypeFn: PolyType => Type, flags: FlagSet) =
160+
private def enterT1ParameterlessMethod(cls: ClassSymbol, name: TermName, resultTypeFn: PolyType => Type, flags: FlagSet, bounds: TypeName => TypeBounds = _ => TypeBounds.empty) =
160161
enterPolyMethod(cls, name, 1, resultTypeFn, flags)
161162

162-
private def enterT1EmptyParamsMethod(cls: ClassSymbol, name: TermName, resultTypeFn: PolyType => Type, flags: FlagSet) =
163+
private def enterT1EmptyParamsMethod(cls: ClassSymbol, name: TermName, resultTypeFn: PolyType => Type, flags: FlagSet, bounds: TypeName => TypeBounds = _ => TypeBounds.empty) =
163164
enterPolyMethod(cls, name, 1, pt => MethodType(Nil, resultTypeFn(pt)), flags)
164165

165166
private def mkArityArray(name: String, arity: Int, countFrom: Int): Array[TypeRef] = {
@@ -938,16 +939,17 @@ class Definitions {
938939
val newDecls = new MutableScope(oldDecls) {
939940
override def lookupEntry(name: Name)(implicit ctx: Context): ScopeEntry = {
940941
val res = super.lookupEntry(name)
941-
if (res == null && name.isTypeName && name.isSyntheticFunction)
942+
if (res ne null) res
943+
else if (name.isTypeName && name.isSyntheticFunction)
942944
newScopeEntry(newFunctionNTrait(name.asTypeName))
943-
else res
945+
else null
944946
}
945947
}
946948
ScalaPackageClass.info = oldInfo.derivedClassInfo(decls = newDecls)
947949
}
948950

949951
/** Lists core classes that don't have underlying bytecode, but are synthesized on-the-fly in every reflection universe */
950-
lazy val syntheticScalaClasses = List(
952+
private lazy val syntheticScalaClasses = List(
951953
AnyClass,
952954
AnyRefAlias,
953955
RepeatedParamClass,
@@ -956,14 +958,15 @@ class Definitions {
956958
NullClass,
957959
NothingClass,
958960
SingletonClass,
959-
EqualsPatternClass)
961+
EqualsPatternClass,
962+
PhantomClass)
960963

961964
lazy val syntheticCoreClasses = syntheticScalaClasses ++ List(
962965
EmptyPackageVal,
963966
OpsPackageClass)
964967

965968
/** Lists core methods that don't have underlying bytecode, but are synthesized on-the-fly in every reflection universe */
966-
lazy val syntheticCoreMethods = AnyMethods ++ ObjectMethods ++ List(String_+, throwMethod)
969+
private lazy val syntheticCoreMethods = AnyMethods ++ ObjectMethods ++ List(String_+, throwMethod)
967970

968971
lazy val reservedScalaClassNames: Set[Name] = syntheticScalaClasses.map(_.name).toSet
969972

@@ -985,4 +988,44 @@ class Definitions {
985988
_isInitialized = true
986989
}
987990
}
991+
992+
// ----- Phantoms ---------------------------------------------------------
993+
994+
lazy val PhantomClass: ClassSymbol = {
995+
val cls = completeClass(enterCompleteClassSymbol(ScalaPackageClass, tpnme.Phantom, Abstract, List(AnyType)))
996+
997+
val any = enterCompleteClassSymbol(cls, tpnme.Any, Protected | Final | NoInitsTrait, Nil)
998+
val nothing = enterCompleteClassSymbol(cls, tpnme.Nothing, Protected | Final | NoInitsTrait, List(any.typeRef))
999+
1000+
val tparamNames = tpnme.syntheticTypeParamNames(1)
1001+
val ptype = PolyType(tparamNames, List(0))(_ => TypeBounds(nothing.typeRef, any.typeRef) :: Nil, PolyParam(_, 0))
1002+
newSymbol(cls, nme.assume_, Protected | Final | Method, ptype).entered
1003+
1004+
cls
1005+
}
1006+
1007+
def isPhantomAnyClass(sym: Symbol)(implicit ctx: Context): Boolean =
1008+
sym.exists && (sym.owner eq PhantomClass) && sym.name == tpnme.Any
1009+
1010+
def isPhantomNothingClass(sym: Symbol)(implicit ctx: Context): Boolean =
1011+
sym.exists && (sym.owner eq PhantomClass) && sym.name == tpnme.Nothing
1012+
1013+
def isPhantomAssume(sym: Symbol)(implicit ctx: Context): Boolean =
1014+
sym.exists && (sym.owner eq PhantomClass) && sym.name == nme.assume_
1015+
1016+
def topOf(tp: Type)(implicit ctx: Context): Type = tp.phantomTopClass match {
1017+
case top: ClassInfo => top.prefix.select(tpnme.Any)
1018+
case _ => defn.AnyType
1019+
}
1020+
1021+
def bottomOf(tp: Type)(implicit ctx: Context): Type = tp.phantomTopClass match {
1022+
case top: ClassInfo => top.prefix.select(tpnme.Nothing)
1023+
case _ => defn.NothingType
1024+
}
1025+
1026+
lazy val ErasedPhantomClass = ctx.requiredClass("dotty.runtime.ErasedPhantom")
1027+
def ErasedPhantomType = ErasedPhantomClass.typeRef
1028+
1029+
lazy val ErasedPhantomLatticeClass = ctx.requiredClass("dotty.runtime.ErasedPhantomLattice")
1030+
def ErasedPhantomLatticeType = ErasedPhantomLatticeClass.typeRef
9881031
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,8 @@ object StdNames {
230230
final val SourceFileATTR: N = "SourceFile"
231231
final val SyntheticATTR: N = "Synthetic"
232232

233+
final val Phantom: N = "Phantom"
234+
233235
// ----- Term names -----------------------------------------
234236

235237
// Compiler-internal

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import config.Printers.{typr, constr, subtyping, noPrinter}
1212
import TypeErasure.{erasedLub, erasedGlb}
1313
import TypeApplications._
1414
import scala.util.control.NonFatal
15+
import scala.annotation.tailrec
1516

1617
/** Provides methods to compare types.
1718
*/
@@ -550,8 +551,16 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
550551
case OrType(tp1, tp2) => isNullable(tp1) || isNullable(tp2)
551552
case _ => false
552553
}
553-
(tp1.symbol eq NothingClass) && tp2.isValueTypeOrLambda ||
554-
(tp1.symbol eq NullClass) && isNullable(tp2)
554+
def isPhantom(tp: Type): Boolean = tp.widenDealias match {
555+
case tp: TypeRef => defn.isPhantomAnyClass(tp.symbol)
556+
case tp: RefinedOrRecType => isPhantom(tp.parent)
557+
case tp: AndOrType => isPhantom(tp.tp1)
558+
case _ => false
559+
}
560+
if (tp1.symbol eq NothingClass) tp2.isValueTypeOrLambda && !isPhantom(tp2)
561+
else if (tp1.symbol eq NullClass) isNullable(tp2) && !isPhantom(tp2)
562+
else if (defn.isPhantomNothingClass(tp1.symbol)) tp2.isValueTypeOrLambda && (tp1.phantomTopClass == tp2.phantomTopClass)
563+
else false
555564
}
556565
case tp1: SingletonType =>
557566
/** if `tp2 == p.type` and `p: q.type` then try `tp1 <:< q.type` as a last effort.*/

compiler/src/dotty/tools/dotc/core/TypeOps.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import util.{SimpleMap, Property}
1515
import collection.mutable
1616
import ast.tpd._
1717

18+
import scala.annotation.tailrec
19+
1820
trait TypeOps { this: Context => // TODO: Make standalone object.
1921

2022
/** The type `tp` as seen from prefix `pre` and owner `cls`. See the spec
@@ -199,7 +201,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
199201
}
200202

201203
/** The minimal set of classes in `cs` which derive all other classes in `cs` */
202-
def dominators(cs: List[ClassSymbol], accu: List[ClassSymbol]): List[ClassSymbol] = (cs: @unchecked) match {
204+
@tailrec def dominators(cs: List[ClassSymbol], accu: List[ClassSymbol]): List[ClassSymbol] = cs match {
203205
case c :: rest =>
204206
val accu1 = if (accu exists (_ derivesFrom c)) accu else c :: accu
205207
if (cs == c.baseClasses) accu1 else dominators(rest, accu1)
@@ -255,7 +257,8 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
255257
else tp.baseTypeWithArgs(cls)
256258
base.mapReduceOr(identity)(mergeRefined)
257259
}
258-
doms.map(baseTp).reduceLeft(AndType.apply)
260+
if (doms.isEmpty) new ErrorType("no parents in common") // This can happen in the union of Any with PhantomAny
261+
else doms.map(baseTp).reduceLeft(AndType.apply)
259262
}
260263
}
261264
}

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,21 @@ object Types {
169169
case _ =>
170170
false
171171
}
172-
cls == defn.AnyClass || loop(this)
172+
loop(this)
173+
}
174+
175+
final def isPhantom(implicit ctx: Context): Boolean = phantomTopClass.exists
176+
177+
final def phantomTopClass(implicit ctx: Context): Type = this match {
178+
case tp: ClassInfo if isPhantomClass(tp.classSymbol) => tp
179+
case tp: TypeProxy => tp.superType.phantomTopClass
180+
case tp: AndOrType => tp.tp1.phantomTopClass
181+
case _ => NoType
173182
}
174183

184+
private def isPhantomClass(sym: Symbol)(implicit ctx: Context): Boolean =
185+
sym.isClass && (sym.owner eq defn.PhantomClass) && (sym.name == tpnme.Any || sym.name == tpnme.Nothing)
186+
175187
/** Is this type guaranteed not to have `null` as a value?
176188
* For the moment this is only true for modules, but it could
177189
* be refined later.

compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ public enum ErrorMessageID {
5555
RecursiveValueNeedsResultTypeID,
5656
CyclicReferenceInvolvingID,
5757
CyclicReferenceInvolvingImplicitID,
58+
ErasedPhantomsSignatureCollisionID,
59+
PhantomInheritanceID,
60+
PhantomMixedBoundsID,
61+
PhantomCrossedMixedBoundsID,
62+
MatchPhantomID,
63+
MatchOnPhantomID,
64+
IfElsePhantomID,
65+
PhantomIsInObjectID,
5866
;
5967

6068
public int errorNumber() {

compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import Symbols._
1010
import Names._
1111
import NameOps._
1212
import Types._
13+
import Flags._
1314
import util.SourcePosition
1415
import config.Settings.Setting
1516
import interfaces.Diagnostic.{ERROR, INFO, WARNING}
@@ -133,7 +134,7 @@ object messages {
133134
}
134135

135136
case class EmptyCatchBlock(tryBody: untpd.Tree)(implicit ctx: Context)
136-
extends EmptyCatchOrFinallyBlock(tryBody, EmptyCatchBlockID) {
137+
extends EmptyCatchOrFinallyBlock(tryBody, EmptyCatchBlockID) {
137138
val kind = "Syntax"
138139
val msg =
139140
hl"""|The ${"catch"} block does not contain a valid expression, try
@@ -1192,4 +1193,81 @@ object messages {
11921193
|""".stripMargin
11931194
}
11941195

1196+
1197+
case class ErasedPhantomsSignatureCollision(decls: Iterable[Symbol], erased: (Name, Type))(implicit ctx: Context)
1198+
extends Message(ErasedPhantomsSignatureCollisionID) {
1199+
val kind = "Phantom restriction"
1200+
val msg = em"After phantom erasure methods $methodsString will have the same signature: ${erased._1}${erased._2}"
1201+
1202+
private def methodsString = decls.map(decl => em"${decl.name}${decl.info}").mkString(", ")
1203+
1204+
val explanation =
1205+
hl"""|Phantom erasure removes all phantom parameters/arguments from methods and functions.
1206+
|""".stripMargin
1207+
}
1208+
1209+
case class PhantomInheritance(cdef: tpd.TypeDef)(implicit ctx: Context)
1210+
extends Message(PhantomInheritanceID) {
1211+
val kind = "Phantom restriction"
1212+
val msg = perfix + " cannot extend both Any and PhantomAny."
1213+
1214+
def perfix =
1215+
if (cdef.symbol.flags.is(Flags.Trait)) "A trait"
1216+
else if (cdef.symbol.flags.is(Flags.Abstract)) "An abstract class"
1217+
else "A class"
1218+
1219+
val explanation =
1220+
hl"""|""".stripMargin
1221+
}
1222+
1223+
case class PhantomMixedBounds(op: untpd.Ident)(implicit ctx: Context)
1224+
extends Message(PhantomMixedBoundsID) {
1225+
val kind = "Phantom restriction"
1226+
val msg = hl"Can not mix types of ${"Any"} and ${"Phantom.Any"} with ${op.show}."
1227+
1228+
val explanation =
1229+
hl"""|""".stripMargin
1230+
}
1231+
1232+
case class PhantomCrossedMixedBounds(lo: untpd.Tree, hi: untpd.Tree)(implicit ctx: Context)
1233+
extends Message(PhantomCrossedMixedBoundsID) {
1234+
val kind = "Phantom restriction"
1235+
val msg = hl"Type can not be bounded at the same time by types in the ${"Any"} and ${"Phantom.Any"} latices."
1236+
1237+
val explanation =
1238+
hl"""|""".stripMargin
1239+
}
1240+
1241+
case class MatchPhantom()(implicit ctx: Context) extends Message(MatchPhantomID) {
1242+
val kind = "Phantom restriction"
1243+
val msg = "Pattern matches cannot return phantom and non phantoms"
1244+
1245+
val explanation =
1246+
hl"""|""".stripMargin
1247+
}
1248+
1249+
1250+
case class MatchOnPhantom()(implicit ctx: Context) extends Message(MatchOnPhantomID) {
1251+
val kind = "Phantom restriction"
1252+
val msg = "Cannot pattern match on phantoms"
1253+
1254+
val explanation =
1255+
hl"""|""".stripMargin
1256+
}
1257+
1258+
case class IfElsePhantom()(implicit ctx: Context) extends Message(IfElsePhantomID) {
1259+
val kind = "Phantom restriction"
1260+
val msg = "Cannot yield a phantom type in one of the if branches and not in the other one."
1261+
1262+
val explanation =
1263+
hl"""|""".stripMargin
1264+
}
1265+
1266+
case class PhantomIsInObject()(implicit ctx: Context) extends Message(PhantomIsInObjectID) {
1267+
val kind = "Phantom restriction"
1268+
val msg = s"Only ${"object"} can extend ${"scala.Phantom"}"
1269+
1270+
val explanation =
1271+
hl"""|""".stripMargin
1272+
}
11951273
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package dotty.tools.dotc.transform.phantom
2+
3+
import dotty.tools.dotc.ast.tpd
4+
import dotty.tools.dotc.core.Constants.Constant
5+
import dotty.tools.dotc.core.Contexts._
6+
import dotty.tools.dotc.core.DenotTransformers._
7+
import dotty.tools.dotc.core.Symbols._
8+
import dotty.tools.dotc.core.Types._
9+
import dotty.tools.dotc.transform.TreeTransforms.{MiniPhaseTransform, TransformerInfo}
10+
11+
class PhantomTypeErasure extends MiniPhaseTransform with InfoTransformer {
12+
13+
import tpd._
14+
15+
override def phaseName: String = "phantomTypeErasure"
16+
17+
/** Check what the phase achieves, to be called at any point after it is finished. */
18+
override def checkPostCondition(tree: tpd.Tree)(implicit ctx: Context): Unit = {
19+
assert(!tree.tpe.isPhantom, tree.tpe + " should be erased in " + tree)
20+
}
21+
22+
/* Tree transform */
23+
24+
override def transformTypeTree(tree: TypeTree)(implicit ctx: Context, info: TransformerInfo): Tree = {
25+
val newTpe = erasePhantomAnyType(tree.tpe)
26+
if (newTpe =:= tree.tpe) tree else TypeTree(newTpe)
27+
}
28+
29+
override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo): Tree =
30+
if (defn.isPhantomAssume(tree.fun.symbol)) Literal(Constant(null)).withType(defn.ErasedPhantomType) else tree
31+
32+
/* Symbol transform */
33+
34+
def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = erasePhantomAnyType(tp)
35+
36+
/* private methods */
37+
38+
private def erasePhantomAnyType(tp: Type)(implicit ctx: Context): Type = {
39+
val erasePhantomAnyTypeMap = new DeepTypeMap {
40+
override def apply(tp: Type): Type = tp match {
41+
case tp: TypeRef if defn.isPhantomAnyClass(tp.symbol) || defn.isPhantomNothingClass(tp.symbol) =>
42+
defn.ErasedPhantomType
43+
case tp: TypeRef if tp.typeSymbol eq defn.PhantomClass =>
44+
defn.ErasedPhantomLatticeType
45+
case tp: MethodType if tp.resultType.isPhantom =>
46+
// Erase return type to Object to match FunctionN erased return type
47+
val methodType = if (tp.isImplicit) ImplicitMethodType else MethodType
48+
methodType(tp.paramNames, tp.paramTypes, defn.ObjectType)
49+
case _ => mapOver(tp)
50+
}
51+
}
52+
erasePhantomAnyTypeMap(tp)
53+
}
54+
55+
}

0 commit comments

Comments
 (0)