Skip to content

Commit

Permalink
Merge pull request #2399 from dotty-staging/implement-phantom-types-p…
Browse files Browse the repository at this point in the history
…art-2

Erase arguments of phantom type
  • Loading branch information
nicolasstucki authored Jul 18, 2017
2 parents b989415 + 975cee5 commit d121ad5
Show file tree
Hide file tree
Showing 21 changed files with 455 additions and 10 deletions.
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ class Compiler {
new ShortcutImplicits, // Allow implicit functions without creating closures
new CrossCastAnd, // Normalize selections involving intersection types.
new Splitter), // Expand selections involving union types into conditionals
List(new VCInlineMethods, // Inlines calls to value class methods
List(new PhantomArgLift, // Extracts the evaluation of phantom arguments placing them before the call.
new VCInlineMethods, // Inlines calls to value class methods
new SeqLiterals, // Express vararg arguments as arrays
new InterceptedMethods, // Special handling of `==`, `|=`, `getClass` methods
new Getters, // Replace non-private vals and vars with getter defs (fields are added later)
Expand Down
12 changes: 9 additions & 3 deletions compiler/src/dotty/tools/dotc/core/PhantomErasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.Symbols.defn
import dotty.tools.dotc.core.Types.Type

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

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

}
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Signature.scala
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ case class Signature(paramsSig: List[TypeName], resSig: TypeName) {
* to the parameter part of this signature.
*/
def prepend(params: List[Type], isJava: Boolean)(implicit ctx: Context) =
Signature((params.map(sigName(_, isJava))) ++ paramsSig, resSig)
Signature(params.collect { case p if !p.isPhantom => sigName(p, isJava) } ++ paramsSig, resSig)

/** A signature is under-defined if its paramsSig part contains at least one
* `tpnme.Uninstantiated`. Under-defined signatures arise when taking a signature
Expand Down
9 changes: 6 additions & 3 deletions compiler/src/dotty/tools/dotc/core/TypeErasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -400,12 +400,15 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
case tp: MethodType =>
def paramErasure(tpToErase: Type) =
erasureFn(tp.isJava, semiEraseVCs, isConstructor, wildcardOK)(tpToErase)
val formals = tp.paramInfos.mapConserve(paramErasure)
val (names, formals0) =
if (tp.paramInfos.exists(_.isPhantom)) tp.paramNames.zip(tp.paramInfos).filterNot(_._2.isPhantom).unzip
else (tp.paramNames, tp.paramInfos)
val formals = formals0.mapConserve(paramErasure)
eraseResult(tp.resultType) match {
case rt: MethodType =>
tp.derivedLambdaType(tp.paramNames ++ rt.paramNames, formals ++ rt.paramInfos, rt.resultType)
tp.derivedLambdaType(names ++ rt.paramNames, formals ++ rt.paramInfos, rt.resultType)
case rt =>
tp.derivedLambdaType(tp.paramNames, formals, rt)
tp.derivedLambdaType(names, formals, rt)
}
case tp: PolyType =>
this(tp.resultType)
Expand Down
14 changes: 13 additions & 1 deletion compiler/src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ object Erasure {
val tree1 =
if (tree.tpe isRef defn.NullClass)
adaptToType(tree, underlying)
else if (wasPhantom(underlying))
PhantomErasure.erasedParameterRef
else if (!(tree.tpe <:< tycon)) {
assert(!(tree.tpe.typeSymbol.isPrimitiveValueClass))
val nullTree = Literal(Constant(null))
Expand Down Expand Up @@ -418,6 +420,7 @@ object Erasure {

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

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

def takesBridges(sym: Symbol)(implicit ctx: Context) =
sym.isClass && !sym.is(Flags.Trait | Flags.Package)

private def wasPhantom(tp: Type)(implicit ctx: Context): Boolean =
tp.widenDealias.classSymbol eq defn.ErasedPhantomClass
}
69 changes: 69 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/PhantomArgLift.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package dotty.tools.dotc.transform

import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.core.Contexts._
import dotty.tools.dotc.core.NameKinds._
import dotty.tools.dotc.core.Types._
import dotty.tools.dotc.transform.TreeTransforms.{MiniPhaseTransform, TransformerInfo}
import dotty.tools.dotc.typer.EtaExpansion

import scala.collection.mutable.ListBuffer

/** This phase extracts the arguments of phantom type before the application to avoid losing any
* effects in the argument tree. This trivializes the removal of parameter in the Erasure phase.
*
* `f(x1,...)(y1,...)...(...)` with at least one phantom argument
*
* -->
*
* `val ev$f = f` // if `f` is some expression that needs evaluation
* `val ev$x1 = x1`
* ...
* `val ev$y1 = y1`
* ...
* `ev$f(ev$x1,...)(ev$y1,...)...(...)`
*
*/
class PhantomArgLift extends MiniPhaseTransform {
import tpd._

override def phaseName: String = "phantomArgLift"

/** Check what the phase achieves, to be called at any point after it is finished. */
override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = tree match {
case tree: Apply =>
tree.args.foreach { arg =>
assert(!arg.tpe.isPhantom || isPureExpr(arg))
}
case _ =>
}

/* Tree transform */

override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = tree.tpe.widen match {
case _: MethodType => tree // Do the transformation higher in the tree if needed
case _ =>
if (!hasImpurePhantomArgs(tree)) tree
else {
val buffer = ListBuffer.empty[Tree]
val app = EtaExpansion.liftApp(buffer, tree)
if (buffer.isEmpty) app
else Block(buffer.result(), app)
}
}

/* private methods */

/** Returns true if at least on of the arguments is an impure phantom.
* Inner applies are also checked in case of multiple parameter list.
*/
private def hasImpurePhantomArgs(tree: Apply)(implicit ctx: Context): Boolean = {
tree.args.exists(arg => arg.tpe.isPhantom && !isPureExpr(arg)) || {
tree.fun match {
case fun: Apply => hasImpurePhantomArgs(fun)
case _ => false
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class VCInlineMethods extends MiniPhaseTransform with IdentityDenotTransformer {
override def phaseName: String = "vcInlineMethods"

override def runsAfter: Set[Class[_ <: Phase]] =
Set(classOf[ExtensionMethods], classOf[PatternMatcher])
Set(classOf[ExtensionMethods], classOf[PatternMatcher], classOf[PhantomArgLift])

/** Replace a value class method call by a call to the corresponding extension method.
*
Expand Down
1 change: 1 addition & 0 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ class CompilationTests extends ParallelTesting {
compileFile("../tests/neg/customArgs/xfatalWarnings.scala", defaultOptions.and("-Xfatal-warnings")) +
compileFile("../tests/neg/customArgs/pureStatement.scala", defaultOptions.and("-Xfatal-warnings")) +
compileFile("../tests/neg/customArgs/phantom-overload.scala", allowDoubleBindings) +
compileFile("../tests/neg/customArgs/phantom-overload-2.scala", allowDoubleBindings) +
compileFile("../tests/neg/tailcall/t1672b.scala", defaultOptions) +
compileFile("../tests/neg/tailcall/t3275.scala", defaultOptions) +
compileFile("../tests/neg/tailcall/t6574.scala", defaultOptions) +
Expand Down
21 changes: 21 additions & 0 deletions tests/neg/customArgs/phantom-overload-2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

class phantomOverload2 {
import Boo._

def foo1() = ???
def foo1(x: A) = ??? // error
def foo1(x1: B)(x2: N) = ??? // error

def foo2(x1: Int, x2: A) = ???
def foo2(x1: A)(x2: Int) = ??? // error
def foo2(x1: N)(x2: A)(x3: Int) = ??? // error

def foo3(x1: Int, x2: A) = ???
def foo3(x1: Int, x2: A)(x3: A) = ??? // error
}

object Boo extends Phantom {
type A <: this.Any
type B <: this.Any
type N = this.Nothing
}
16 changes: 16 additions & 0 deletions tests/pos/phantom-in-value-class.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

object PhantomInValueClass {
import BooUtil._
new VC("ghi").foo(boo)
}

object BooUtil extends Phantom {

type Boo <: this.Any
def boo: Boo = assume

class VC[T](val x: T) extends AnyVal {
def foo(b: Boo) = println(x)
}

}
Empty file.
33 changes: 33 additions & 0 deletions tests/run/phantom-erased-methods.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import dotty.runtime.ErasedPhantom

/* Run this test with
* `run tests/run/xyz.scala -Xprint-diff-del -Xprint:arrayConstructors,phantomTermErasure,phantomTypeErasure,erasure`
* to see the the diffs after PhantomRefErasure, PhantomDeclErasure and Erasure.
*/

object Test {
import Boo._

def main(args: Array[String]): Unit = {
val foo = new Foo

// check that parameters have been removed from the functions
// (would fail with NoSuchMethodException if not erased)
foo.getClass.getDeclaredMethod("fun1")
foo.getClass.getDeclaredMethod("fun2", classOf[String])

assert(foo.getClass.getDeclaredMethod("fun3").getReturnType == classOf[ErasedPhantom])
}
}

class Foo {
import Boo._
def fun1(b: BooAny): Unit = ()
def fun2(b: BooAny, s: String): Unit = ()
def fun3(): BooAny = boo
}

object Boo extends Phantom {
type BooAny = Boo.Any
def boo: BooAny = assume
}
26 changes: 26 additions & 0 deletions tests/run/phantom-methods-11.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
x1
x2
x3
x4
x5
fun
y1
y2
y3
y4
y5
Fun
Fun2
z1
z2
z3
z4
z5
Fun2fun
Fun2
w1
w2
w3
w4
w5
Fun2fun2
74 changes: 74 additions & 0 deletions tests/run/phantom-methods-11.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/* Run this test with
* `run tests/run/xyz.scala -Xprint-diff-del -Xprint:arrayConstructors,phantomRefErasure,phantomErasure,erasure`
* to see the the diffs after PhantomRefErasure, PhantomDeclErasure and Erasure.
*/

object Test {
import Boo._

def main(args: Array[String]): Unit = {
fun(
{ println("x1"); boo },
{ println("x2"); boo }
)(
{ println("x3"); boo }
)(
{ println("x4"); boo },
{ println("x5"); boo }
)

new Fun(
{ println("y1"); boo },
{ println("y2"); boo }
)(
{ println("y3"); boo }
)(
{ println("y4"); boo },
{ println("y5"); boo }
)

(new Fun2().fun)(
{ println("z1"); boo },
{ println("z2"); boo }
)(
{ println("z3"); boo }
)(
{ println("z4"); boo },
{ println("z5"); boo }
)

(new Fun2().fun2)(
{ println("w1"); boo },
{ println("w2"); boo }
)(
{ println("w3"); boo }
)(
{ println("w4"); boo },
{ println("w5"); boo }
)
}

def fun(x1: Inky, x2: Inky)(x3: Inky)(x4: Inky, x5: Inky) = {
println("fun")
}

class Fun(y1: Inky, y2: Inky)(y3: Inky)(y4: Inky, y5: Inky) {
println("Fun")
}

class Fun2 {
println("Fun2")
def fun(z1: Inky, z2: Inky)(z3: Inky)(z4: Inky, z5: Inky) = {
println("Fun2fun")
}

def fun2[T](z1: Inky, z2: Inky)(z3: Inky)(z4: Inky, z5: Inky) = {
println("Fun2fun2")
}
}
}

object Boo extends Phantom {
type Inky <: this.Any
def boo: Inky = assume
}
18 changes: 18 additions & 0 deletions tests/run/phantom-methods-12.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
x1
x2
x3
x4
x5
fun1
y1
y2
y3
y4
y5
fun2
z1
z2
z3
z4
z5
fun3
Loading

0 comments on commit d121ad5

Please sign in to comment.