Skip to content

Commit 7bafca1

Browse files
Merge pull request #8041 from dotty-staging/move-unsafe-operations-out-of-expr
Move unsafe operations from Expr to UnsafeExpr
2 parents b8d5be5 + 9185dc4 commit 7bafca1

File tree

7 files changed

+90
-67
lines changed

7 files changed

+90
-67
lines changed

library/src/scala/quoted/Expr.scala

Lines changed: 0 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,6 @@ class Expr[+T] private[scala] {
2929
final def matches(that: Expr[Any])(given qctx: QuoteContext): Boolean =
3030
!scala.internal.quoted.Expr.unapply[Unit, Unit](this)(given that, false, qctx).isEmpty
3131

32-
/** Returns the undelying argument that was in the call before inlining.
33-
*
34-
* ```
35-
* inline foo(x: Int): Int = baz(x, x)
36-
* foo(bar())
37-
* ```
38-
* is inlined as
39-
* ```
40-
* val x = bar()
41-
* baz(x, x)
42-
* ```
43-
* in this case the undelying argument of `x` will be `bar()`.
44-
*
45-
* Warning: Using the undelying argument directly in the expansion of a macro may change the parameter
46-
* semantics from by-value to by-name.
47-
*/
48-
def underlyingArgument(given qctx: QuoteContext): Expr[T] = {
49-
import qctx.tasty.{given, _}
50-
this.unseal.underlyingArgument.seal.asInstanceOf[Expr[T]]
51-
}
5232
}
5333

5434
object Expr {
@@ -201,40 +181,4 @@ object Expr {
201181
ofTuple(elems).cast[Tuple.InverseMap[T, Expr]]
202182
}
203183

204-
// TODO generalize for any function arity (see Expr.betaReduce)
205-
def open[T1, R, X](f: Expr[T1 => R])(content: (Expr[R], [t] => Expr[t] => Expr[T1] => Expr[t]) => X)(given qctx: QuoteContext): X = {
206-
import qctx.tasty.{given, _}
207-
val (params, bodyExpr) = paramsAndBody(f)
208-
content(bodyExpr, [t] => (e: Expr[t]) => (v: Expr[T1]) => bodyFn[t](e.unseal, params, List(v.unseal)).seal.asInstanceOf[Expr[t]])
209-
}
210-
211-
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 = {
212-
import qctx.tasty.{given, _}
213-
val (params, bodyExpr) = paramsAndBody(f)
214-
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]])
215-
}
216-
217-
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 = {
218-
import qctx.tasty.{given, _}
219-
val (params, bodyExpr) = paramsAndBody(f)
220-
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]])
221-
}
222-
223-
private def paramsAndBody[R](given qctx: QuoteContext)(f: Expr[Any]) = {
224-
import qctx.tasty.{given, _}
225-
val Block(List(DefDef("$anonfun", Nil, List(params), _, Some(body))), Closure(Ident("$anonfun"), None)) = f.unseal.etaExpand
226-
(params, body.seal.asInstanceOf[Expr[R]])
227-
}
228-
229-
private def bodyFn[t](given qctx: QuoteContext)(e: qctx.tasty.Term, params: List[qctx.tasty.ValDef], args: List[qctx.tasty.Term]): qctx.tasty.Term = {
230-
import qctx.tasty.{given, _}
231-
val map = params.map(_.symbol).zip(args).toMap
232-
new TreeMap {
233-
override def transformTerm(tree: Term)(given ctx: Context): Term =
234-
super.transformTerm(tree) match
235-
case tree: Ident => map.getOrElse(tree.symbol, tree)
236-
case tree => tree
237-
}.transformTerm(e)
238-
}
239-
240184
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package scala.quoted
2+
package unsafe
3+
4+
object UnsafeExpr {
5+
6+
/** Returns the undelying argument that was in the call before inlining.
7+
*
8+
* ```
9+
* inline foo(x: Int): Int = baz(x, x)
10+
* foo(bar())
11+
* ```
12+
* is inlined as
13+
* ```
14+
* val x = bar()
15+
* baz(x, x)
16+
* ```
17+
* in this case the undelying argument of `x` will be `bar()`.
18+
*
19+
* Warning: Using the undelying argument directly in the expansion of a macro may
20+
* change the parameter semantics as by-value parameter could be re-evaluated.
21+
*/
22+
def underlyingArgument[T](expr: Expr[T])(given qctx: QuoteContext): Expr[T] = {
23+
import qctx.tasty.{given, _}
24+
expr.unseal.underlyingArgument.seal.asInstanceOf[Expr[T]]
25+
}
26+
27+
// TODO generalize for any function arity (see Expr.betaReduce)
28+
/** Allows inspection or transformation of the body of the expression of function.
29+
* This body may have references to the arguments of the function which should be closed
30+
* over if the expression will be spliced.
31+
*
32+
* ```
33+
* val f: Expr[T => R] = ...
34+
* UnsafeExpr.open(f) { (body, close) =>
35+
* val newParam: Expr[T] = ...
36+
* ...
37+
* close(body)(newParam) // body or part of the body
38+
* }
39+
* ```
40+
*/
41+
def open[T1, R, X](f: Expr[T1 => R])(content: (Expr[R], [t] => Expr[t] => Expr[T1] => Expr[t]) => X)(given qctx: QuoteContext): X = {
42+
import qctx.tasty.{given, _}
43+
val (params, bodyExpr) = paramsAndBody(f)
44+
content(bodyExpr, [t] => (e: Expr[t]) => (v: Expr[T1]) => bodyFn[t](e.unseal, params, List(v.unseal)).seal.asInstanceOf[Expr[t]])
45+
}
46+
47+
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 = {
48+
import qctx.tasty.{given, _}
49+
val (params, bodyExpr) = paramsAndBody(f)
50+
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]])
51+
}
52+
53+
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 = {
54+
import qctx.tasty.{given, _}
55+
val (params, bodyExpr) = paramsAndBody(f)
56+
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]])
57+
}
58+
59+
private def paramsAndBody[R](given qctx: QuoteContext)(f: Expr[Any]) = {
60+
import qctx.tasty.{given, _}
61+
val Block(List(DefDef("$anonfun", Nil, List(params), _, Some(body))), Closure(Ident("$anonfun"), None)) = f.unseal.etaExpand
62+
(params, body.seal.asInstanceOf[Expr[R]])
63+
}
64+
65+
private def bodyFn[t](given qctx: QuoteContext)(e: qctx.tasty.Term, params: List[qctx.tasty.ValDef], args: List[qctx.tasty.Term]): qctx.tasty.Term = {
66+
import qctx.tasty.{given, _}
67+
val map = params.map(_.symbol).zip(args).toMap
68+
new TreeMap {
69+
override def transformTerm(tree: Term)(given ctx: Context): Term =
70+
super.transformTerm(tree) match
71+
case tree: Ident => map.getOrElse(tree.symbol, tree)
72+
case tree => tree
73+
}.transformTerm(e)
74+
}
75+
}

tests/run-macros/i7898/Macro_1.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import quoted._
2+
import quoted.unsafe._
23
object Main {
34

45
def myMacroImpl(body: Expr[_])(given qctx: QuoteContext): Expr[_] = {
56
import qctx.tasty.{_, given}
6-
val bodyTerm = body.underlyingArgument.unseal
7+
val bodyTerm = UnsafeExpr.underlyingArgument(body).unseal
78
val showed = bodyTerm.show
89
'{
910
println(${Expr(showed)})

tests/run-macros/quote-matcher-symantics-2/quoted_1.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import scala.quoted._
22
import scala.quoted.matching._
3+
import scala.quoted.unsafe._
34

45
object Macros {
56

@@ -22,7 +23,7 @@ object Macros {
2223
case '{ ($f: DSL => DSL)($x: DSL) } => sym.app(liftFun(f), lift(x))
2324

2425
case '{ val x: DSL = $value; ($bodyFn: DSL => DSL)(x) } =>
25-
Expr.open(bodyFn) { (body1, close) =>
26+
UnsafeExpr.open(bodyFn) { (body1, close) =>
2627
val (i, nEnvVar) = freshEnvVar()
2728
lift(close(body1)(nEnvVar))(env + (i -> lift(value)))
2829
}
@@ -38,7 +39,7 @@ object Macros {
3839
def liftFun(e: Expr[DSL => DSL])(implicit env: Map[Int, Expr[T]]): Expr[T => T] = e match {
3940
case '{ (x: DSL) => ($bodyFn: DSL => DSL)(x) } =>
4041
sym.lam((y: Expr[T]) =>
41-
Expr.open(bodyFn) { (body1, close) =>
42+
UnsafeExpr.open(bodyFn) { (body1, close) =>
4243
val (i, nEnvVar) = freshEnvVar()
4344
lift(close(body1)(nEnvVar))(env + (i -> y))
4445
}

tests/run-macros/quote-matcher-symantics-3/quoted_1.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import scala.quoted._
22
import scala.quoted.matching._
3+
import scala.quoted.unsafe._
34

45
object Macros {
56

@@ -46,17 +47,17 @@ object Macros {
4647

4748
case '{ (x0: Int) => ($bodyFn: Int => Any)(x0) } =>
4849
val (i, nEnvVar) = freshEnvVar[Int]()
49-
val body2 = Expr.open(bodyFn) { (body1, close) => close(body1)(nEnvVar) }
50+
val body2 = UnsafeExpr.open(bodyFn) { (body1, close) => close(body1)(nEnvVar) }
5051
'{ $sym.lam((x: R[Int]) => ${given Env = envWith(i, 'x)(given env); lift(body2)}).asInstanceOf[R[T]] }
5152

5253
case '{ (x0: Boolean) => ($bodyFn: Boolean => Any)(x0) } =>
5354
val (i, nEnvVar) = freshEnvVar[Boolean]()
54-
val body2 = Expr.open(bodyFn) { (body1, close) => close(body1)(nEnvVar) }
55+
val body2 = UnsafeExpr.open(bodyFn) { (body1, close) => close(body1)(nEnvVar) }
5556
'{ $sym.lam((x: R[Boolean]) => ${given Env = envWith(i, 'x)(given env); lift(body2)}).asInstanceOf[R[T]] }
5657

5758
case '{ (x0: Int => Int) => ($bodyFn: (Int => Int) => Any)(x0) } =>
5859
val (i, nEnvVar) = freshEnvVar[Int => Int]()
59-
val body2 = Expr.open(bodyFn) { (body1, close) => close(body1)(nEnvVar) }
60+
val body2 = UnsafeExpr.open(bodyFn) { (body1, close) => close(body1)(nEnvVar) }
6061
'{ $sym.lam((x: R[Int => Int]) => ${given Env = envWith(i, 'x)(given env); lift(body2)}).asInstanceOf[R[T]] }
6162

6263
case '{ Symantics.fix[$t, $u]($f) } =>

tests/run-macros/quote-matching-open/Macro_1.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import scala.quoted._
2-
2+
import scala.quoted.unsafe._
33
object Macro {
44

55
inline def openTest(x: => Any): Any = ${ Macro.impl('x) }
66

77
def impl(x: Expr[Any])(given QuoteContext): Expr[Any] = {
88
x match {
9-
case '{ (x: Int) => ($body: Int => Int)(x) } => Expr.open(body) { (body, close) => close(body)(Expr(2)) }
10-
case '{ (x1: Int, x2: Int) => ($body: (Int, Int) => Int)(x1, x2) } => Expr.open(body) { (body, close) => close(body)(Expr(2), Expr(3)) }
11-
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)) }
9+
case '{ (x: Int) => ($body: Int => Int)(x) } => UnsafeExpr.open(body) { (body, close) => close(body)(Expr(2)) }
10+
case '{ (x1: Int, x2: Int) => ($body: (Int, Int) => Int)(x1, x2) } => UnsafeExpr.open(body) { (body, close) => close(body)(Expr(2), Expr(3)) }
11+
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)) }
1212
}
1313
}
1414

tests/run-macros/quoted-matching-docs/Macro_1.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import scala.quoted._
22
import scala.quoted.matching._
3+
import scala.quoted.unsafe._
34

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

@@ -10,7 +11,7 @@ private def sumExprShow(argsExpr: Expr[Seq[Int]])(given QuoteContext): Expr[Stri
1011

1112
private def sumExpr(argsExpr: Expr[Seq[Int]])(given qctx: QuoteContext): Expr[Int] = {
1213
import qctx.tasty.{given, _}
13-
argsExpr.underlyingArgument match {
14+
UnsafeExpr.underlyingArgument(argsExpr) match {
1415
case ConstSeq(args) => // args is of type Seq[Int]
1516
Expr(args.sum) // precompute result of sum
1617
case ExprSeq(argExprs) => // argExprs is of type Seq[Expr[Int]]

0 commit comments

Comments
 (0)