Skip to content

Move unsafe operations from Expr to UnsafeExpr #8041

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
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
56 changes: 0 additions & 56 deletions library/src/scala/quoted/Expr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -201,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)
}

}
75 changes: 75 additions & 0 deletions library/src/scala/quoted/unsafe/UnsafeExpr.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
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]]
}

// 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)
}
}
3 changes: 2 additions & 1 deletion tests/run-macros/i7898/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -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)})
Expand Down
5 changes: 3 additions & 2 deletions tests/run-macros/quote-matcher-symantics-2/quoted_1.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import scala.quoted._
import scala.quoted.matching._
import scala.quoted.unsafe._

object Macros {

Expand All @@ -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)))
}
Expand All @@ -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))
}
Expand Down
7 changes: 4 additions & 3 deletions tests/run-macros/quote-matcher-symantics-3/quoted_1.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import scala.quoted._
import scala.quoted.matching._
import scala.quoted.unsafe._

object Macros {

Expand Down Expand Up @@ -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) } =>
Expand Down
8 changes: 4 additions & 4 deletions tests/run-macros/quote-matching-open/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -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)) }
}
}

Expand Down
3 changes: 2 additions & 1 deletion tests/run-macros/quoted-matching-docs/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import scala.quoted._
import scala.quoted.matching._
import scala.quoted.unsafe._

inline def sum(args: Int*): Int = ${ sumExpr('args) }

Expand All @@ -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]]
Expand Down