Skip to content

Encode quoted values #4103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,9 @@ class Definitions {
lazy val QuotedExpr_runR = QuotedExprClass.requiredMethodRef(nme.run)
def QuotedExpr_run(implicit ctx: Context) = QuotedExpr_runR.symbol

lazy val QuotedExprsModule = ctx.requiredModule("scala.quoted.Exprs")
def QuotedExprsClass(implicit ctx: Context) = QuotedExprsModule.symbol.asClass

lazy val QuotedTypeType = ctx.requiredClassRef("scala.quoted.Type")
def QuotedTypeClass(implicit ctx: Context) = QuotedTypeType.symbol.asClass

Expand All @@ -636,6 +639,7 @@ class Definitions {
def QuotedType_apply(implicit ctx: Context) = QuotedType_applyR.symbol

def Unpickler_unpickleExpr = ctx.requiredMethod("scala.runtime.quoted.Unpickler.unpickleExpr")
def Unpickler_liftedExpr = ctx.requiredMethod("scala.runtime.quoted.Unpickler.liftedExpr")
def Unpickler_unpickleType = ctx.requiredMethod("scala.runtime.quoted.Unpickler.unpickleType")

lazy val EqType = ctx.requiredClassRef("scala.Eq")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ object PickledQuotes {
/** Transform the expression into its fully spliced Tree */
def quotedExprToTree(expr: quoted.Expr[_])(implicit ctx: Context): Tree = expr match {
case expr: TastyExpr[_] => unpickleExpr(expr)
case expr: ValueExpr[_] => Literal(Constant(expr.value))
case expr: LiftedExpr[_] => Literal(Constant(expr.value))
case expr: TreeExpr[Tree] @unchecked => expr.tree
case expr: FunctionAppliedTo[_, _] =>
functionAppliedTo(quotedExprToTree(expr.f), quotedExprToTree(expr.x))
Expand Down
8 changes: 4 additions & 4 deletions compiler/src/dotty/tools/dotc/quoted/Toolbox.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import dotty.tools.dotc.printing.RefinedPrinter

import scala.quoted.Expr
import scala.runtime.BoxedUnit
import scala.quoted.Exprs.ValueExpr
import scala.quoted.Exprs.LiftedExpr
import scala.runtime.quoted._

/** Default runners for quoted expressions */
Expand All @@ -23,12 +23,12 @@ object Toolbox {
): Toolbox[T] = new Toolbox[T] {

def run(expr: Expr[T]): T = expr match {
case expr: ValueExpr[T] => expr.value
case expr: LiftedExpr[T] => expr.value
case _ => new QuoteDriver().run(expr, runSettings)
}

def show(expr: Expr[T]): String = expr match {
case expr: ValueExpr[T] =>
case expr: LiftedExpr[T] =>
implicit val ctx = new QuoteDriver().initCtx
if (showSettings.compilerArgs.contains("-color:never"))
ctx.settings.color.update("never")
Expand All @@ -46,7 +46,7 @@ object Toolbox {
case _ => None
}
expr match {
case expr: ValueExpr[T] => Some(expr.value)
case expr: LiftedExpr[T] => Some(expr.value)
case _ => new QuoteDriver().withTree(expr, (tree, _) => toConstantOpt(tree), Settings.run())
}
}
Expand Down
79 changes: 51 additions & 28 deletions compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import tasty.TreePickler.Hole
import MegaPhase.MiniPhase
import SymUtils._
import NameKinds._
import dotty.tools.dotc.ast.tpd.Tree
import typer.Implicits.SearchFailureType

import scala.collection.mutable
Expand Down Expand Up @@ -72,19 +73,19 @@ class ReifyQuotes extends MacroTransformWithImplicits {
val levelOf = new mutable.HashMap[Symbol, Int]

/** Register a reference defined in a quote but used in another quote nested in a splice.
* Returns a lifted version of the reference that needs to be used in its place.
* Returns a version of the reference that needs to be used in its place.
* '{
* val x = ???
* { ... '{ ... x ... } ... }.unary_~
* }
* Lifting the `x` in `{ ... '{ ... x ... } ... }.unary_~` will return a `x$1.unary_~` for which the `x$1`
* Eta expanding the `x` in `{ ... '{ ... x ... } ... }.unary_~` will return a `x$1.unary_~` for which the `x$1`
* be created by some outer reifier.
*
* This transformation is only applied to definitions at staging level 1.
*
* See `needsLifting`
* See `isCaptured`
*/
val lifters = new mutable.HashMap[Symbol, RefTree => Tree]
val capturers = new mutable.HashMap[Symbol, RefTree => Tree]
}

/** The main transformer class
Expand Down Expand Up @@ -290,12 +291,25 @@ class ReifyQuotes extends MacroTransformWithImplicits {
}
else {
val (body1, splices) = nested(isQuote = true).split(body)
pickledQuote(body1, splices, isType).withPos(quote.pos)
}
}

private def pickledQuote(body: Tree, splices: List[Tree], isType: Boolean)(implicit ctx: Context) = {
def pickleAsValue[T](value: T) =
ref(defn.Unpickler_liftedExpr).appliedToType(body.tpe.widen).appliedTo(Literal(Constant(value)))
def pickleAsTasty() = {
val meth =
if (isType) ref(defn.Unpickler_unpickleType).appliedToType(body1.tpe)
else ref(defn.Unpickler_unpickleExpr).appliedToType(body1.tpe.widen)
if (isType) ref(defn.Unpickler_unpickleType).appliedToType(body.tpe)
else ref(defn.Unpickler_unpickleExpr).appliedToType(body.tpe.widen)
meth.appliedTo(
liftList(PickledQuotes.pickleQuote(body1).map(x => Literal(Constant(x))), defn.StringType),
liftList(splices, defn.AnyType)).withPos(quote.pos)
liftList(PickledQuotes.pickleQuote(body).map(x => Literal(Constant(x))), defn.StringType),
liftList(splices, defn.AnyType))
}
if (splices.nonEmpty) pickleAsTasty()
else ReifyQuotes.toValue(body) match {
case Some(value) => pickleAsValue(value)
case _ => pickleAsTasty()
}
}

Expand Down Expand Up @@ -334,7 +348,7 @@ class ReifyQuotes extends MacroTransformWithImplicits {
* { ... '{ ... x$1.unary_~ ... y$1.unary_~ ... } ... }
* }
*
* See: `lift`
* See: `capture`
*
* At the same time register `embedded` trees `x` and `y` to place as arguments of the hole
* placed in the original code.
Expand All @@ -347,17 +361,17 @@ class ReifyQuotes extends MacroTransformWithImplicits {
private def makeLambda(tree: Tree)(implicit ctx: Context): Tree = {
def body(arg: Tree)(implicit ctx: Context): Tree = {
var i = 0
transformWithLifter(tree)(
(lifted: mutable.ListBuffer[Tree]) => (tree: RefTree) => {
transformWithCapturer(tree)(
(captured: mutable.ListBuffer[Tree]) => (tree: RefTree) => {
val argTpe =
if (tree.isTerm) defn.QuotedExprType.appliedTo(tree.tpe.widen)
else defn.QuotedTypeType.appliedTo(defn.AnyType)
val selectArg = arg.select(nme.apply).appliedTo(Literal(Constant(i))).asInstance(argTpe)
val liftedArg = SyntheticValDef(UniqueName.fresh(tree.name.toTermName).toTermName, selectArg)
val capturedArg = SyntheticValDef(UniqueName.fresh(tree.name.toTermName).toTermName, selectArg)
i += 1
embedded += tree
lifted += liftedArg
ref(liftedArg.symbol)
captured += capturedArg
ref(capturedArg.symbol)
}
)
}
Expand All @@ -368,21 +382,21 @@ class ReifyQuotes extends MacroTransformWithImplicits {
Closure(meth, tss => body(tss.head.head)(ctx.withOwner(meth)).changeOwner(ctx.owner, meth))
}

private def transformWithLifter(tree: Tree)(
lifter: mutable.ListBuffer[Tree] => RefTree => Tree)(implicit ctx: Context): Tree = {
val lifted = new mutable.ListBuffer[Tree]
val lifter2 = lifter(lifted)
outer.enteredSyms.foreach(s => lifters.put(s, lifter2))
private def transformWithCapturer(tree: Tree)(
capturer: mutable.ListBuffer[Tree] => RefTree => Tree)(implicit ctx: Context): Tree = {
val captured = new mutable.ListBuffer[Tree]
val captured2 = capturer(captured)
outer.enteredSyms.foreach(s => capturers.put(s, captured2))
val tree2 = transform(tree)
lifters --= outer.enteredSyms
seq(lifted.result(), tree2)
capturers --= outer.enteredSyms
seq(captured.result(), tree2)
}

/** Returns true if this tree will be lifted by `makeLambda` */
private def needsLifting(tree: RefTree)(implicit ctx: Context): Boolean = {
// Check phase consistency and presence of lifter
/** Returns true if this tree will be captured by `makeLambda` */
private def isCaptured(tree: RefTree)(implicit ctx: Context): Boolean = {
// Check phase consistency and presence of capturer
level == 1 && !tree.symbol.is(Inline) && levelOf.get(tree.symbol).contains(1) &&
lifters.contains(tree.symbol)
capturers.contains(tree.symbol)
}

/** Transform `tree` and return the resulting tree and all `embedded` quotes
Expand Down Expand Up @@ -416,9 +430,9 @@ class ReifyQuotes extends MacroTransformWithImplicits {
quotation(quotedTree, tree)
case tree: Select if tree.symbol.isSplice =>
splice(tree)
case tree: RefTree if needsLifting(tree) =>
val lift = lifters(tree.symbol)
splice(lift(tree).select(if (tree.isTerm) nme.UNARY_~ else tpnme.UNARY_~))
case tree: RefTree if isCaptured(tree) =>
val capturer = capturers(tree.symbol)
splice(capturer(tree).select(if (tree.isTerm) nme.UNARY_~ else tpnme.UNARY_~))
case Block(stats, _) =>
val last = enteredSyms
stats.foreach(markDef)
Expand Down Expand Up @@ -473,3 +487,12 @@ class ReifyQuotes extends MacroTransformWithImplicits {
}
}
}

object ReifyQuotes {
def toValue(tree: Tree): Option[Any] = tree match {
case Literal(Constant(c)) => Some(c)
case Block(Nil, e) => toValue(e)
case Inlined(_, Nil, e) => toValue(e)
case _ => None
}
}
4 changes: 2 additions & 2 deletions library/src/scala/quoted/Expr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ object Exprs {
override def toString(): String = s"Expr(<pickled>)"
}

/** An Expr backed by a value.
/** An Expr backed by a lifted value.
* Values can only be of type Boolean, Byte, Short, Char, Int, Long, Float, Double, Unit, String or Null.
*/
final class ValueExpr[T](val value: T) extends Expr[T] {
final class LiftedExpr[T](val value: T) extends Expr[T] {
override def toString: String = s"Expr($value)"
}

Expand Down
20 changes: 10 additions & 10 deletions library/src/scala/quoted/Liftable.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package scala.quoted

import scala.quoted.Exprs.ValueExpr
import scala.runtime.quoted.Unpickler.liftedExpr

/** A typeclass for types that can be turned to `quoted.Expr[T]`
* without going through an explicit `'(...)` operation.
Expand All @@ -15,14 +15,14 @@ abstract class Liftable[T] {
* gives an alternative implementation using just the basic staging system.
*/
object Liftable {
implicit def BooleanIsLiftable: Liftable[Boolean] = (x: Boolean) => new ValueExpr(x)
implicit def ByteLiftable: Liftable[Byte] = (x: Byte) => new ValueExpr(x)
implicit def CharIsLiftable: Liftable[Char] = (x: Char) => new ValueExpr(x)
implicit def ShortIsLiftable: Liftable[Short] = (x: Short) => new ValueExpr(x)
implicit def IntIsLiftable: Liftable[Int] = (x: Int) => new ValueExpr(x)
implicit def LongIsLiftable: Liftable[Long] = (x: Long) => new ValueExpr(x)
implicit def FloatIsLiftable: Liftable[Float] = (x: Float) => new ValueExpr(x)
implicit def DoubleIsLiftable: Liftable[Double] = (x: Double) => new ValueExpr(x)
implicit def BooleanIsLiftable: Liftable[Boolean] = (x: Boolean) => liftedExpr(x)
implicit def ByteLiftable: Liftable[Byte] = (x: Byte) => liftedExpr(x)
implicit def CharIsLiftable: Liftable[Char] = (x: Char) => liftedExpr(x)
implicit def ShortIsLiftable: Liftable[Short] = (x: Short) => liftedExpr(x)
implicit def IntIsLiftable: Liftable[Int] = (x: Int) => liftedExpr(x)
implicit def LongIsLiftable: Liftable[Long] = (x: Long) => liftedExpr(x)
implicit def FloatIsLiftable: Liftable[Float] = (x: Float) => liftedExpr(x)
implicit def DoubleIsLiftable: Liftable[Double] = (x: Double) => liftedExpr(x)

implicit def StringIsLiftable: Liftable[String] = (x: String) => new ValueExpr(x)
implicit def StringIsLiftable: Liftable[String] = (x: String) => liftedExpr(x)
}
7 changes: 6 additions & 1 deletion library/src/scala/runtime/quoted/Unpickler.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package scala.runtime.quoted

import scala.quoted.Types.TastyType
import scala.quoted.Exprs.TastyExpr
import scala.quoted.Exprs.{LiftedExpr, TastyExpr}
import scala.quoted.{Expr, Type}

/** Provides methods to unpickle `Expr` and `Type` trees. */
Expand All @@ -17,6 +17,11 @@ object Unpickler {
*/
def unpickleExpr[T](repr: Pickled, args: Seq[Any]): Expr[T] = new TastyExpr[T](repr, args)

/** Lift the `value` to an `Expr` tree.
* Values can only be of type Boolean, Byte, Short, Char, Int, Long, Float, Double, Unit, String or Null.
*/
def liftedExpr[T](value: T): LiftedExpr[T] = new LiftedExpr[T](value)

/** Unpickle `repr` which represents a pickled `Type` tree,
* replacing splice nodes with `args`
*/
Expand Down
15 changes: 15 additions & 0 deletions tests/run/quote-compile-constants.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class scala.quoted.Exprs$LiftedExpr
class scala.quoted.Exprs$LiftedExpr
class scala.quoted.Exprs$LiftedExpr
class scala.quoted.Exprs$LiftedExpr
class scala.quoted.Exprs$LiftedExpr
class scala.quoted.Exprs$LiftedExpr
class scala.quoted.Exprs$LiftedExpr
class scala.quoted.Exprs$LiftedExpr
class scala.quoted.Exprs$LiftedExpr
class scala.quoted.Exprs$LiftedExpr
class scala.quoted.Exprs$LiftedExpr
class scala.quoted.Exprs$LiftedExpr
class scala.quoted.Exprs$LiftedExpr
class scala.quoted.Exprs$LiftedExpr
class scala.quoted.Exprs$LiftedExpr
21 changes: 21 additions & 0 deletions tests/run/quote-compile-constants.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import scala.quoted._

object Test {
def main(args: Array[String]): Unit = {
println(('(true)).getClass)
println(('{ 'a' }).getClass)
println(('{ '\n' }).getClass)
println(('{ '"' }).getClass)
println(('{ '\'' }).getClass)
println(('{ '\\' }).getClass)
println(('(1)).getClass)
println(('( { { 2 } } )).getClass)
println(('(3L)).getClass)
println(('(4.0f)).getClass)
println(('(5.0d)).getClass)
println(('("xyz")).getClass)
println(('()).getClass)
println(('{()}).getClass)
println(('{{()}}).getClass)
}
}