Skip to content

Commit 13bc498

Browse files
committed
Add let abstractions with customizable and fresh names
1 parent 082edca commit 13bc498

File tree

15 files changed

+210
-3
lines changed

15 files changed

+210
-3
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package dotty.tools.dotc.tastyreflect
2+
3+
import dotty.tools.dotc.ast.tpd
4+
import dotty.tools.dotc.core._
5+
import dotty.tools.dotc.core.Contexts._
6+
import dotty.tools.dotc.util.Property
7+
8+
object QuoteContextState {
9+
10+
val NextIndex = new Property.Key[scala.runtime.IntRef]
11+
12+
def init(ctx: Context): Context =
13+
ctx.fresh.setProperty(NextIndex, new scala.runtime.IntRef(0))
14+
15+
def nextIndex()(given ctx: Context): Int = {
16+
val ref = ctx.property(NextIndex).get
17+
val ret = ref.elem
18+
ref.elem = ret + 1
19+
ret
20+
}
21+
}
22+

compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import dotty.tools.dotc.core.Symbols._
1212
import dotty.tools.dotc.core.Decorators._
1313
import dotty.tools.dotc.core.Types.SingletonType
1414
import dotty.tools.dotc.tastyreflect.FromSymbol.{definitionFromSym, packageDefFromSym}
15+
import dotty.tools.dotc.tastyreflect.QuoteContextState
1516
import dotty.tools.dotc.typer.Implicits.{AmbiguousImplicits, DivergingImplicit, NoMatchingImplicits, SearchFailure, SearchFailureType}
1617
import dotty.tools.dotc.util.{SourceFile, SourcePosition, Spans}
1718

@@ -28,6 +29,8 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend
2829
def rootPosition: util.SourcePosition =
2930
tastyreflect.MacroExpansion.position.getOrElse(SourcePosition(rootContext.source, Spans.NoSpan))
3031

32+
def nextIndex(): Int = QuoteContextState.nextIndex()
33+
3134
//
3235
// QUOTE UNPICKLING
3336
//

compiler/src/dotty/tools/dotc/typer/Inliner.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1236,7 +1236,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
12361236
private def expandMacro(body: Tree, span: Span)(implicit ctx: Context) = {
12371237
assert(level == 0)
12381238
val inlinedFrom = enclosingInlineds.last
1239-
val ctx1 = tastyreflect.MacroExpansion.context(inlinedFrom)
1239+
val ctx1 = tastyreflect.QuoteContextState.init(tastyreflect.MacroExpansion.context(inlinedFrom))
12401240

12411241
val dependencies = macroDependencies(body)
12421242

docs/docs/reference/metaprogramming/macros.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,52 @@ while (i < arr.length) {
564564
}
565565
sum
566566
```
567+
### Showing meaningful definition names in quotes
568+
569+
In the `powerCode` example above there is a `'{ val y = $x * $x; ... }` which when printed
570+
may show several different `val y = ...`.
571+
572+
For example
573+
```scala
574+
powerCode(16, '{7}).show
575+
```
576+
will show
577+
```scala
578+
val y: scala.Double = 7
579+
val y: scala.Double = y.*(y)
580+
val y: scala.Double = y.*(y)
581+
val y: scala.Double = y.*(y)
582+
val y: scala.Double = y.*(y)
583+
y
584+
```
585+
Even though there is no hygiene issue it may be hard to undestand the code. To overcome this inconvenience
586+
each `y` can be assigned a meaningful name using the `scala.quoted.util.let`.
587+
For example `let("y" + i)('{ $x * $x })(yi => ...)` will assign to each `y` a name
588+
`y{i}` where `{i}` is a known String, if `i == 3` then it would be named `y3`.
589+
590+
The `powerCode` can be defined as follows using `showName`
591+
```scala
592+
def powerCodeD(n: Long, x: Expr[Double]))(given QuoteContext): Expr[Double] =
593+
let("y1")(x)(y1 => powerCodeD(n, 2, y1))
594+
595+
def powerCodeD(n: Long, i: Int, x: Expr[Double])(given QuoteContext): Expr[Double] =
596+
if (n == 0) '{1.0}
597+
else if (n % 2 == 0) let("y" + i)('{ $x * $x })(ai => powerCodeD(n / 2, idx * 2, yi) }
598+
else '{ $x * ${powerCodeD(n - 1, idx, x)} }
599+
```
600+
then
601+
```scala
602+
powerCodeD(16, '{7}).show
603+
```
604+
will show
605+
```scala
606+
val y1: scala.Double = 7
607+
val y2: scala.Double = y1.*(y1)
608+
val y4: scala.Double = y2.*(y2)
609+
val y8: scala.Double = y4.*(y4)
610+
val y16: scala.Double = y8.*(y8)
611+
a16
612+
```
567613

568614
### Find implicits within a macro
569615

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package scala.quoted
2+
3+
import scala.internal.quoted.showName
4+
5+
package object util {
6+
7+
/** Genrate the code `'{ val `<name>` = $expr; ${ body('<name>) } }` with the given name */
8+
def let[T: Type, U: Type](name: String)(expr: Expr[T])(body: Expr[T] => Expr[U])(given QuoteContext): Expr[U] = '{
9+
@showName(${Expr(name)}) val x = $expr
10+
${ body('x) }
11+
}
12+
13+
/** Genrate the code `'{ val x$<i> = $expr; ${ body('x$<i>) } }` for a fresh i */
14+
def let[T: Type, U: Type](expr: Expr[T])(body: Expr[T] => Expr[U])(given qctx: QuoteContext): Expr[U] =
15+
let("x$" + qctx.nextIndex())(expr)(body)
16+
17+
}

library/src/scala/quoted/QuoteContext.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ class QuoteContext(val tasty: scala.tasty.Reflection) {
4646
tasty.warning(msg, expr.unseal.pos)(given rootContext)
4747
}
4848

49+
/** Get a fresh index within the scope of this quote context.
50+
* Each `scala.quoted.staging.run` and macro expansion starts with the next index at 0.
51+
*/
52+
def nextIndex(): Int =
53+
tasty.internal.nextIndex()
54+
4955
}
5056

5157
object QuoteContext {

library/src/scala/tasty/reflect/CompilerInterface.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ trait CompilerInterface {
117117

118118
def settings: Settings
119119

120+
def nextIndex(): Int
121+
120122
//
121123
// QUOTE UNPICKLING
122124
//

staging/src/scala/quoted/staging/QuoteDriver.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package staging
44
import dotty.tools.dotc.ast.tpd
55
import dotty.tools.dotc.Driver
66
import dotty.tools.dotc.core.Contexts.{Context, ContextBase, FreshContext}
7-
import dotty.tools.dotc.tastyreflect.ReflectionImpl
7+
import dotty.tools.dotc.tastyreflect.{ReflectionImpl, QuoteContextState}
88
import dotty.tools.io.{AbstractFile, Directory, PlainDirectory, VirtualDirectory}
99
import dotty.tools.repl.AbstractFileClassLoader
1010
import dotty.tools.dotc.reporting._
@@ -32,7 +32,7 @@ private class QuoteDriver(appClassloader: ClassLoader) extends Driver {
3232
}
3333

3434
val (_, ctx0: Context) = setup(settings.compilerArgs.toArray :+ "dummy.scala", initCtx.fresh)
35-
val ctx = setToolboxSettings(ctx0.fresh.setSetting(ctx0.settings.outputDir, outDir), settings)
35+
val ctx = setToolboxSettings(QuoteContextState.init(ctx0).fresh.setSetting(ctx0.settings.outputDir, outDir), settings)
3636

3737
new QuoteCompiler().newRun(ctx).compileExpr(exprBuilder) match {
3838
case Right(value) =>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
Test.x.*({
2+
val x$0: scala.Double = Test.x.*(Test.x)
3+
val x$1: scala.Double = x$0.*(x$0)
4+
x$1.*({
5+
val x$2: scala.Double = x$1.*(x$1)
6+
x$2.*({
7+
val x$3: scala.Double = x$2.*(x$2)
8+
val x$4: scala.Double = x$3.*(x$3)
9+
val x$5: scala.Double = x$4.*(x$4)
10+
x$5
11+
})
12+
})
13+
})
14+
Test.x.*({
15+
val x$0: scala.Double = Test.x.*(Test.x)
16+
x$0.*({
17+
val x$1: scala.Double = x$0.*(x$0)
18+
val x$2: scala.Double = x$1.*(x$1)
19+
val x$3: scala.Double = x$2.*(x$2)
20+
val x$4: scala.Double = x$3.*(x$3)
21+
x$4
22+
})
23+
})
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import scala.quoted._
2+
import scala.quoted.util.let
3+
4+
inline def power(x: Double, inline n: Long): String =
5+
${ powerCodeShow('x, n) }
6+
7+
private def powerCodeShow(x: Expr[Double], n: Long)(given QuoteContext): Expr[String] =
8+
Expr(powerCode(n, 2, x).show)
9+
10+
private def powerCode(n: Long, idx: Int, x: Expr[Double])(given QuoteContext): Expr[Double] =
11+
if (n == 0) '{1.0}
12+
else if (n == 1) x
13+
else if (n % 2 == 0) let('{ $x * $x })(y => powerCode(n / 2, idx * 2, y))
14+
else '{ $x * ${powerCode(n - 1, idx, x)} }
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
3+
4+
object Test extends App {
5+
val x = 4.5
6+
println(power(x, 77))
7+
println(power(x, 35))
8+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
((x: scala.Double) => x.*({
2+
val x$0: scala.Double = x.*(x)
3+
val x$1: scala.Double = x$0.*(x$0)
4+
x$1.*({
5+
val x$2: scala.Double = x$1.*(x$1)
6+
x$2.*({
7+
val x$3: scala.Double = x$2.*(x$2)
8+
val x$4: scala.Double = x$3.*(x$3)
9+
val x$5: scala.Double = x$4.*(x$4)
10+
x$5
11+
})
12+
})
13+
}))
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import scala.quoted._
2+
import scala.quoted.util.let
3+
import scala.quoted.staging._
4+
5+
object Test {
6+
given Toolbox = Toolbox.make(getClass.getClassLoader)
7+
def main(args: Array[String]): Unit = withQuoteContext {
8+
println(powerCode(77).show)
9+
}
10+
11+
def powerCode(n: Long)(given QuoteContext): Expr[Double => Double] =
12+
'{ x => ${powerCode(n, 2, 'x)} }
13+
14+
def powerCode(n: Long, idx: Int, x: Expr[Double])(given QuoteContext): Expr[Double] =
15+
if (n == 0) '{1.0}
16+
else if (n == 1) x
17+
else if (n % 2 == 0) let('{ $x * $x })(y => powerCode(n / 2, idx * 2, y))
18+
else '{ $x * ${powerCode(n - 1, idx, x)} }
19+
20+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
((x1: scala.Double) => x1.*({
2+
val x2: scala.Double = x1.*(x1)
3+
val x4: scala.Double = x2.*(x2)
4+
x4.*({
5+
val x8: scala.Double = x4.*(x4)
6+
x8.*({
7+
val x16: scala.Double = x8.*(x8)
8+
val x32: scala.Double = x16.*(x16)
9+
val x64: scala.Double = x32.*(x32)
10+
x64
11+
})
12+
})
13+
}))
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import scala.quoted._
2+
import scala.quoted.util.let
3+
import scala.quoted.staging._
4+
5+
object Test {
6+
given Toolbox = Toolbox.make(getClass.getClassLoader)
7+
def main(args: Array[String]): Unit = withQuoteContext {
8+
println(powerCode(77).show)
9+
}
10+
11+
def powerCode(n: Long)(given QuoteContext): Expr[Double => Double] =
12+
'{ x1 => ${powerCode(n, 2, 'x1)} }
13+
14+
def powerCode(n: Long, idx: Int, x: Expr[Double])(given QuoteContext): Expr[Double] =
15+
if (n == 0) '{1.0}
16+
else if (n == 1) x
17+
else if (n % 2 == 0) let(s"x$idx")('{ $x * $x })(y => powerCode(n / 2, idx * 2, y))
18+
else '{ $x * ${powerCode(n - 1, idx, x)} }
19+
20+
}

0 commit comments

Comments
 (0)