Skip to content

Commit 0cc2643

Browse files
committed
Implement phantom types.
In this commit phantom types are defined and implemented. Only a minimal version of phantom term erasure is included. Phantom types are in latices outside of the normal `Any`/`Nothing` type lattice. A new phantom lattice can be defined by extending an `object` with `scala.Phantom`. This trait defines synthetic members * `protected final trait Any` where this `Any` does not extends `scala.Any` * `protected final abstract class Nothing extends this.Any` * `protected final def assume[P >: this.Nothing <: this.Any]: P` A phantom lattice object can expose any of the phantom members (`Any`, `Nothing`, `assume`) using an alias but it is not required. Restriction on lattices: * Types from different lattice can not be mixed using `&` or `|` types. * Type parameters must be bounded by types in a single lattice. Phantom type erasure (minimalistic): * Type references `Phantom.Any` and `Phantom.Nothing` are erased to `dotty.runtime.ErasedPhantom` * `Phantom.assume[P]` is erased to `null.asInstanceOf[P]` * `Phantom` is erased to `dotty.runtime.ErasedPhantomLattice`, removing the members of `Phantom`
1 parent 673564d commit 0cc2643

File tree

85 files changed

+1490
-27
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

+1490
-27
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
@@ -705,16 +705,6 @@ object desugar {
705705
tree
706706
}
707707

708-
/** EmptyTree in lower bound ==> Nothing
709-
* EmptyTree in upper bounds ==> Any
710-
*/
711-
def typeBoundsTree(tree: TypeBoundsTree)(implicit ctx: Context): TypeBoundsTree = {
712-
val TypeBoundsTree(lo, hi) = tree
713-
val lo1 = if (lo.isEmpty) untpd.TypeTree(defn.NothingType) else lo
714-
val hi1 = if (hi.isEmpty) untpd.TypeTree(defn.AnyType) else hi
715-
cpy.TypeBoundsTree(tree)(lo1, hi1)
716-
}
717-
718708
/** Make closure corresponding to function.
719709
* params => body
720710
* ==>

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

Lines changed: 49 additions & 7 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 = PolyType.syntheticParamNames(typeParamCount)
154-
val tparamInfos = tparamNames map (_ => TypeBounds.empty)
155+
val tparamInfos = tparamNames map bounds
155156
val ptype = PolyType(tparamNames)(_ => tparamInfos, 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] = {
@@ -926,7 +927,7 @@ class Definitions {
926927
// ----- Initialization ---------------------------------------------------
927928

928929
/** Lists core classes that don't have underlying bytecode, but are synthesized on-the-fly in every reflection universe */
929-
lazy val syntheticScalaClasses = List(
930+
private lazy val syntheticScalaClasses = List(
930931
AnyClass,
931932
AnyRefAlias,
932933
RepeatedParamClass,
@@ -935,14 +936,15 @@ class Definitions {
935936
NullClass,
936937
NothingClass,
937938
SingletonClass,
938-
EqualsPatternClass)
939+
EqualsPatternClass,
940+
PhantomClass)
939941

940942
lazy val syntheticCoreClasses = syntheticScalaClasses ++ List(
941943
EmptyPackageVal,
942944
OpsPackageClass)
943945

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

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

@@ -962,4 +964,44 @@ class Definitions {
962964
_isInitialized = true
963965
}
964966
}
967+
968+
// ----- Phantoms ---------------------------------------------------------
969+
970+
lazy val PhantomClass: ClassSymbol = {
971+
val cls = completeClass(enterCompleteClassSymbol(ScalaPackageClass, tpnme.Phantom, Abstract, List(AnyType)))
972+
973+
val any = enterCompleteClassSymbol(cls, tpnme.Any, Protected | Final | NoInitsTrait, Nil)
974+
val nothing = enterCompleteClassSymbol(cls, tpnme.Nothing, Protected | Final | NoInitsTrait, List(any.typeRef))
975+
976+
val tparamNames = tpnme.syntheticTypeParamNames(1)
977+
val ptype = PolyType(tparamNames, List(0))(_ => TypeBounds(nothing.typeRef, any.typeRef) :: Nil, PolyParam(_, 0))
978+
newSymbol(cls, nme.assume_, Protected | Final | Method, ptype).entered
979+
980+
cls
981+
}
982+
983+
def isPhantomAnyClass(sym: Symbol)(implicit ctx: Context): Boolean =
984+
sym.exists && (sym.owner eq PhantomClass) && sym.name == tpnme.Any
985+
986+
def isPhantomNothingClass(sym: Symbol)(implicit ctx: Context): Boolean =
987+
sym.exists && (sym.owner eq PhantomClass) && sym.name == tpnme.Nothing
988+
989+
def isPhantomAssume(sym: Symbol)(implicit ctx: Context): Boolean =
990+
sym.exists && (sym.owner eq PhantomClass) && sym.name == nme.assume_
991+
992+
def topOf(tp: Type)(implicit ctx: Context): Type = tp.phantomTopClass match {
993+
case top: ClassInfo => top.prefix.select(tpnme.Any)
994+
case _ => defn.AnyType
995+
}
996+
997+
def bottomOf(tp: Type)(implicit ctx: Context): Type = tp.phantomTopClass match {
998+
case top: ClassInfo => top.prefix.select(tpnme.Nothing)
999+
case _ => defn.NothingType
1000+
}
1001+
1002+
lazy val ErasedPhantomClass = ctx.requiredClass("dotty.runtime.ErasedPhantom")
1003+
def ErasedPhantomType = ErasedPhantomClass.typeRef
1004+
1005+
lazy val ErasedPhantomLatticeClass = ctx.requiredClass("dotty.runtime.ErasedPhantomLattice")
1006+
def ErasedPhantomLatticeType = ErasedPhantomLatticeClass.typeRef
9651007
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ object StdNames {
236236
final val SourceFileATTR: N = "SourceFile"
237237
final val SyntheticATTR: N = "Synthetic"
238238

239+
final val Phantom: N = "Phantom"
240+
239241
// ----- Term names -----------------------------------------
240242

241243
// 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
@@ -16,6 +16,8 @@ import util.{SimpleMap, Property}
1616
import collection.mutable
1717
import ast.tpd._
1818

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

2123
/** The type `tp` as seen from prefix `pre` and owner `cls`. See the spec
@@ -204,7 +206,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
204206
}
205207

206208
/** The minimal set of classes in `cs` which derive all other classes in `cs` */
207-
def dominators(cs: List[ClassSymbol], accu: List[ClassSymbol]): List[ClassSymbol] = (cs: @unchecked) match {
209+
@tailrec def dominators(cs: List[ClassSymbol], accu: List[ClassSymbol]): List[ClassSymbol] = cs match {
208210
case c :: rest =>
209211
val accu1 = if (accu exists (_ derivesFrom c)) accu else c :: accu
210212
if (cs == c.baseClasses) accu1 else dominators(rest, accu1)
@@ -260,7 +262,8 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
260262
else tp.baseTypeWithArgs(cls)
261263
base.mapReduceOr(identity)(mergeRefined)
262264
}
263-
doms.map(baseTp).reduceLeft(AndType.apply)
265+
if (doms.isEmpty) new ErrorType("no parents in common") // This can happen in the union of Any with PhantomAny
266+
else doms.map(baseTp).reduceLeft(AndType.apply)
264267
}
265268
}
266269
}

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

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

187+
private def isPhantomClass(sym: Symbol)(implicit ctx: Context): Boolean =
188+
sym.isClass && (sym.owner eq defn.PhantomClass) && (sym.name == tpnme.Any || sym.name == tpnme.Nothing)
189+
178190
/** Is this type guaranteed not to have `null` as a value?
179191
* For the moment this is only true for modules, but it could
180192
* 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
@@ -56,6 +56,14 @@ public enum ErrorMessageID {
5656
CyclicReferenceInvolvingID,
5757
CyclicReferenceInvolvingImplicitID,
5858
SuperQualMustBeParentID,
59+
ErasedPhantomsSignatureCollisionID,
60+
PhantomInheritanceID,
61+
PhantomMixedBoundsID,
62+
PhantomCrossedMixedBoundsID,
63+
MatchPhantomID,
64+
MatchOnPhantomID,
65+
IfElsePhantomID,
66+
PhantomIsInObjectID,
5967
;
6068

6169
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
@@ -1221,4 +1222,81 @@ object messages {
12211222
|Attempting to define a field in a method signature after a varargs field is an error.
12221223
|""".stripMargin
12231224
}
1225+
1226+
case class ErasedPhantomsSignatureCollision(decls: Iterable[Symbol], erased: (Name, Type))(implicit ctx: Context)
1227+
extends Message(ErasedPhantomsSignatureCollisionID) {
1228+
val kind = "Phantom restriction"
1229+
val msg = em"After phantom erasure methods $methodsString will have the same signature: ${erased._1}${erased._2}"
1230+
1231+
private def methodsString = decls.map(decl => em"${decl.name}${decl.info}").mkString(", ")
1232+
1233+
val explanation =
1234+
hl"""|Phantom erasure removes all phantom parameters/arguments from methods and functions.
1235+
|""".stripMargin
1236+
}
1237+
1238+
case class PhantomInheritance(cdef: tpd.TypeDef)(implicit ctx: Context)
1239+
extends Message(PhantomInheritanceID) {
1240+
val kind = "Phantom restriction"
1241+
val msg = perfix + " cannot extend both Any and PhantomAny."
1242+
1243+
def perfix =
1244+
if (cdef.symbol.flags.is(Flags.Trait)) "A trait"
1245+
else if (cdef.symbol.flags.is(Flags.Abstract)) "An abstract class"
1246+
else "A class"
1247+
1248+
val explanation =
1249+
hl"""|""".stripMargin
1250+
}
1251+
1252+
case class PhantomMixedBounds(op: untpd.Ident)(implicit ctx: Context)
1253+
extends Message(PhantomMixedBoundsID) {
1254+
val kind = "Phantom restriction"
1255+
val msg = hl"Can not mix types of ${"Any"} and ${"Phantom.Any"} with ${op.show}."
1256+
1257+
val explanation =
1258+
hl"""|""".stripMargin
1259+
}
1260+
1261+
case class PhantomCrossedMixedBounds(lo: untpd.Tree, hi: untpd.Tree)(implicit ctx: Context)
1262+
extends Message(PhantomCrossedMixedBoundsID) {
1263+
val kind = "Phantom restriction"
1264+
val msg = hl"Type can not be bounded at the same time by types in the ${"Any"} and ${"Phantom.Any"} latices."
1265+
1266+
val explanation =
1267+
hl"""|""".stripMargin
1268+
}
1269+
1270+
case class MatchPhantom()(implicit ctx: Context) extends Message(MatchPhantomID) {
1271+
val kind = "Phantom restriction"
1272+
val msg = "Pattern matches cannot return phantom and non phantoms"
1273+
1274+
val explanation =
1275+
hl"""|""".stripMargin
1276+
}
1277+
1278+
1279+
case class MatchOnPhantom()(implicit ctx: Context) extends Message(MatchOnPhantomID) {
1280+
val kind = "Phantom restriction"
1281+
val msg = "Cannot pattern match on phantoms"
1282+
1283+
val explanation =
1284+
hl"""|""".stripMargin
1285+
}
1286+
1287+
case class IfElsePhantom()(implicit ctx: Context) extends Message(IfElsePhantomID) {
1288+
val kind = "Phantom restriction"
1289+
val msg = "Cannot yield a phantom type in one of the if branches and not in the other one."
1290+
1291+
val explanation =
1292+
hl"""|""".stripMargin
1293+
}
1294+
1295+
case class PhantomIsInObject()(implicit ctx: Context) extends Message(PhantomIsInObjectID) {
1296+
val kind = "Phantom restriction"
1297+
val msg = s"Only ${"object"} can extend ${"scala.Phantom"}"
1298+
1299+
val explanation =
1300+
hl"""|""".stripMargin
1301+
}
12241302
}
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)