From 4bd610b5e5c0ce722b247df24fc08ed414bba1fa Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 20 Jan 2020 11:54:44 +0100 Subject: [PATCH 1/2] Move Expr.underlyingArgument to UnsafeExpr.underlyingArgument Make it explicit that this operation may change the call semantics. --- library/src/scala/quoted/Expr.scala | 20 ------------- .../src/scala/quoted/unsafe/UnsafeExpr.scala | 28 +++++++++++++++++++ tests/run-macros/i7898/Macro_1.scala | 3 +- .../quoted-matching-docs/Macro_1.scala | 3 +- 4 files changed, 32 insertions(+), 22 deletions(-) create mode 100644 library/src/scala/quoted/unsafe/UnsafeExpr.scala diff --git a/library/src/scala/quoted/Expr.scala b/library/src/scala/quoted/Expr.scala index c3045fd021c5..f360b15d88ed 100644 --- a/library/src/scala/quoted/Expr.scala +++ b/library/src/scala/quoted/Expr.scala @@ -29,26 +29,6 @@ class Expr[+T] private[scala] { final def matches(that: Expr[Any])(given qctx: QuoteContext): Boolean = !scala.internal.quoted.Expr.unapply[Unit, Unit](this)(given that, false, qctx).isEmpty - /** Returns the undelying argument that was in the call before inlining. - * - * ``` - * inline foo(x: Int): Int = baz(x, x) - * foo(bar()) - * ``` - * is inlined as - * ``` - * val x = bar() - * baz(x, x) - * ``` - * in this case the undelying argument of `x` will be `bar()`. - * - * Warning: Using the undelying argument directly in the expansion of a macro may change the parameter - * semantics from by-value to by-name. - */ - def underlyingArgument(given qctx: QuoteContext): Expr[T] = { - import qctx.tasty.{given, _} - this.unseal.underlyingArgument.seal.asInstanceOf[Expr[T]] - } } object Expr { diff --git a/library/src/scala/quoted/unsafe/UnsafeExpr.scala b/library/src/scala/quoted/unsafe/UnsafeExpr.scala new file mode 100644 index 000000000000..22a9bdb73862 --- /dev/null +++ b/library/src/scala/quoted/unsafe/UnsafeExpr.scala @@ -0,0 +1,28 @@ +package scala.quoted +package unsafe + +object UnsafeExpr { + + /** Returns the undelying argument that was in the call before inlining. + * + * ``` + * inline foo(x: Int): Int = baz(x, x) + * foo(bar()) + * ``` + * is inlined as + * ``` + * val x = bar() + * baz(x, x) + * ``` + * in this case the undelying argument of `x` will be `bar()`. + * + * Warning: Using the undelying argument directly in the expansion of a macro may + * change the parameter semantics as by-value parameter could be re-evaluated. + */ + def underlyingArgument[T](expr: Expr[T])(given qctx: QuoteContext): Expr[T] = { + import qctx.tasty.{given, _} + expr.unseal.underlyingArgument.seal.asInstanceOf[Expr[T]] + } + + +} diff --git a/tests/run-macros/i7898/Macro_1.scala b/tests/run-macros/i7898/Macro_1.scala index d76d0567535e..57e8adeb8434 100644 --- a/tests/run-macros/i7898/Macro_1.scala +++ b/tests/run-macros/i7898/Macro_1.scala @@ -1,9 +1,10 @@ import quoted._ +import quoted.unsafe._ object Main { def myMacroImpl(body: Expr[_])(given qctx: QuoteContext): Expr[_] = { import qctx.tasty.{_, given} - val bodyTerm = body.underlyingArgument.unseal + val bodyTerm = UnsafeExpr.underlyingArgument(body).unseal val showed = bodyTerm.show '{ println(${Expr(showed)}) diff --git a/tests/run-macros/quoted-matching-docs/Macro_1.scala b/tests/run-macros/quoted-matching-docs/Macro_1.scala index 480e8240d934..ed1a983a04af 100644 --- a/tests/run-macros/quoted-matching-docs/Macro_1.scala +++ b/tests/run-macros/quoted-matching-docs/Macro_1.scala @@ -1,5 +1,6 @@ import scala.quoted._ import scala.quoted.matching._ +import scala.quoted.unsafe._ inline def sum(args: Int*): Int = ${ sumExpr('args) } @@ -10,7 +11,7 @@ private def sumExprShow(argsExpr: Expr[Seq[Int]])(given QuoteContext): Expr[Stri private def sumExpr(argsExpr: Expr[Seq[Int]])(given qctx: QuoteContext): Expr[Int] = { import qctx.tasty.{given, _} - argsExpr.underlyingArgument match { + UnsafeExpr.underlyingArgument(argsExpr) match { case ConstSeq(args) => // args is of type Seq[Int] Expr(args.sum) // precompute result of sum case ExprSeq(argExprs) => // argExprs is of type Seq[Expr[Int]] From 9185dc4c1484e554604a8875df2cad89198703b3 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 20 Jan 2020 13:29:24 +0100 Subject: [PATCH 2/2] Move Expr.open to UnsafeExpr.open --- library/src/scala/quoted/Expr.scala | 36 -------------- .../src/scala/quoted/unsafe/UnsafeExpr.scala | 47 +++++++++++++++++++ .../quote-matcher-symantics-2/quoted_1.scala | 5 +- .../quote-matcher-symantics-3/quoted_1.scala | 7 +-- .../quote-matching-open/Macro_1.scala | 8 ++-- 5 files changed, 58 insertions(+), 45 deletions(-) diff --git a/library/src/scala/quoted/Expr.scala b/library/src/scala/quoted/Expr.scala index f360b15d88ed..584d3a41c261 100644 --- a/library/src/scala/quoted/Expr.scala +++ b/library/src/scala/quoted/Expr.scala @@ -181,40 +181,4 @@ object Expr { ofTuple(elems).cast[Tuple.InverseMap[T, Expr]] } - // TODO generalize for any function arity (see Expr.betaReduce) - def open[T1, R, X](f: Expr[T1 => R])(content: (Expr[R], [t] => Expr[t] => Expr[T1] => Expr[t]) => X)(given qctx: QuoteContext): X = { - import qctx.tasty.{given, _} - val (params, bodyExpr) = paramsAndBody(f) - content(bodyExpr, [t] => (e: Expr[t]) => (v: Expr[T1]) => bodyFn[t](e.unseal, params, List(v.unseal)).seal.asInstanceOf[Expr[t]]) - } - - def open[T1, T2, R, X](f: Expr[(T1, T2) => R])(content: (Expr[R], [t] => Expr[t] => (Expr[T1], Expr[T2]) => Expr[t]) => X)(given qctx: QuoteContext)(given DummyImplicit): X = { - import qctx.tasty.{given, _} - val (params, bodyExpr) = paramsAndBody(f) - content(bodyExpr, [t] => (e: Expr[t]) => (v1: Expr[T1], v2: Expr[T2]) => bodyFn[t](e.unseal, params, List(v1.unseal, v2.unseal)).seal.asInstanceOf[Expr[t]]) - } - - def open[T1, T2, T3, R, X](f: Expr[(T1, T2, T3) => R])(content: (Expr[R], [t] => Expr[t] => (Expr[T1], Expr[T2], Expr[T3]) => Expr[t]) => X)(given qctx: QuoteContext)(given DummyImplicit, DummyImplicit): X = { - import qctx.tasty.{given, _} - val (params, bodyExpr) = paramsAndBody(f) - content(bodyExpr, [t] => (e: Expr[t]) => (v1: Expr[T1], v2: Expr[T2], v3: Expr[T3]) => bodyFn[t](e.unseal, params, List(v1.unseal, v2.unseal, v3.unseal)).seal.asInstanceOf[Expr[t]]) - } - - private def paramsAndBody[R](given qctx: QuoteContext)(f: Expr[Any]) = { - import qctx.tasty.{given, _} - val Block(List(DefDef("$anonfun", Nil, List(params), _, Some(body))), Closure(Ident("$anonfun"), None)) = f.unseal.etaExpand - (params, body.seal.asInstanceOf[Expr[R]]) - } - - private def bodyFn[t](given qctx: QuoteContext)(e: qctx.tasty.Term, params: List[qctx.tasty.ValDef], args: List[qctx.tasty.Term]): qctx.tasty.Term = { - import qctx.tasty.{given, _} - val map = params.map(_.symbol).zip(args).toMap - new TreeMap { - override def transformTerm(tree: Term)(given ctx: Context): Term = - super.transformTerm(tree) match - case tree: Ident => map.getOrElse(tree.symbol, tree) - case tree => tree - }.transformTerm(e) - } - } diff --git a/library/src/scala/quoted/unsafe/UnsafeExpr.scala b/library/src/scala/quoted/unsafe/UnsafeExpr.scala index 22a9bdb73862..fa447bff7ec5 100644 --- a/library/src/scala/quoted/unsafe/UnsafeExpr.scala +++ b/library/src/scala/quoted/unsafe/UnsafeExpr.scala @@ -24,5 +24,52 @@ object UnsafeExpr { expr.unseal.underlyingArgument.seal.asInstanceOf[Expr[T]] } + // TODO generalize for any function arity (see Expr.betaReduce) + /** Allows inspection or transformation of the body of the expression of function. + * This body may have references to the arguments of the function which should be closed + * over if the expression will be spliced. + * + * ``` + * val f: Expr[T => R] = ... + * UnsafeExpr.open(f) { (body, close) => + * val newParam: Expr[T] = ... + * ... + * close(body)(newParam) // body or part of the body + * } + * ``` + */ + def open[T1, R, X](f: Expr[T1 => R])(content: (Expr[R], [t] => Expr[t] => Expr[T1] => Expr[t]) => X)(given qctx: QuoteContext): X = { + import qctx.tasty.{given, _} + val (params, bodyExpr) = paramsAndBody(f) + content(bodyExpr, [t] => (e: Expr[t]) => (v: Expr[T1]) => bodyFn[t](e.unseal, params, List(v.unseal)).seal.asInstanceOf[Expr[t]]) + } + def open[T1, T2, R, X](f: Expr[(T1, T2) => R])(content: (Expr[R], [t] => Expr[t] => (Expr[T1], Expr[T2]) => Expr[t]) => X)(given qctx: QuoteContext)(given DummyImplicit): X = { + import qctx.tasty.{given, _} + val (params, bodyExpr) = paramsAndBody(f) + content(bodyExpr, [t] => (e: Expr[t]) => (v1: Expr[T1], v2: Expr[T2]) => bodyFn[t](e.unseal, params, List(v1.unseal, v2.unseal)).seal.asInstanceOf[Expr[t]]) + } + + def open[T1, T2, T3, R, X](f: Expr[(T1, T2, T3) => R])(content: (Expr[R], [t] => Expr[t] => (Expr[T1], Expr[T2], Expr[T3]) => Expr[t]) => X)(given qctx: QuoteContext)(given DummyImplicit, DummyImplicit): X = { + import qctx.tasty.{given, _} + val (params, bodyExpr) = paramsAndBody(f) + content(bodyExpr, [t] => (e: Expr[t]) => (v1: Expr[T1], v2: Expr[T2], v3: Expr[T3]) => bodyFn[t](e.unseal, params, List(v1.unseal, v2.unseal, v3.unseal)).seal.asInstanceOf[Expr[t]]) + } + + private def paramsAndBody[R](given qctx: QuoteContext)(f: Expr[Any]) = { + import qctx.tasty.{given, _} + val Block(List(DefDef("$anonfun", Nil, List(params), _, Some(body))), Closure(Ident("$anonfun"), None)) = f.unseal.etaExpand + (params, body.seal.asInstanceOf[Expr[R]]) + } + + private def bodyFn[t](given qctx: QuoteContext)(e: qctx.tasty.Term, params: List[qctx.tasty.ValDef], args: List[qctx.tasty.Term]): qctx.tasty.Term = { + import qctx.tasty.{given, _} + val map = params.map(_.symbol).zip(args).toMap + new TreeMap { + override def transformTerm(tree: Term)(given ctx: Context): Term = + super.transformTerm(tree) match + case tree: Ident => map.getOrElse(tree.symbol, tree) + case tree => tree + }.transformTerm(e) + } } diff --git a/tests/run-macros/quote-matcher-symantics-2/quoted_1.scala b/tests/run-macros/quote-matcher-symantics-2/quoted_1.scala index e9977c17290c..95a007ae40fe 100644 --- a/tests/run-macros/quote-matcher-symantics-2/quoted_1.scala +++ b/tests/run-macros/quote-matcher-symantics-2/quoted_1.scala @@ -1,5 +1,6 @@ import scala.quoted._ import scala.quoted.matching._ +import scala.quoted.unsafe._ object Macros { @@ -22,7 +23,7 @@ object Macros { case '{ ($f: DSL => DSL)($x: DSL) } => sym.app(liftFun(f), lift(x)) case '{ val x: DSL = $value; ($bodyFn: DSL => DSL)(x) } => - Expr.open(bodyFn) { (body1, close) => + UnsafeExpr.open(bodyFn) { (body1, close) => val (i, nEnvVar) = freshEnvVar() lift(close(body1)(nEnvVar))(env + (i -> lift(value))) } @@ -38,7 +39,7 @@ object Macros { def liftFun(e: Expr[DSL => DSL])(implicit env: Map[Int, Expr[T]]): Expr[T => T] = e match { case '{ (x: DSL) => ($bodyFn: DSL => DSL)(x) } => sym.lam((y: Expr[T]) => - Expr.open(bodyFn) { (body1, close) => + UnsafeExpr.open(bodyFn) { (body1, close) => val (i, nEnvVar) = freshEnvVar() lift(close(body1)(nEnvVar))(env + (i -> y)) } diff --git a/tests/run-macros/quote-matcher-symantics-3/quoted_1.scala b/tests/run-macros/quote-matcher-symantics-3/quoted_1.scala index ea10cf1a8b1c..164c07c974f0 100644 --- a/tests/run-macros/quote-matcher-symantics-3/quoted_1.scala +++ b/tests/run-macros/quote-matcher-symantics-3/quoted_1.scala @@ -1,5 +1,6 @@ import scala.quoted._ import scala.quoted.matching._ +import scala.quoted.unsafe._ object Macros { @@ -46,17 +47,17 @@ object Macros { case '{ (x0: Int) => ($bodyFn: Int => Any)(x0) } => val (i, nEnvVar) = freshEnvVar[Int]() - val body2 = Expr.open(bodyFn) { (body1, close) => close(body1)(nEnvVar) } + val body2 = UnsafeExpr.open(bodyFn) { (body1, close) => close(body1)(nEnvVar) } '{ $sym.lam((x: R[Int]) => ${given Env = envWith(i, 'x)(given env); lift(body2)}).asInstanceOf[R[T]] } case '{ (x0: Boolean) => ($bodyFn: Boolean => Any)(x0) } => val (i, nEnvVar) = freshEnvVar[Boolean]() - val body2 = Expr.open(bodyFn) { (body1, close) => close(body1)(nEnvVar) } + val body2 = UnsafeExpr.open(bodyFn) { (body1, close) => close(body1)(nEnvVar) } '{ $sym.lam((x: R[Boolean]) => ${given Env = envWith(i, 'x)(given env); lift(body2)}).asInstanceOf[R[T]] } case '{ (x0: Int => Int) => ($bodyFn: (Int => Int) => Any)(x0) } => val (i, nEnvVar) = freshEnvVar[Int => Int]() - val body2 = Expr.open(bodyFn) { (body1, close) => close(body1)(nEnvVar) } + val body2 = UnsafeExpr.open(bodyFn) { (body1, close) => close(body1)(nEnvVar) } '{ $sym.lam((x: R[Int => Int]) => ${given Env = envWith(i, 'x)(given env); lift(body2)}).asInstanceOf[R[T]] } case '{ Symantics.fix[$t, $u]($f) } => diff --git a/tests/run-macros/quote-matching-open/Macro_1.scala b/tests/run-macros/quote-matching-open/Macro_1.scala index a39ccf9a592e..30c8ac2697cb 100644 --- a/tests/run-macros/quote-matching-open/Macro_1.scala +++ b/tests/run-macros/quote-matching-open/Macro_1.scala @@ -1,14 +1,14 @@ import scala.quoted._ - +import scala.quoted.unsafe._ object Macro { inline def openTest(x: => Any): Any = ${ Macro.impl('x) } def impl(x: Expr[Any])(given QuoteContext): Expr[Any] = { x match { - case '{ (x: Int) => ($body: Int => Int)(x) } => Expr.open(body) { (body, close) => close(body)(Expr(2)) } - case '{ (x1: Int, x2: Int) => ($body: (Int, Int) => Int)(x1, x2) } => Expr.open(body) { (body, close) => close(body)(Expr(2), Expr(3)) } - case '{ (x1: Int, x2: Int, x3: Int) => ($body: (Int, Int, Int) => Int)(x1, x2, x3) } => Expr.open(body) { (body, close) => close(body)(Expr(2), Expr(3), Expr(4)) } + case '{ (x: Int) => ($body: Int => Int)(x) } => UnsafeExpr.open(body) { (body, close) => close(body)(Expr(2)) } + case '{ (x1: Int, x2: Int) => ($body: (Int, Int) => Int)(x1, x2) } => UnsafeExpr.open(body) { (body, close) => close(body)(Expr(2), Expr(3)) } + case '{ (x1: Int, x2: Int, x3: Int) => ($body: (Int, Int, Int) => Int)(x1, x2, x3) } => UnsafeExpr.open(body) { (body, close) => close(body)(Expr(2), Expr(3), Expr(4)) } } }