Skip to content

Commit 7c574fe

Browse files
committed
Implement erasable phantom types.
1 parent 5158b26 commit 7c574fe

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
@@ -150,17 +150,18 @@ class Definitions {
150150
}
151151

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

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

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

166167
private def mkArityArray(name: String, arity: Int, countFrom: Int): Array[TypeRef] = {
@@ -934,16 +935,17 @@ class Definitions {
934935
val newDecls = new MutableScope(oldDecls) {
935936
override def lookupEntry(name: Name)(implicit ctx: Context): ScopeEntry = {
936937
val res = super.lookupEntry(name)
937-
if (res == null && name.isTypeName && name.isSyntheticFunction)
938+
if (res ne null) res
939+
else if (name.isTypeName && name.isSyntheticFunction)
938940
newScopeEntry(newFunctionNTrait(name.asTypeName))
939-
else res
941+
else null
940942
}
941943
}
942944
ScalaPackageClass.info = oldInfo.derivedClassInfo(decls = newDecls)
943945
}
944946

945947
/** Lists core classes that don't have underlying bytecode, but are synthesized on-the-fly in every reflection universe */
946-
lazy val syntheticScalaClasses = List(
948+
private lazy val syntheticScalaClasses = List(
947949
AnyClass,
948950
AnyRefAlias,
949951
RepeatedParamClass,
@@ -952,14 +954,15 @@ class Definitions {
952954
NullClass,
953955
NothingClass,
954956
SingletonClass,
955-
EqualsPatternClass)
957+
EqualsPatternClass,
958+
PhantomClass)
956959

957960
lazy val syntheticCoreClasses = syntheticScalaClasses ++ List(
958961
EmptyPackageVal,
959962
OpsPackageClass)
960963

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

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

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

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

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

232+
final val Phantom: N = "Phantom"
233+
232234
// ----- Term names -----------------------------------------
233235

234236
// 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
@@ -168,9 +168,21 @@ object Types {
168168
case _ =>
169169
false
170170
}
171-
cls == defn.AnyClass || loop(this)
171+
loop(this)
172+
}
173+
174+
final def isPhantom(implicit ctx: Context): Boolean = phantomTopClass.exists
175+
176+
final def phantomTopClass(implicit ctx: Context): Type = this match {
177+
case tp: ClassInfo if isPhantomClass(tp.classSymbol) => tp
178+
case tp: TypeProxy => tp.superType.phantomTopClass
179+
case tp: AndOrType => tp.tp1.phantomTopClass
180+
case _ => NoType
172181
}
173182

183+
private def isPhantomClass(sym: Symbol)(implicit ctx: Context): Boolean =
184+
sym.isClass && (sym.owner eq defn.PhantomClass) && (sym.name == tpnme.Any || sym.name == tpnme.Nothing)
185+
174186
/** Is this type guaranteed not to have `null` as a value?
175187
* For the moment this is only true for modules, but it could
176188
* 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
@@ -51,6 +51,14 @@ public enum ErrorMessageID {
5151
MixedLeftAndRightAssociativeOpsID,
5252
CantInstantiateAbstractClassOrTraitID,
5353
AnnotatedPrimaryConstructorRequiresModifierOrThisID,
54+
ErasedPhantomsSignatureCollisionID,
55+
PhantomInheritanceID,
56+
PhantomMixedBoundsID,
57+
PhantomCrossedMixedBoundsID,
58+
MatchPhantomID,
59+
MatchOnPhantomID,
60+
IfElsePhantomID,
61+
PhantomIsInObjectID,
5462
;
5563

5664
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}
@@ -132,7 +133,7 @@ object messages {
132133
}
133134

134135
case class EmptyCatchBlock(tryBody: untpd.Tree)(implicit ctx: Context)
135-
extends EmptyCatchOrFinallyBlock(tryBody, EmptyCatchBlockID) {
136+
extends EmptyCatchOrFinallyBlock(tryBody, EmptyCatchBlockID) {
136137
val kind = "Syntax"
137138
val msg =
138139
hl"""|The ${"catch"} block does not contain a valid expression, try
@@ -1147,4 +1148,81 @@ object messages {
11471148
| ^^^^
11481149
|""".stripMargin
11491150
}
1151+
1152+
case class ErasedPhantomsSignatureCollision(decls: Iterable[Symbol], erased: (Name, Type))(implicit ctx: Context)
1153+
extends Message(ErasedPhantomsSignatureCollisionID) {
1154+
val kind = "Phantom restriction"
1155+
val msg = em"After phantom erasure methods $methodsString will have the same signature: ${erased._1}${erased._2}"
1156+
1157+
private def methodsString = decls.map(decl => em"${decl.name}${decl.info}").mkString(", ")
1158+
1159+
val explanation =
1160+
hl"""|Phantom erasure removes all phantom parameters/arguments from methods and functions.
1161+
|""".stripMargin
1162+
}
1163+
1164+
case class PhantomInheritance(cdef: tpd.TypeDef)(implicit ctx: Context)
1165+
extends Message(PhantomInheritanceID) {
1166+
val kind = "Phantom restriction"
1167+
val msg = perfix + " cannot extend both Any and PhantomAny."
1168+
1169+
def perfix =
1170+
if (cdef.symbol.flags.is(Flags.Trait)) "A trait"
1171+
else if (cdef.symbol.flags.is(Flags.Abstract)) "An abstract class"
1172+
else "A class"
1173+
1174+
val explanation =
1175+
hl"""|""".stripMargin
1176+
}
1177+
1178+
case class PhantomMixedBounds(op: untpd.Ident)(implicit ctx: Context)
1179+
extends Message(PhantomMixedBoundsID) {
1180+
val kind = "Phantom restriction"
1181+
val msg = hl"Can not mix types of ${"Any"} and ${"Phantom.Any"} with ${op.show}."
1182+
1183+
val explanation =
1184+
hl"""|""".stripMargin
1185+
}
1186+
1187+
case class PhantomCrossedMixedBounds(lo: untpd.Tree, hi: untpd.Tree)(implicit ctx: Context)
1188+
extends Message(PhantomCrossedMixedBoundsID) {
1189+
val kind = "Phantom restriction"
1190+
val msg = hl"Type can not be bounded at the same time by types in the ${"Any"} and ${"Phantom.Any"} latices."
1191+
1192+
val explanation =
1193+
hl"""|""".stripMargin
1194+
}
1195+
1196+
case class MatchPhantom()(implicit ctx: Context) extends Message(MatchPhantomID) {
1197+
val kind = "Phantom restriction"
1198+
val msg = "Pattern matches cannot return phantom and non phantoms"
1199+
1200+
val explanation =
1201+
hl"""|""".stripMargin
1202+
}
1203+
1204+
1205+
case class MatchOnPhantom()(implicit ctx: Context) extends Message(MatchOnPhantomID) {
1206+
val kind = "Phantom restriction"
1207+
val msg = "Cannot pattern match on phantoms"
1208+
1209+
val explanation =
1210+
hl"""|""".stripMargin
1211+
}
1212+
1213+
case class IfElsePhantom()(implicit ctx: Context) extends Message(IfElsePhantomID) {
1214+
val kind = "Phantom restriction"
1215+
val msg = "Cannot yield a phantom type in one of the if branches and not in the other one."
1216+
1217+
val explanation =
1218+
hl"""|""".stripMargin
1219+
}
1220+
1221+
case class PhantomIsInObject()(implicit ctx: Context) extends Message(PhantomIsInObjectID) {
1222+
val kind = "Phantom restriction"
1223+
val msg = s"Only ${"object"} can extend ${"scala.Phantom"}"
1224+
1225+
val explanation =
1226+
hl"""|""".stripMargin
1227+
}
11501228
}
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)