Skip to content

Commit d121ad5

Browse files
Merge pull request #2399 from dotty-staging/implement-phantom-types-part-2
Erase arguments of phantom type
2 parents b989415 + 975cee5 commit d121ad5

21 files changed

+455
-10
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ class Compiler {
6969
new ShortcutImplicits, // Allow implicit functions without creating closures
7070
new CrossCastAnd, // Normalize selections involving intersection types.
7171
new Splitter), // Expand selections involving union types into conditionals
72-
List(new VCInlineMethods, // Inlines calls to value class methods
72+
List(new PhantomArgLift, // Extracts the evaluation of phantom arguments placing them before the call.
73+
new VCInlineMethods, // Inlines calls to value class methods
7374
new SeqLiterals, // Express vararg arguments as arrays
7475
new InterceptedMethods, // Special handling of `==`, `|=`, `getClass` methods
7576
new Getters, // Replace non-private vals and vars with getter defs (fields are added later)

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ import dotty.tools.dotc.core.Contexts.Context
55
import dotty.tools.dotc.core.Symbols.defn
66
import dotty.tools.dotc.core.Types.Type
77

8-
/** Phantom erasure erases (minimal erasure):
8+
/** Phantom erasure erases:
99
*
10-
* - Parameters/arguments are erased to ErasedPhantom. The next step will remove the parameters
11-
* from the method definitions and calls (implemented in branch implement-phantom-types-part-2).
10+
* - Parameters/arguments are removed from the function definition/call in `PhantomArgLift`.
11+
* If the evaluation of the phantom arguments may produce a side effect, these are evaluated and stored in
12+
* local `val`s and then the non phantoms are used in the Apply. Phantom `val`s are then erased to
13+
* `val ev$i: ErasedPhantom = myPhantom` intended to be optimized away by local optimizations. `myPhantom` could be
14+
* a reference to a phantom parameter, a call to Phantom assume or a call to a method that returns a phantom.
1215
* - Definitions of `def`, `val`, `lazy val` and `var` returning a phantom type to return a ErasedPhantom. Where fields
1316
* with ErasedPhantom type are not memoized (see transform/Memoize.scala).
1417
* - Calls to Phantom.assume become calls to ErasedPhantom.UNIT. Intended to be optimized away by local optimizations.
@@ -21,4 +24,7 @@ object PhantomErasure {
2124
/** Returns the default erased tree for a call to Phantom.assume */
2225
def erasedAssume(implicit ctx: Context): Tree = ref(defn.ErasedPhantom_UNIT)
2326

27+
/** Returns the default erased tree for a phantom parameter ref */
28+
def erasedParameterRef(implicit ctx: Context): Tree = ref(defn.ErasedPhantom_UNIT)
29+
2430
}

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.map(sigName(_, isJava))) ++ paramsSig, resSig)
87+
Signature(params.collect { case p if !p.isPhantom => 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: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -400,12 +400,15 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
400400
case tp: MethodType =>
401401
def paramErasure(tpToErase: Type) =
402402
erasureFn(tp.isJava, semiEraseVCs, isConstructor, wildcardOK)(tpToErase)
403-
val formals = tp.paramInfos.mapConserve(paramErasure)
403+
val (names, formals0) =
404+
if (tp.paramInfos.exists(_.isPhantom)) tp.paramNames.zip(tp.paramInfos).filterNot(_._2.isPhantom).unzip
405+
else (tp.paramNames, tp.paramInfos)
406+
val formals = formals0.mapConserve(paramErasure)
404407
eraseResult(tp.resultType) match {
405408
case rt: MethodType =>
406-
tp.derivedLambdaType(tp.paramNames ++ rt.paramNames, formals ++ rt.paramInfos, rt.resultType)
409+
tp.derivedLambdaType(names ++ rt.paramNames, formals ++ rt.paramInfos, rt.resultType)
407410
case rt =>
408-
tp.derivedLambdaType(tp.paramNames, formals, rt)
411+
tp.derivedLambdaType(names, formals, rt)
409412
}
410413
case tp: PolyType =>
411414
this(tp.resultType)

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ object Erasure {
209209
val tree1 =
210210
if (tree.tpe isRef defn.NullClass)
211211
adaptToType(tree, underlying)
212+
else if (wasPhantom(underlying))
213+
PhantomErasure.erasedParameterRef
212214
else if (!(tree.tpe <:< tycon)) {
213215
assert(!(tree.tpe.typeSymbol.isPrimitiveValueClass))
214216
val nullTree = Literal(Constant(null))
@@ -418,6 +420,7 @@ object Erasure {
418420

419421
override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context): tpd.Tree =
420422
if (tree.symbol eq defn.Phantom_assume) PhantomErasure.erasedAssume
423+
else if (tree.symbol.is(Flags.Param) && wasPhantom(tree.typeOpt)) PhantomErasure.erasedParameterRef
421424
else super.typedIdent(tree, pt)
422425

423426
override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree =
@@ -475,7 +478,8 @@ object Erasure {
475478
.withType(defn.ArrayOf(defn.ObjectType))
476479
args0 = bunchedArgs :: Nil
477480
}
478-
val args1 = args0.zipWithConserve(mt.paramInfos)(typedExpr)
481+
// Arguments are phantom if an only if the parameters are phantom, guaranteed by the separation of type lattices
482+
val args1 = args0.filterConserve(arg => !wasPhantom(arg.typeOpt)).zipWithConserve(mt.paramInfos)(typedExpr)
479483
untpd.cpy.Apply(tree)(fun1, args1) withType mt.resultType
480484
case _ =>
481485
throw new MatchError(i"tree $tree has unexpected type of function ${fun1.tpe.widen}, was ${fun.typeOpt.widen}")
@@ -536,6 +540,11 @@ object Erasure {
536540
vparamss1 = (tpd.ValDef(bunchedParam) :: Nil) :: Nil
537541
rhs1 = untpd.Block(paramDefs, rhs1)
538542
}
543+
vparamss1 = vparamss1.mapConserve(_.filterConserve(vparam => !wasPhantom(vparam.tpe)))
544+
if (sym.is(Flags.ParamAccessor) && wasPhantom(ddef.tpt.tpe)) {
545+
sym.resetFlag(Flags.ParamAccessor)
546+
rhs1 = PhantomErasure.erasedParameterRef
547+
}
539548
val ddef1 = untpd.cpy.DefDef(ddef)(
540549
tparams = Nil,
541550
vparamss = vparamss1,
@@ -619,4 +628,7 @@ object Erasure {
619628

620629
def takesBridges(sym: Symbol)(implicit ctx: Context) =
621630
sym.isClass && !sym.is(Flags.Trait | Flags.Package)
631+
632+
private def wasPhantom(tp: Type)(implicit ctx: Context): Boolean =
633+
tp.widenDealias.classSymbol eq defn.ErasedPhantomClass
622634
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package dotty.tools.dotc.transform
2+
3+
import dotty.tools.dotc.ast.tpd
4+
import dotty.tools.dotc.core.Contexts._
5+
import dotty.tools.dotc.core.NameKinds._
6+
import dotty.tools.dotc.core.Types._
7+
import dotty.tools.dotc.transform.TreeTransforms.{MiniPhaseTransform, TransformerInfo}
8+
import dotty.tools.dotc.typer.EtaExpansion
9+
10+
import scala.collection.mutable.ListBuffer
11+
12+
/** This phase extracts the arguments of phantom type before the application to avoid losing any
13+
* effects in the argument tree. This trivializes the removal of parameter in the Erasure phase.
14+
*
15+
* `f(x1,...)(y1,...)...(...)` with at least one phantom argument
16+
*
17+
* -->
18+
*
19+
* `val ev$f = f` // if `f` is some expression that needs evaluation
20+
* `val ev$x1 = x1`
21+
* ...
22+
* `val ev$y1 = y1`
23+
* ...
24+
* `ev$f(ev$x1,...)(ev$y1,...)...(...)`
25+
*
26+
*/
27+
class PhantomArgLift extends MiniPhaseTransform {
28+
import tpd._
29+
30+
override def phaseName: String = "phantomArgLift"
31+
32+
/** Check what the phase achieves, to be called at any point after it is finished. */
33+
override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = tree match {
34+
case tree: Apply =>
35+
tree.args.foreach { arg =>
36+
assert(!arg.tpe.isPhantom || isPureExpr(arg))
37+
}
38+
case _ =>
39+
}
40+
41+
/* Tree transform */
42+
43+
override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = tree.tpe.widen match {
44+
case _: MethodType => tree // Do the transformation higher in the tree if needed
45+
case _ =>
46+
if (!hasImpurePhantomArgs(tree)) tree
47+
else {
48+
val buffer = ListBuffer.empty[Tree]
49+
val app = EtaExpansion.liftApp(buffer, tree)
50+
if (buffer.isEmpty) app
51+
else Block(buffer.result(), app)
52+
}
53+
}
54+
55+
/* private methods */
56+
57+
/** Returns true if at least on of the arguments is an impure phantom.
58+
* Inner applies are also checked in case of multiple parameter list.
59+
*/
60+
private def hasImpurePhantomArgs(tree: Apply)(implicit ctx: Context): Boolean = {
61+
tree.args.exists(arg => arg.tpe.isPhantom && !isPureExpr(arg)) || {
62+
tree.fun match {
63+
case fun: Apply => hasImpurePhantomArgs(fun)
64+
case _ => false
65+
}
66+
}
67+
}
68+
69+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class VCInlineMethods extends MiniPhaseTransform with IdentityDenotTransformer {
4444
override def phaseName: String = "vcInlineMethods"
4545

4646
override def runsAfter: Set[Class[_ <: Phase]] =
47-
Set(classOf[ExtensionMethods], classOf[PatternMatcher])
47+
Set(classOf[ExtensionMethods], classOf[PatternMatcher], classOf[PhantomArgLift])
4848

4949
/** Replace a value class method call by a call to the corresponding extension method.
5050
*

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ class CompilationTests extends ParallelTesting {
165165
compileFile("../tests/neg/customArgs/xfatalWarnings.scala", defaultOptions.and("-Xfatal-warnings")) +
166166
compileFile("../tests/neg/customArgs/pureStatement.scala", defaultOptions.and("-Xfatal-warnings")) +
167167
compileFile("../tests/neg/customArgs/phantom-overload.scala", allowDoubleBindings) +
168+
compileFile("../tests/neg/customArgs/phantom-overload-2.scala", allowDoubleBindings) +
168169
compileFile("../tests/neg/tailcall/t1672b.scala", defaultOptions) +
169170
compileFile("../tests/neg/tailcall/t3275.scala", defaultOptions) +
170171
compileFile("../tests/neg/tailcall/t6574.scala", defaultOptions) +
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
class phantomOverload2 {
3+
import Boo._
4+
5+
def foo1() = ???
6+
def foo1(x: A) = ??? // error
7+
def foo1(x1: B)(x2: N) = ??? // error
8+
9+
def foo2(x1: Int, x2: A) = ???
10+
def foo2(x1: A)(x2: Int) = ??? // error
11+
def foo2(x1: N)(x2: A)(x3: Int) = ??? // error
12+
13+
def foo3(x1: Int, x2: A) = ???
14+
def foo3(x1: Int, x2: A)(x3: A) = ??? // error
15+
}
16+
17+
object Boo extends Phantom {
18+
type A <: this.Any
19+
type B <: this.Any
20+
type N = this.Nothing
21+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
object PhantomInValueClass {
3+
import BooUtil._
4+
new VC("ghi").foo(boo)
5+
}
6+
7+
object BooUtil extends Phantom {
8+
9+
type Boo <: this.Any
10+
def boo: Boo = assume
11+
12+
class VC[T](val x: T) extends AnyVal {
13+
def foo(b: Boo) = println(x)
14+
}
15+
16+
}

0 commit comments

Comments
 (0)