Skip to content

Commit 9dc1087

Browse files
committed
Add scala.quoted.withNewQuoteContext
This provides a way to materialize a QuoteContext without calling `run` and hence performing the full compilation. It is useful if some user only wants to show or inspect an expression without evaluating it. Added a general optimization where if the expression passed to `run` is a known literal, the literal is returned directly without compiling the code.
1 parent ab8a677 commit 9dc1087

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+206
-150
lines changed

compiler/src/dotty/tools/dotc/quoted/QuoteCompiler.scala

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,18 @@ import dotty.tools.dotc.util.Spans.Span
2020
import dotty.tools.dotc.util.SourceFile
2121
import dotty.tools.io.{Path, VirtualFile}
2222

23-
import scala.quoted.{Expr, Type, QuoteContext}
23+
import scala.annotation.tailrec
24+
import scala.concurrent.Promise
25+
import scala.quoted.{Expr, QuoteContext, Type}
2426

2527
/** Compiler that takes the contents of a quoted expression `expr` and produces
2628
* a class file with `class ' { def apply: Object = expr }`.
2729
*/
2830
class QuoteCompiler extends Compiler {
2931

32+
/** Either `Left` with name of the classfile generated or `Right` with the value contained in the expression */
33+
private[this] var result: Either[String, Any] = null
34+
3035
override protected def frontendPhases: List[List[Phase]] =
3136
List(List(new QuotedFrontend))
3237

@@ -44,14 +49,36 @@ class QuoteCompiler extends Compiler {
4449
class QuotedFrontend extends Phase {
4550
import tpd._
4651

52+
4753
def phaseName: String = "quotedFrontend"
4854

4955
override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = {
50-
units.map {
56+
units.flatMap {
5157
case exprUnit: ExprCompilationUnit =>
52-
val tree = inClass(exprUnit.exprBuilder)
53-
val source = SourceFile.virtual("<quoted.Expr>", "")
54-
CompilationUnit(source, tree, forceTrees = true)
58+
val pos = Span(0)
59+
val assocFile = new VirtualFile("<quote>")
60+
61+
// Places the contents of expr in a compilable tree for a class with the following format.
62+
// `package __root__ { class ' { def apply: Any = <expr> } }`
63+
val cls = ctx.newCompleteClassSymbol(defn.RootClass, outputClassName, EmptyFlags,
64+
defn.ObjectType :: Nil, newScope, coord = pos, assocFile = assocFile).entered.asClass
65+
cls.enter(ctx.newDefaultConstructor(cls), EmptyScope)
66+
val meth = ctx.newSymbol(cls, nme.apply, Method, ExprType(defn.AnyType), coord = pos).entered
67+
68+
val quoted = PickledQuotes.quotedExprToTree(checkValidRunExpr(exprUnit.exprBuilder.apply(new QuoteContext(ReflectionImpl(ctx)))))(ctx.withOwner(meth))
69+
70+
getLiteral(quoted) match {
71+
case Some(value) =>
72+
result = Right(value)
73+
None // Stop copilation here we already have the result
74+
case None =>
75+
val run = DefDef(meth, quoted)
76+
val classTree = ClassDef(cls, DefDef(cls.primaryConstructor.asTerm), run :: Nil)
77+
val tree = PackageDef(ref(defn.RootPackage).asInstanceOf[Ident], classTree :: Nil).withSpan(pos)
78+
val source = SourceFile.virtual("<quoted.Expr>", "")
79+
result = Left(outputClassName.toString)
80+
Some(CompilationUnit(source, tree, forceTrees = true))
81+
}
5582
}
5683
}
5784

@@ -61,33 +88,25 @@ class QuoteCompiler extends Compiler {
6188
case _ => expr
6289
}
6390

64-
/** Places the contents of expr in a compilable tree for a class
65-
* with the following format.
66-
* `package __root__ { class ' { def apply: Any = <expr> } }`
67-
*/
68-
private def inClass(exprBuilder: QuoteContext => Expr[_])(implicit ctx: Context): Tree = {
69-
val pos = Span(0)
70-
val assocFile = new VirtualFile("<quote>")
71-
72-
val cls = ctx.newCompleteClassSymbol(defn.RootClass, outputClassName, EmptyFlags,
73-
defn.ObjectType :: Nil, newScope, coord = pos, assocFile = assocFile).entered.asClass
74-
cls.enter(ctx.newDefaultConstructor(cls), EmptyScope)
75-
val meth = ctx.newSymbol(cls, nme.apply, Method, ExprType(defn.AnyType), coord = pos).entered
76-
77-
val quoted = PickledQuotes.quotedExprToTree(checkValidRunExpr(exprBuilder.apply(new QuoteContext(ReflectionImpl(ctx)))))(ctx.withOwner(meth))
78-
79-
val run = DefDef(meth, quoted)
80-
val classTree = ClassDef(cls, DefDef(cls.primaryConstructor.asTerm), run :: Nil)
81-
PackageDef(ref(defn.RootPackage).asInstanceOf[Ident], classTree :: Nil).withSpan(pos)
91+
/** Get the literal value if this tree only contains a literal tree */
92+
@tailrec private def getLiteral(tree: Tree): Option[Any] = tree match {
93+
case Literal(lit) => Some(lit.value)
94+
case Block(Nil, expr) => getLiteral(expr)
95+
case Inlined(_, Nil, expr) => getLiteral(expr)
96+
case _ => None
8297
}
8398

8499
def run(implicit ctx: Context): Unit = unsupported("run")
85100
}
86101

87-
class ExprRun(comp: Compiler, ictx: Context) extends Run(comp, ictx) {
88-
def compileExpr(exprBuilder: QuoteContext => Expr[_]): Unit = {
102+
class ExprRun(comp: QuoteCompiler, ictx: Context) extends Run(comp, ictx) {
103+
/** Unpickle and optionally compile the expression.
104+
* Returns either `Left` with name of the classfile generated or `Right` with the value contained in the expression.
105+
*/
106+
def compileExpr(exprBuilder: QuoteContext => Expr[_]): Either[String, Any] = {
89107
val units = new ExprCompilationUnit(exprBuilder) :: Nil
90108
compileUnits(units)
109+
result
91110
}
92111
}
93112
}

compiler/src/dotty/tools/dotc/quoted/QuoteDriver.scala

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,21 @@ class QuoteDriver(appClassloader: ClassLoader) extends Driver {
3333
val (_, ctx0: Context) = setup(settings.compilerArgs.toArray :+ "dummy.scala", initCtx.fresh)
3434
val ctx = setToolboxSettings(ctx0.fresh.setSetting(ctx0.settings.outputDir, outDir), settings)
3535

36-
val driver = new QuoteCompiler
37-
driver.newRun(ctx).compileExpr(exprBuilder)
36+
new QuoteCompiler().newRun(ctx).compileExpr(exprBuilder) match {
37+
case Right(value) =>
38+
value.asInstanceOf[T]
3839

39-
assert(!ctx.reporter.hasErrors)
40+
case Left(classname) =>
41+
assert(!ctx.reporter.hasErrors)
4042

41-
val classLoader = new AbstractFileClassLoader(outDir, appClassloader)
43+
val classLoader = new AbstractFileClassLoader(outDir, appClassloader)
4244

43-
val clazz = classLoader.loadClass(driver.outputClassName.toString)
44-
val method = clazz.getMethod("apply")
45-
val inst = clazz.getConstructor().newInstance()
45+
val clazz = classLoader.loadClass(classname)
46+
val method = clazz.getMethod("apply")
47+
val inst = clazz.getConstructor().newInstance()
4648

47-
method.invoke(inst).asInstanceOf[T]
49+
method.invoke(inst).asInstanceOf[T]
50+
}
4851
}
4952

5053
override def initCtx: Context = {
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
scala> implicit def toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(getClass.getClassLoader)
22
def toolbox: quoted.Toolbox
3-
scala> import scala.quoted._
43
scala> val v = '{ (if true then Some(1) else None).map(v => v+1) }
54
val v: quoted.Expr[Option[Int]] = Expr(<pickled tasty>)
6-
scala> scala.quoted.run(v.show.toExpr)
5+
scala> scala.quoted.withNewQuoteContext(v.show)
76
val res0: String = (if (true) scala.Some.apply[scala.Int](1) else scala.None).map[scala.Int](((v: scala.Int) => v.+(1)))
87
scala> scala.quoted.run(v)
98
val res1: Option[Int] = Some(2)

docs/docs/reference/metaprogramming/staging.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,15 @@ The framework as discussed so far allows code to be staged, i.e. be prepared
6060
to be executed at a later stage. To run that code, there is another method
6161
in class `Expr` called `run`. Note that `$` and `run` both map from `Expr[T]`
6262
to `T` but only `$` is subject to the PCP, whereas `run` is just a normal method.
63+
Run provides a `QuoteContext` that can be used to show the expression in the scope of `run`.
64+
On the other hand `withNewQuoteContext` provides a `QuoteContext` without evauating the expression.
6365

6466
```scala
6567
package scala.quoted
6668

6769
def run[T](expr: given QuoteContext => Expr[T]) given (toolbox: Toolbox): T = ...
70+
71+
def withNewQuoteContext[T](thunk: given QuoteContext => T) given (toolbox: Toolbox): T = ...
6872
```
6973

7074
## Example
@@ -74,10 +78,7 @@ do not want to pass an array statically but generated code at run-time and pass
7478
the value, also at run-time. Note, how we make a future-stage function of type
7579
`Expr[Array[Int] => Int]` in line 4 below. Using `run { ... }` we can evaluate an
7680
expression at runtime. Within the scope of `run` we can also invoke `show` on an expression
77-
to get
78-
79-
Invoking the `.show` or `.run` we can
80-
either show the code or run it respectivelly.
81+
to get a source-like representation of the expression.
8182

8283
```scala
8384
// make available the necessary toolbox for runtime code generation

library/src-3.x/scala/quoted/package.scala

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,48 @@ package scala
33
package object quoted {
44

55
/** Evaluate the contents of this expression and return the result.
6+
* It provides a new QuoteContext that is only valid within the scope the argument.
67
*
78
* Usage:
89
* ```
9-
* val e: T = run {
10-
* expr
10+
* val e: T = run { // (given qctx: QuoteContext) =>
11+
* expr
1112
* }
1213
* ```
1314
* where `expr: Expr[T]`
14-
*
15+
*
16+
* This method shoul not be called in a context where there is already has a QuoteContext.
17+
*
1518
* May throw a FreeVariableError on expressions that came from a macro.
1619
*/
1720
def run[T](expr: given QuoteContext => Expr[T]) given (toolbox: Toolbox): T = toolbox.run(expr given _)
1821

22+
/** Provide a new quote context within the scope of the argument that is only valid within the scope the argument.
23+
* Return the result of the argument.
24+
*
25+
* Usage:
26+
* ```
27+
* val e: T = withNewQuoteContext { // (given qctx: QuoteContext) =>
28+
* thunk
29+
* }
30+
* ```
31+
* where `thunk: T`
32+
*
33+
* This method shoul not be called in a context where there is already has a QuoteContext.
34+
*/
35+
def withNewQuoteContext[T](thunk: given QuoteContext => T) given (toolbox: Toolbox): T = {
36+
var result: T = NoResult.asInstanceOf[T]
37+
def dummyRun given QuoteContext: Expr[Unit] = {
38+
result = thunk
39+
'{}
40+
}
41+
toolbox.run(dummyRun given _)
42+
assert(result != NoResult) // toolbox.run should have thrown an exception
43+
result
44+
}
45+
46+
private object NoResult
47+
1948
object autolift {
2049
implicit def autoToExpr[T: Liftable](x: T): Expr[T] = x.toExpr
2150
}

tests/disabled/reflect/run/position-val-def.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ object Test {
1010
def test(expr: String): Unit = {
1111
val t = toolbox.parse(expr)
1212
println(expr)
13-
println(run(t, printPositions = true.show.toExpr))
13+
println(show(t, printPositions = true))
1414
println()
1515
}
1616
val tests = """

tests/run-with-compiler/i3823-b.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import scala.quoted._
22
object Test {
3-
def main(args: Array[String]): Unit = {
3+
implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(getClass.getClassLoader)
4+
def main(args: Array[String]): Unit = withNewQuoteContext {
45
def f[T](x: Expr[T])(implicit t: Type[T]) = '{
56
val z: $t = $x
67
}
7-
implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(getClass.getClassLoader)
8-
println(run(f('{2})(Type.IntTag).show.toExpr))
8+
println(f('{2})(Type.IntTag).show)
99
}
1010
}

tests/run-with-compiler/i3823-c.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import scala.quoted._
22
object Test {
3-
def main(args: Array[String]): Unit = {
3+
implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(getClass.getClassLoader)
4+
def main(args: Array[String]): Unit = withNewQuoteContext {
45
def f[T](x: Expr[T])(implicit t: Type[T]) = '{
56
val z = $x
67
}
7-
implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(getClass.getClassLoader)
8-
println(run(f('{2})(Type.IntTag).show.toExpr))
8+
println(f('{2})(Type.IntTag).show)
99
}
1010
}

tests/run-with-compiler/i3823.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import scala.quoted._
22
object Test {
3-
def main(args: Array[String]): Unit = {
3+
implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(getClass.getClassLoader)
4+
def main(args: Array[String]): Unit = withNewQuoteContext {
45
def f[T: Type](x: Expr[T])(t: Type[T]) = '{
56
val z: $t = $x
67
}
7-
implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(getClass.getClassLoader)
8-
println(run(f('{2})('[Int]).show.toExpr))
8+
println(f('{2})('[Int]).show)
99
}
1010
}

tests/run-with-compiler/i3847-b.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ object Arrays {
1313
}
1414

1515
object Test {
16-
def main(args: Array[String]): Unit = {
17-
implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(getClass.getClassLoader)
16+
implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(getClass.getClassLoader)
17+
def main(args: Array[String]): Unit = withNewQuoteContext {
1818
import Arrays._
1919
implicit val ct: Expr[ClassTag[Int]] = '{ClassTag.Int}
2020
val arr: Expr[Array[List[Int]]] = Array[List[Int]](List(1, 2, 3)).toExpr
21-
println(run(arr.show.toExpr))
21+
println(arr.show)
2222
}
2323
}

tests/run-with-compiler/i3847.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ object Arrays {
1313
}
1414

1515
object Test {
16-
def main(args: Array[String]): Unit = {
17-
implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(this.getClass.getClassLoader)
16+
implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(this.getClass.getClassLoader)
17+
def main(args: Array[String]): Unit = withNewQuoteContext {
1818
import Arrays._
1919
implicit val ct: Expr[ClassTag[Int]] = '{ClassTag.Int}
2020
val arr: Expr[Array[Int]] = Array[Int](1, 2, 3).toExpr
21-
println(run(arr.show.toExpr))
21+
println(arr.show)
2222
}
2323
}

tests/run-with-compiler/i3876-b.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ object Test {
1111
}
1212

1313
println(run(f2(x)))
14-
println(run(f2(x).show.toExpr))
14+
println(withNewQuoteContext(f2(x).show))
1515
}
1616
}

tests/run-with-compiler/i3876-c.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ object Test {
1111
}
1212

1313
println(run(f3(x)))
14-
println(run(f3(x).show.toExpr)) // TODO improve printer
14+
println(withNewQuoteContext(f3(x).show)) // TODO improve printer
1515
}
1616
}

tests/run-with-compiler/i3876-d.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ object Test {
99
inlineLambda
1010
}
1111
println(run(f4(x)))
12-
println(run(f4(x).show.toExpr))
12+
println(withNewQuoteContext(f4(x).show))
1313
}
1414

1515
inline def inlineLambda <: Int => Int = x => x + x

tests/run-with-compiler/i3876-e.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ object Test {
99
inlineLambda
1010
}
1111
println(run(f4(x)))
12-
println(run(f4(x).show.toExpr))
12+
println(withNewQuoteContext(f4(x).show))
1313
}
1414

1515
inline def inlineLambda <: Int => Int = x => x + x

tests/run-with-compiler/i3876.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ object Test {
88
val f: Expr[Int => Int] = '{ (x: Int) => x + x }
99

1010
println(run(f(x)))
11-
println(run(f(x).show.toExpr))
11+
println(withNewQuoteContext(f(x).show))
1212
}
1313
}

tests/run-with-compiler/i3946.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ object Test {
33
def main(args: Array[String]): Unit = {
44
implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(getClass.getClassLoader)
55
val u: Expr[Unit] = '{}
6-
println(run(u.show.toExpr))
6+
println(withNewQuoteContext(u.show))
77
println(run(u))
88
}
99
}

tests/run-with-compiler/i4044a.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ class Foo {
55
implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(getClass.getClassLoader)
66
val e: Expr[Int] = '{3}
77
val q = '{ ${ '{ $e } } }
8-
println(run(q.show.toExpr))
8+
println(withNewQuoteContext(q.show))
99
}
1010
}
1111

tests/run-with-compiler/i4044b.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ object Test {
2222
def main(args: Array[String]): Unit = {
2323
implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(getClass.getClassLoader)
2424
val q = VarRef('{4})(varRef => '{ ${varRef.update('{3})}; ${varRef.expr} })
25-
println(run(q.show.toExpr))
25+
println(withNewQuoteContext(q.show))
2626
}
2727
}

tests/run-with-compiler/i4044c.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ class Foo {
44
def foo: Unit = {
55
implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(getClass.getClassLoader)
66
val q = '{ ${ '{ ${ '{ 5 } } } } }
7-
println(run(q.show.toExpr))
7+
println(withNewQuoteContext(q.show))
88
}
99
}
1010

tests/run-with-compiler/i4044e.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class Foo {
77
val f: Expr[Int] = '{5}
88
val t: Type[Int] = '[Int]
99
val q = '{ ${ '{ ($e + $f).asInstanceOf[$t] } } }
10-
println(run(q.show.toExpr))
10+
println(withNewQuoteContext(q.show))
1111
}
1212
}
1313

0 commit comments

Comments
 (0)