Skip to content

Commit a8b10ef

Browse files
committed
Allow to beta reduce curried function applications in quotes reflect
Previously, the curried functions with multiple applications were not able to be beta-reduced in any way, which was unexpected. Now we allow reducing any number of top-level function applications for a curried function. This was also made clearer in the documentation for the affected (Expr.betaReduce and Term.betaReduce) methods.
1 parent cdc9fe8 commit a8b10ef

File tree

5 files changed

+158
-10
lines changed

5 files changed

+158
-10
lines changed

compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala

+12
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,18 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
378378
for e <- betaReduce(expr) yield tpd.cpy.Block(tree)(Nil, e)
379379
case tpd.Inlined(_, Nil, expr) =>
380380
betaReduce(expr)
381+
case tpd.Apply(tpd.Select(expr: Apply, nme), args) =>
382+
betaReduce(expr).map { expr1 =>
383+
dotc.transform.BetaReduce(
384+
tpd.Apply(tpd.Select(expr1, nme), args)
385+
).withSpan(tree.span)
386+
}
387+
case tpd.Apply(tpd.TypeApply(tpd.Select(expr: Apply, nme), tpts), args) =>
388+
betaReduce(expr).map { expr1 =>
389+
dotc.transform.BetaReduce(
390+
tpd.Apply(tpd.TypeApply(tpd.Select(expr1, nme), tpts), args)
391+
).withSpan(tree.span)
392+
}
381393
case _ =>
382394
val tree1 = dotc.transform.BetaReduce(tree)
383395
if tree1 eq tree then None

library/src/scala/quoted/Expr.scala

+27-5
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,34 @@ abstract class Expr[+T] private[scala] ()
1010
object Expr {
1111

1212
/** `e.betaReduce` returns an expression that is functionally equivalent to `e`,
13-
* however if `e` is of the form `((y1, ..., yn) => e2)(e1, ..., en)`
14-
* then it optimizes this the top most call by returning the result of beta-reducing the application.
15-
* Otherwise returns `expr`.
13+
* however if `e` is of the form `((y1, ..., yn) => e2)(e1, ..., en)`
14+
* then it optimizes the top most call by returning the result of beta-reducing the application.
15+
* Similarly, all outermost curried function applications will be beta-reduced, if possible.
16+
* Otherwise returns `expr`.
1617
*
17-
* To retain semantics the argument `ei` is bound as `val yi = ei` and by-name arguments to `def yi = ei`.
18-
* Some bindings may be elided as an early optimization.
18+
* To retain semantics the argument `ei` is bound as `val yi = ei` and by-name arguments to `def yi = ei`.
19+
* Some bindings may be elided as an early optimization.
20+
*
21+
* Example:
22+
* ```scala sc:nocompile
23+
* ([X1, Y1, ...] => (x1, y1, ...) => ... => [Xn, Yn, ...] => (xn, yn, ...) => f[X1, Y1, ..., Xn, Yn, ...](x1, y1, ..., xn, yn, ...))).apply[Tx1, Ty1, ...](myX1, myY1, ...)....apply[Txn, Tyn, ...](myXn, myYn, ...)
24+
* ```
25+
* will be reduced to
26+
* ```scala sc:nocompile
27+
* type X1 = Tx1
28+
* type Y1 = Ty1
29+
* ...
30+
* val x1 = myX1
31+
* val y1 = myY1
32+
* ...
33+
* type Xn = Txn
34+
* type Yn = Tyn
35+
* ...
36+
* val xn = myXn
37+
* val yn = myYn
38+
* ...
39+
* f[X1, Y1, ..., Xn, Yn, ...](x1, y1, ..., xn, yn, ...)
40+
* ```
1941
*/
2042
def betaReduce[T](expr: Expr[T])(using Quotes): Expr[T] =
2143
import quotes.reflect._

library/src/scala/quoted/Quotes.scala

+28-5
Original file line numberDiff line numberDiff line change
@@ -751,14 +751,37 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
751751
/** Methods of the module object `val Term` */
752752
trait TermModule { this: Term.type =>
753753

754-
/** Returns a term that is functionally equivalent to `t`,
754+
/** Returns a term that is functionally equivalent to `t`,
755755
* however if `t` is of the form `((y1, ..., yn) => e2)(e1, ..., en)`
756-
* then it optimizes this the top most call by returning the `Some`
757-
* with the result of beta-reducing the application.
756+
* then it optimizes the top most call by returning the `Some`
757+
* with the result of beta-reducing the function application.
758+
* Similarly, all outermost curried function applications will be
759+
* beta-reduced, if possible.
758760
* Otherwise returns `None`.
759761
*
760-
* To retain semantics the argument `ei` is bound as `val yi = ei` and by-name arguments to `def yi = ei`.
761-
* Some bindings may be elided as an early optimization.
762+
* To retain semantics the argument `ei` is bound as `val yi = ei` and by-name arguments to `def yi = ei`.
763+
* Some bindings may be elided as an early optimization.
764+
*
765+
* Example:
766+
* ```scala sc:nocompile
767+
* ([X1, Y1, ...] => (x1, y1, ...) => ... => [Xn, Yn, ...] => (xn, yn, ...) => f[X1, Y1, ..., Xn, Yn, ...](x1, y1, ..., xn, yn, ...))).apply[Tx1, Ty1, ...](myX1, myY1, ...)....apply[Txn, Tyn, ...](myXn, myYn, ...)
768+
* ```
769+
* will be reduced to
770+
* ```scala sc:nocompile
771+
* type X1 = Tx1
772+
* type Y1 = Ty1
773+
* ...
774+
* val x1 = myX1
775+
* val y1 = myY1
776+
* ...
777+
* type Xn = Txn
778+
* type Yn = Tyn
779+
* ...
780+
* val xn = myXn
781+
* val yn = myYn
782+
* ...
783+
* f[X1, Y1, ..., Xn, Yn, ...](x1, y1, ..., xn, yn, ...)
784+
* ```
762785
*/
763786
def betaReduce(term: Term): Option[Term]
764787

tests/pos-macros/i17506/Macro.scala

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import scala.quoted._
2+
3+
def assertBetaReduction(using Quotes)(applied: Expr[Any], expected: String): quotes.reflect.Term =
4+
import quotes.reflect._
5+
val reducedMaybe = Term.betaReduce(applied.asTerm)
6+
assert(reducedMaybe.isDefined)
7+
val reduced = reducedMaybe.get
8+
assert(reduced.show == expected)
9+
reduced
10+
11+
inline def regularCurriedCtxFun2BetaReduceTest(inline f: Foo ?=> Bar ?=> Int): Unit =
12+
${regularCurriedCtxFun2BetaReduceTestImpl('f)}
13+
def regularCurriedCtxFun2BetaReduceTestImpl(f: Expr[Foo ?=> Bar ?=> Int])(using Quotes): Expr[Int] =
14+
val expected =
15+
"""|{
16+
| val evidence$3: Bar = new Bar()
17+
| val evidence$2: Foo = new Foo()
18+
| 123
19+
|}""".stripMargin
20+
val applied = '{$f(using new Foo())(using new Bar())}
21+
assertBetaReduction(applied, expected).asExprOf[Int]
22+
23+
inline def regularCurriedFun2BetaReduceTest(inline f: Foo => Bar => Int): Int =
24+
${regularCurriedFun2BetaReduceTestImpl('f)}
25+
def regularCurriedFun2BetaReduceTestImpl(f: Expr[Foo => Bar => Int])(using Quotes): Expr[Int] =
26+
val expected =
27+
"""|{
28+
| val b: Bar = new Bar()
29+
| val f: Foo = new Foo()
30+
| 123
31+
|}""".stripMargin
32+
val applied = '{$f(new Foo())(new Bar())}
33+
assertBetaReduction(applied, expected).asExprOf[Int]
34+
35+
inline def typeParamCurriedFun2BetaReduceTest(inline f: [A] => A => [B] => B => Unit): Unit =
36+
${typeParamCurriedFun2BetaReduceTestImpl('f)}
37+
def typeParamCurriedFun2BetaReduceTestImpl(f: Expr[[A] => (a: A) => [B] => (b: B) => Unit])(using Quotes): Expr[Unit] =
38+
val expected =
39+
"""|{
40+
| type Y = Bar
41+
| val y: Bar = new Bar()
42+
| type X = Foo
43+
| val x: Foo = new Foo()
44+
| typeParamFun2[Y, X](y, x)
45+
|}""".stripMargin
46+
val applied = '{$f.apply[Foo](new Foo()).apply[Bar](new Bar())}
47+
assertBetaReduction(applied, expected).asExprOf[Unit]
48+
49+
inline def regularCurriedFun3BetaReduceTest(inline f: Foo => Bar => Baz => Int): Int =
50+
${regularCurriedFun3BetaReduceTestImpl('f)}
51+
def regularCurriedFun3BetaReduceTestImpl(f: Expr[Foo => Bar => Baz => Int])(using Quotes): Expr[Int] =
52+
val expected =
53+
"""|{
54+
| val i: Baz = new Baz()
55+
| val b: Bar = new Bar()
56+
| val f: Foo = new Foo()
57+
| 123
58+
|}""".stripMargin
59+
val applied = '{$f(new Foo())(new Bar())(new Baz())}
60+
assertBetaReduction(applied, expected).asExprOf[Int]
61+
62+
inline def typeParamCurriedFun3BetaReduceTest(inline f: [A] => A => [B] => B => [C] => C => Unit): Unit =
63+
${typeParamCurriedFun3BetaReduceTestImpl('f)}
64+
def typeParamCurriedFun3BetaReduceTestImpl(f: Expr[[A] => A => [B] => B => [C] => C => Unit])(using Quotes): Expr[Unit] =
65+
val expected =
66+
"""|{
67+
| type Z = Baz
68+
| val z: Baz = new Baz()
69+
| type Y = Bar
70+
| val y: Bar = new Bar()
71+
| type X = Foo
72+
| val x: Foo = new Foo()
73+
| typeParamFun3[Z, Y, X](z, y, x)
74+
|}""".stripMargin
75+
val applied = '{$f.apply[Foo](new Foo()).apply[Bar](new Bar()).apply[Baz](new Baz())}
76+
assertBetaReduction(applied, expected).asExprOf[Unit]

tests/pos-macros/i17506/Test.scala

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class Foo
2+
class Bar
3+
class Baz
4+
5+
@main def run() =
6+
def typeParamFun2[A, B](a: A, b: B): Unit = println(a.toString + " " + b.toString)
7+
def typeParamFun3[A, B, C](a: A, b: B, c: C): Unit = println(a.toString + " " + b.toString)
8+
9+
regularCurriedCtxFun2BetaReduceTest((f: Foo) ?=> (b: Bar) ?=> 123)
10+
regularCurriedCtxFun2BetaReduceTest(123)
11+
regularCurriedFun2BetaReduceTest(((f: Foo) => (b: Bar) => 123))
12+
typeParamCurriedFun2BetaReduceTest([X] => (x: X) => [Y] => (y: Y) => typeParamFun2[Y, X](y, x))
13+
14+
regularCurriedFun3BetaReduceTest((f: Foo) => (b: Bar) => (i: Baz) => 123)
15+
typeParamCurriedFun3BetaReduceTest([X] => (x: X) => [Y] => (y: Y) => [Z] => (z: Z) => typeParamFun3[Z, Y, X](z, y, x))

0 commit comments

Comments
 (0)