Skip to content

Commit f2e637b

Browse files
committed
Make all phantoms unused
This removes the necesity of erasing phantoms. This process is done by the erasure of `unused`.
1 parent a06d1a1 commit f2e637b

File tree

84 files changed

+233
-488
lines changed

Some content is hidden

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

84 files changed

+233
-488
lines changed

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,7 @@ class Compiler {
6868
new ShortcutImplicits, // Allow implicit functions without creating closures
6969
new CrossCastAnd, // Normalize selections involving intersection types.
7070
new Splitter), // Expand selections involving union types into conditionals
71-
List(new PhantomArgLift, // Extracts the evaluation of phantom arguments placing them before the call
72-
new UnusedDecls, // Removes all unused defs and vals decls (except for parameters)
71+
List(new UnusedDecls, // Removes all unused defs and vals decls (except for parameters)
7372
new VCInlineMethods, // Inlines calls to value class methods
7473
new SeqLiterals, // Express vararg arguments as arrays
7574
new InterceptedMethods, // Special handling of `==`, `|=`, `getClass` methods

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
437437
else if (tpw isRef defn.DoubleClass) Literal(Constant(0d))
438438
else if (tpw isRef defn.ByteClass) Literal(Constant(0.toByte))
439439
else if (tpw isRef defn.ShortClass) Literal(Constant(0.toShort))
440+
else if (tpw.isPhantom) Literal(Constant(null)).withType(tpw)
440441
else Literal(Constant(null)).select(defn.Any_asInstanceOf).appliedToType(tpe)
441442
}
442443

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,21 +1109,15 @@ class Definitions {
11091109

11101110
val any = enterCompleteClassSymbol(cls, tpnme.Any, Protected | Final | NoInitsTrait, Nil)
11111111
val nothing = enterCompleteClassSymbol(cls, tpnme.Nothing, Protected | Final | NoInitsTrait, List(any.typeRef))
1112-
enterMethod(cls, nme.assume_, ExprType(nothing.typeRef), Protected | Final | Method)
1112+
enterMethod(cls, nme.assume_, ExprType(nothing.typeRef), Protected | Final | Method | Unused)
11131113

11141114
cls
11151115
}
11161116
lazy val Phantom_AnyClass = PhantomClass.unforcedDecls.find(_.name eq tpnme.Any).asClass
11171117
lazy val Phantom_NothingClass = PhantomClass.unforcedDecls.find(_.name eq tpnme.Nothing).asClass
1118-
lazy val Phantom_assume = PhantomClass.unforcedDecls.find(_.name eq nme.assume_)
11191118

11201119
/** If the symbol is of the class scala.Phantom.Any or scala.Phantom.Nothing */
11211120
def isPhantomTerminalClass(sym: Symbol) = (sym eq Phantom_AnyClass) || (sym eq Phantom_NothingClass)
11221121

1123-
11241122
lazy val ErasedPhantomType: TypeRef = ctx.requiredClassRef("dotty.runtime.ErasedPhantom")
1125-
def ErasedPhantomClass(implicit ctx: Context) = ErasedPhantomType.symbol.asClass
1126-
1127-
def ErasedPhantom_UNIT(implicit ctx: Context) = ErasedPhantomClass.linkedClass.requiredValue("UNIT")
1128-
11291123
}

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

Lines changed: 0 additions & 33 deletions
This file was deleted.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ case class Signature(paramsSig: List[TypeName], resSig: TypeName) {
8484
* to the parameter part of this signature.
8585
*/
8686
def prepend(params: List[Type], isJava: Boolean)(implicit ctx: Context) =
87-
Signature(params.collect { case p if !p.isPhantom => sigName(p, isJava) } ++ paramsSig, resSig)
87+
Signature(params.map(p => sigName(p, isJava)) ++ paramsSig, resSig)
8888

8989
/** A signature is under-defined if its paramsSig part contains at least one
9090
* `tpnme.Uninstantiated`. Under-defined signatures arise when taking a signature

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

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
378378
else if (semiEraseVCs && isDerivedValueClass(sym)) eraseDerivedValueClassRef(tp)
379379
else if (sym == defn.ArrayClass) apply(tp.appliedTo(TypeBounds.empty)) // i966 shows that we can hit a raw Array type.
380380
else if (defn.isSyntheticFunctionClass(sym)) defn.erasedFunctionType(sym)
381-
else if (defn.isPhantomTerminalClass(sym)) PhantomErasure.erasedPhantomType
381+
else if (defn.isPhantomTerminalClass(sym)) defn.ErasedPhantomType
382382
else if (sym eq defn.PhantomClass) defn.ObjectType // To erase the definitions of Phantom.{assume, Any, Nothing}
383383
else eraseNormalClassRef(tp)
384384
case tp: AppliedType =>
@@ -399,10 +399,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
399399
case tp: MethodType =>
400400
def paramErasure(tpToErase: Type) =
401401
erasureFn(tp.isJavaMethod, semiEraseVCs, isConstructor, wildcardOK)(tpToErase)
402-
val (names, formals0) =
403-
if (tp.isUnusedMethod) (Nil, Nil)
404-
else if (tp.paramInfos.exists(_.isPhantom)) tp.paramNames.zip(tp.paramInfos).filterNot(_._2.isPhantom).unzip
405-
else (tp.paramNames, tp.paramInfos)
402+
val (names, formals0) = if (tp.isUnusedMethod) (Nil, Nil) else (tp.paramNames, tp.paramInfos)
406403
val formals = formals0.mapConserve(paramErasure)
407404
eraseResult(tp.resultType) match {
408405
case rt: MethodType =>
@@ -520,8 +517,6 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
520517
}
521518
if (defn.isSyntheticFunctionClass(sym))
522519
sigName(defn.erasedFunctionType(sym))
523-
else if (defn.isPhantomTerminalClass(tp.symbol))
524-
sigName(PhantomErasure.erasedPhantomType)
525520
else
526521
normalizeClass(sym.asClass).fullName.asTypeName
527522
case tp: AppliedType =>

compiler/src/dotty/tools/dotc/transform/Constructors.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ class Constructors extends MiniPhase with IdentityDenotTransformer { thisPhase =
129129
// Produce aligned accessors and constructor parameters. We have to adjust
130130
// for any outer parameters, which are last in the sequence of original
131131
// parameter accessors but come first in the constructor parameter list.
132-
val accessors = cls.paramAccessors.filterNot(x => x.isSetter || x.info.resultType.classSymbol == defn.ErasedPhantomClass)
132+
val accessors = cls.paramAccessors.filterNot(x => x.isSetter)
133133
val vparamsWithOuterLast = vparams match {
134134
case vparam :: rest if vparam.name == nme.OUTER => rest ::: vparam :: Nil
135135
case _ => vparams

compiler/src/dotty/tools/dotc/transform/Erasure.scala

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import ValueClasses._
2828
import TypeUtils._
2929
import ExplicitOuter._
3030
import core.Mode
31-
import core.PhantomErasure
3231
import reporting.trace
3332

3433
class Erasure extends Phase with DenotTransformer {
@@ -210,8 +209,6 @@ object Erasure {
210209
val tree1 =
211210
if (tree.tpe isRef defn.NullClass)
212211
adaptToType(tree, underlying)
213-
else if (wasPhantom(underlying))
214-
PhantomErasure.erasedParameterRef
215212
else if (!(tree.tpe <:< tycon)) {
216213
assert(!(tree.tpe.typeSymbol.isPrimitiveValueClass))
217214
val nullTree = Literal(Constant(null))
@@ -427,16 +424,9 @@ object Erasure {
427424
}
428425
}
429426

430-
if ((origSym eq defn.Phantom_assume) || (origSym.is(Flags.ParamAccessor) && wasPhantom(pt)))
431-
PhantomErasure.erasedAssume
432-
else recur(typed(tree.qualifier, AnySelectionProto))
427+
recur(typed(tree.qualifier, AnySelectionProto))
433428
}
434429

435-
override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context): tpd.Tree =
436-
if (tree.symbol eq defn.Phantom_assume) PhantomErasure.erasedAssume
437-
else if (tree.symbol.is(Flags.Param) && wasPhantom(tree.typeOpt)) PhantomErasure.erasedParameterRef
438-
else super.typedIdent(tree, pt)
439-
440430
override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree =
441431
if (tree.symbol == ctx.owner.lexicallyEnclosingClass || tree.symbol.isStaticOwner) promote(tree)
442432
else {
@@ -496,11 +486,9 @@ object Erasure {
496486
.withType(defn.ArrayOf(defn.ObjectType))
497487
args0 = bunchedArgs :: Nil
498488
}
499-
// Arguments are phantom if an only if the parameters are phantom, guaranteed by the separation of type lattices
500-
val args1 = args0.filterConserve(arg => !wasPhantom(arg.typeOpt))
501-
assert(args1 hasSameLengthAs mt.paramInfos)
502-
val args2 = args1.zipWithConserve(mt.paramInfos)(typedExpr)
503-
untpd.cpy.Apply(tree)(fun1, args2) withType mt.resultType
489+
assert(args0 hasSameLengthAs mt.paramInfos)
490+
val args1 = args0.zipWithConserve(mt.paramInfos)(typedExpr)
491+
untpd.cpy.Apply(tree)(fun1, args1) withType mt.resultType
504492
case _ =>
505493
throw new MatchError(i"tree $tree has unexpected type of function ${fun1.tpe.widen}, was ${fun.typeOpt.widen}")
506494
}
@@ -561,11 +549,6 @@ object Erasure {
561549
rhs1 = untpd.Block(paramDefs, rhs1)
562550
}
563551
vparamss1 = vparamss1.mapConserve(_.filterConserve(!_.symbol.is(Flags.Unused)))
564-
vparamss1 = vparamss1.mapConserve(_.filterConserve(vparam => !wasPhantom(vparam.tpe)))
565-
if (sym.is(Flags.ParamAccessor) && wasPhantom(ddef.tpt.tpe)) {
566-
sym.resetFlag(Flags.ParamAccessor)
567-
rhs1 = PhantomErasure.erasedParameterRef
568-
}
569552
val ddef1 = untpd.cpy.DefDef(ddef)(
570553
tparams = Nil,
571554
vparamss = vparamss1,
@@ -680,7 +663,4 @@ object Erasure {
680663

681664
def takesBridges(sym: Symbol)(implicit ctx: Context) =
682665
sym.isClass && !sym.is(Flags.Trait | Flags.Package)
683-
684-
private def wasPhantom(tp: Type)(implicit ctx: Context): Boolean =
685-
tp.widenDealias.classSymbol eq defn.ErasedPhantomClass
686666
}

compiler/src/dotty/tools/dotc/transform/FirstTransform.scala

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -174,14 +174,6 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase =>
174174
} else ddef
175175
}
176176

177-
override def transformValDef(vdef: tpd.ValDef)(implicit ctx: Context): tpd.Tree = {
178-
if (vdef.tpt.tpe.isPhantom) {
179-
if (vdef.symbol.is(Mutable)) ctx.error("var fields cannot have Phantom types", vdef.pos)
180-
else if (vdef.symbol.hasAnnotation(defn.VolatileAnnot)) ctx.error("Phantom fields cannot be @volatile", vdef.pos)
181-
}
182-
vdef
183-
}
184-
185177
override def transformStats(trees: List[Tree])(implicit ctx: Context): List[Tree] =
186178
ast.Trees.flatten(reorderAndComplete(trees)(ctx.withPhase(thisPhase.next)))
187179

compiler/src/dotty/tools/dotc/transform/Memoize.scala

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ import Decorators._
107107
if (sym eq defn.NothingClass) Throw(Literal(Constant(null)))
108108
else if (sym eq defn.NullClass) Literal(Constant(null))
109109
else if (sym eq defn.BoxedUnitClass) ref(defn.BoxedUnit_UNIT)
110-
else if (sym eq defn.ErasedPhantomClass) ref(defn.ErasedPhantom_UNIT)
111110
else {
112111
assert(false, sym + " has no erased bottom tree")
113112
EmptyTree
@@ -120,11 +119,8 @@ import Decorators._
120119
def adaptToField(tree: Tree): Tree =
121120
if (tree.isEmpty) tree else tree.ensureConforms(field.info.widen)
122121

123-
def isErasableBottomField(cls: Symbol): Boolean = {
124-
// TODO: For Scala.js, return false if this field is in a js.Object unless it is an ErasedPhantomClass.
125-
!field.isVolatile &&
126-
((cls eq defn.NothingClass) || (cls eq defn.NullClass) || (cls eq defn.BoxedUnitClass) || (cls eq defn.ErasedPhantomClass))
127-
}
122+
def isErasableBottomField(cls: Symbol): Boolean =
123+
!field.isVolatile && ((cls eq defn.NothingClass) || (cls eq defn.NullClass) || (cls eq defn.BoxedUnitClass))
128124

129125
if (sym.isGetter) {
130126
var rhs = tree.rhs.changeOwnerAfter(sym, field, thisPhase)

compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ class SyntheticMethods(thisPhase: DenotTransformer) {
163163
val thatAsClazz = ctx.newSymbol(ctx.owner, nme.x_0, Synthetic, clazzType, coord = ctx.owner.pos) // x$0
164164
def wildcardAscription(tp: Type) = Typed(Underscore(tp), TypeTree(tp))
165165
val pattern = Bind(thatAsClazz, wildcardAscription(clazzType)) // x$0 @ (_: C)
166-
val comparisons = accessors collect { case accessor if !accessor.info.isPhantom =>
166+
val comparisons = accessors map { accessor =>
167167
This(clazz).select(accessor).select(defn.Any_==).appliedTo(ref(thatAsClazz).select(accessor)) }
168168
val rhs = // this.x == this$0.x && this.y == x$0.y
169169
if (comparisons.isEmpty) Literal(Constant(true)) else comparisons.reduceLeft(_ and _)
@@ -191,7 +191,7 @@ class SyntheticMethods(thisPhase: DenotTransformer) {
191191
*/
192192
def valueHashCodeBody(implicit ctx: Context): Tree = {
193193
assert(accessors.nonEmpty)
194-
assert(accessors.tail.forall(a => a.info.isPhantom || a.is(Unused)))
194+
assert(accessors.tail.forall(_ is Unused))
195195
ref(accessors.head).select(nme.hashCode_).ensureApplied
196196
}
197197

compiler/src/dotty/tools/dotc/transform/localopt/Simplify.scala

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import core.NameOps._
1111
import transform.MegaPhase.MiniPhase
1212
import config.Printers.simplify
1313
import ast.tpd
14-
import dotty.tools.dotc.core.PhantomErasure
1514

1615
import scala.annotation.tailrec
1716

@@ -184,8 +183,6 @@ object Simplify {
184183
sym.owner.is(CaseClass) &&
185184
sym.name.isSelectorName &&
186185
!sym.info.decls.exists(_.is(Mutable | Lazy)) // Conservatively covers case class A(var x: Int)
187-
val isErasedPhantom = PhantomErasure.isErasedPhantom(sym)
188-
189-
isImmutableGetter || isCaseAccessor || isProductAccessor || isErasedPhantom
186+
isImmutableGetter || isCaseAccessor || isProductAccessor
190187
}
191188
}

compiler/src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -500,12 +500,10 @@ object Checking {
500500
case param :: params =>
501501
if (param.is(Mutable))
502502
ctx.error(ValueClassParameterMayNotBeAVar(clazz, param), param.pos)
503-
if (param.info.isPhantom)
504-
ctx.error("value class first parameter must not be phantom", param.pos)
505-
else if (param.is(Unused))
503+
if (param.is(Unused))
506504
ctx.error("value class first parameter cannot be `unused`", param.pos)
507505
else {
508-
for (p <- params if !(p.info.isPhantom || p.is(Unused)))
506+
for (p <- params if !p.is(Unused))
509507
ctx.error("value class can only have one non `unused` parameter", p.pos)
510508
}
511509
case Nil =>

docs/docs/reference/phantom-types.md

Lines changed: 6 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ package scala
6868
trait Phantom { // only an `object` can extend this trait
6969
protected final type Any // not a subtype of scala.Any
7070
protected final type Nothing // subtype of every subtype of this.Any
71-
protected final def assume: this.Nothing
71+
protected final unused def assume: this.Nothing
7272
}
7373
```
7474

@@ -102,8 +102,8 @@ precise type. In our example we will only create values of type `Pinky` and `Cly
102102
object MyPhantoms extends Phantom {
103103
... // Type definition
104104

105-
def pinky: Pinky = assume
106-
def clyde: Clyde = assume
105+
unused def pinky: Pinky = assume
106+
unused def clyde: Clyde = assume
107107
}
108108
```
109109

@@ -115,8 +115,8 @@ We can look at the following simple application:
115115
```scala
116116
import MyPhantoms._
117117
object MyApp {
118-
def run(phantom: Inky) = println("run")
119-
def hide(phantom: Blinky) = println("run")
118+
def run(unused phantom: Inky) = println("run")
119+
def hide(unused phantom: Blinky) = println("run")
120120

121121
run(pinky)
122122
run(clyde)
@@ -134,48 +134,4 @@ mistakes before when compiling the code, no surprises at runtime (hopefully not
134134
What happens with Phantoms at runtime?
135135
--------------------------------------
136136

137-
Disclaimer: Most of phantom erasure is implemented, but not all of is has been merged in `dotty/master` yet.
138-
139-
As phantom have no effect on the result of a method invocation we just remove them for the call an definition.
140-
The evaluation of the phantom parameter is still be done unless it can be optimized away.
141-
By removing them we also restrict overloading as `def f()` and `def f(x: MyPhantom)` will
142-
have the same signature in the bytecode, just use different names to avoid this.
143-
144-
At runtime the `scala.Phantom` trait will not exist.
145-
* The object extending `Phantom` will not extend it anymore
146-
* All phantom types will be erased on a single erased type (important in overloading for methods returning a phantom)
147-
* Calls to `Phantom.assume` will become a reference to a singleton of the erased phantom type and will be removed wherever possible
148-
149-
```scala
150-
object MyOtherPhantom extends Phantom {
151-
type MyPhantom <: this.Any
152-
def myPhantom: MyPhantom = assume
153-
154-
def f1(a: Int, b: MyPhantom, c: Int): Int = a + c
155-
156-
def f2 = {
157-
f1(3, myPhantom, 2)
158-
}
159-
}
160-
```
161-
162-
will be compiled to
163-
164-
```scala
165-
object MyOtherPhantom {
166-
def myPhantom(): <ErasedPhantom> = <ErasedPhantom.UNIT>
167-
168-
def f1(a: Int, c: Int): Int = a + c
169-
170-
def f2 = {
171-
val a$ = 3
172-
myPhantom()
173-
val b$ = 3
174-
f1(a$, b$)
175-
}
176-
}
177-
```
178-
179-
Note that `myPhantom` is not removed as it could have some side effect before returning the phantom.
180-
To remove it just use `inline def myPhantom` instead this will remove the call and allow the
181-
`<ErasedPhantom.UNIT>` to be optimized away.
137+
All phantoms are erased using the `unused` keyword. Theyb will simply not exist at runtime.

library/src/dotty/runtime/ErasedPhantom.java

Lines changed: 0 additions & 30 deletions
This file was deleted.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package dotty.runtime
2+
3+
/** Type representing an erased phantom type */
4+
final abstract class ErasedPhantom

0 commit comments

Comments
 (0)