Skip to content

Commit 2d85dba

Browse files
committed
Fix management of compiler instances of quoted.Expr run and show
Changes: * Make quotes eager: fixes #5376 (requires context) * Fix bugs shown in #5429: Tree from the macro parameters is passed to another compiler to show it. * Protect against scope extrusion with early (meaningful) runtime failure (rather than in the compiler) * Add an implicit function type to provide context. * Fixes #5161
1 parent b34ea5d commit 2d85dba

File tree

205 files changed

+1330
-1142
lines changed

Some content is hidden

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

205 files changed

+1330
-1142
lines changed

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ class ScalaSettings extends Settings.SettingGroup {
134134
val YprintDebug: Setting[Boolean] = BooleanSetting("-Yprint-debug", "when printing trees, print some extra information useful for debugging.")
135135
val YprintDebugOwners: Setting[Boolean] = BooleanSetting("-Yprint-debug-owners", "when printing trees, print owners of definitions.")
136136
val YshowPrintErrors: Setting[Boolean] = BooleanSetting("-Yshow-print-errors", "don't suppress exceptions thrown during tree printing.")
137-
val YshowRawQuoteTrees: Setting[Boolean] = BooleanSetting("-Yshow-raw-tree", "don't remove quote artifacts")
137+
val YshowRawQuoteTrees: Setting[Boolean] = BooleanSetting("-Yshow-raw-quote-tree", "don't remove quote artifacts")
138138
val YtestPickler: Setting[Boolean] = BooleanSetting("-Ytest-pickler", "self-test for pickling functionality; should be used with -Ystop-after:pickler")
139139
val YcheckReentrant: Setting[Boolean] = BooleanSetting("-Ycheck-reentrant", "check that compiled program does not contain vars that can be accessed from a global root.")
140140
val YdropComments: Setting[Boolean] = BooleanSetting("-Ydrop-comments", "Drop comments when scanning source files.")

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -745,15 +745,26 @@ class Definitions {
745745
lazy val QuotedMatchingBindingType: TypeRef = ctx.requiredClassRef("scala.quoted.matching.Bind")
746746
def QuotedMatchingBindingClass(implicit ctx: Context): ClassSymbol = QuotedMatchingBindingType.symbol.asClass
747747

748-
def Unpickler_unpickleExpr: TermSymbol = ctx.requiredMethod("scala.runtime.quoted.Unpickler.unpickleExpr")
749-
def Unpickler_liftedExpr: TermSymbol = ctx.requiredMethod("scala.runtime.quoted.Unpickler.liftedExpr")
750-
def Unpickler_unpickleType: TermSymbol = ctx.requiredMethod("scala.runtime.quoted.Unpickler.unpickleType")
748+
lazy val UnpicklerModuleRef: TermRef = ctx.requiredModuleRef("scala.runtime.quoted.Unpickler")
749+
def UnpicklerModule(implicit ctx: Context): Symbol = UnpicklerModuleRef.symbol
750+
lazy val UnpicklerType: TypeRef = ctx.requiredClassRef("scala.runtime.quoted.Unpickler")
751+
def UnpicklerClass(implicit ctx: Context): ClassSymbol = UnpicklerType.symbol.asClass
752+
753+
lazy val Unpickler_unpickleExprR: TermRef = UnpicklerModule.requiredMethodRef("unpickleExpr")
754+
def Unpickler_unpickleExpr: Symbol = Unpickler_unpickleExprR.symbol
755+
lazy val Unpickler_liftedExprR: TermRef = UnpicklerModule.requiredMethodRef("liftedExpr")
756+
def Unpickler_liftedExpr: Symbol = Unpickler_liftedExprR.symbol
757+
lazy val Unpickler_unpickleTypeR: TermRef = UnpicklerModule.requiredMethodRef("unpickleType")
758+
def Unpickler_unpickleType: Symbol = Unpickler_unpickleTypeR.symbol
751759

752760
lazy val TastyReflectionType: TypeRef = ctx.requiredClassRef("scala.tasty.Reflection")
753761
def TastyReflectionClass(implicit ctx: Context): ClassSymbol = TastyReflectionType.symbol.asClass
754762

755-
lazy val TastyReflectionModule: TermSymbol = ctx.requiredModule("scala.tasty.Reflection")
756-
lazy val TastyReflection_macroContext: TermSymbol = TastyReflectionModule.requiredMethod("macroContext")
763+
lazy val StagingContextType: TypeRef = ctx.requiredClassRef("scala.quoted.StagingContext")
764+
def StagingContextClass(implicit ctx: Context): ClassSymbol = StagingContextType.symbol.asClass
765+
766+
lazy val StagingContextModule: TermSymbol = ctx.requiredModule("scala.quoted.StagingContext")
767+
lazy val StagingContext_macroContext: TermSymbol = StagingContextModule.requiredMethod("macroContext")
757768

758769
lazy val EqlType: TypeRef = ctx.requiredClassRef("scala.Eql")
759770
def EqlClass(implicit ctx: Context): ClassSymbol = EqlType.symbol.asClass

compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,40 +34,46 @@ object PickledQuotes {
3434

3535
/** Transform the expression into its fully spliced Tree */
3636
def quotedExprToTree[T](expr: quoted.Expr[T])(implicit ctx: Context): Tree = expr match {
37-
case expr: TastyExpr[_] =>
38-
val unpickled = unpickleExpr(expr)
39-
val force = new TreeTraverser {
40-
def traverse(tree: tpd.Tree)(implicit ctx: Context): Unit = traverseChildren(tree)
41-
}
42-
force.traverse(unpickled)
43-
unpickled
4437
case expr: LiftedExpr[T] =>
4538
expr.value match {
4639
case value: Class[_] => ref(defn.Predef_classOf).appliedToType(classToType(value))
4740
case value => Literal(Constant(value))
4841
}
49-
case expr: TastyTreeExpr[Tree] @unchecked => healOwner(expr.tree)
42+
case expr: TastyTreeExpr[Tree] @unchecked =>
43+
if (contextId != expr.ctxId)
44+
throw new scala.quoted.QuoteError(
45+
"""Quote used outside of the `Staging` context where it was defined.
46+
|This usualy indicated that a `run` was called within another `run` or a macro.
47+
""".stripMargin)
48+
healOwner(expr.tree)
5049
case expr: FunctionAppliedTo[_] =>
5150
functionAppliedTo(quotedExprToTree(expr.f), expr.args.map(arg => quotedExprToTree(arg)).toList)
5251
}
5352

5453
/** Transform the expression into its fully spliced TypeTree */
5554
def quotedTypeToTree(expr: quoted.Type[_])(implicit ctx: Context): Tree = expr match {
56-
case expr: TastyType[_] => unpickleType(expr)
5755
case expr: TaggedType[_] => classTagToTypeTree(expr.ct)
5856
case expr: TreeType[Tree] @unchecked => healOwner(expr.typeTree)
5957
}
6058

6159
/** Unpickle the tree contained in the TastyExpr */
62-
private def unpickleExpr(expr: TastyExpr[_])(implicit ctx: Context): Tree = {
63-
val tastyBytes = TastyString.unpickle(expr.tasty)
64-
unpickle(tastyBytes, expr.args, isType = false)(ctx.addMode(Mode.ReadPositions))
60+
def unpickleExpr(tasty: scala.runtime.quoted.Unpickler.Pickled, args: Seq[Any])(implicit ctx: Context): Tree = {
61+
val tastyBytes = TastyString.unpickle(tasty)
62+
val unpickled = unpickle(tastyBytes, args, isType = false)(ctx.addMode(Mode.ReadPositions))
63+
force.traverse(unpickled)
64+
unpickled
6565
}
6666

6767
/** Unpickle the tree contained in the TastyType */
68-
private def unpickleType(ttpe: TastyType[_])(implicit ctx: Context): Tree = {
69-
val tastyBytes = TastyString.unpickle(ttpe.tasty)
70-
unpickle(tastyBytes, ttpe.args, isType = true)(ctx.addMode(Mode.ReadPositions))
68+
def unpickleType(tasty: scala.runtime.quoted.Unpickler.Pickled, args: Seq[Any])(implicit ctx: Context): Tree = {
69+
val tastyBytes = TastyString.unpickle(tasty)
70+
val unpickled = unpickle(tastyBytes, args, isType = true)(ctx.addMode(Mode.ReadPositions))
71+
force.traverse(unpickled)
72+
unpickled
73+
}
74+
75+
private def force = new TreeTraverser {
76+
def traverse(tree: tpd.Tree)(implicit ctx: Context): Unit = traverseChildren(tree)
7177
}
7278

7379
// TASTY picklingtests/pos/quoteTest.scala
@@ -193,4 +199,12 @@ object PickledQuotes {
193199
case _ => tree
194200
}
195201
}
202+
203+
/** Id of the current compilation (macros) or Expr[_] run */
204+
def contextId(implicit ctx: Context): Int = {
205+
def root(ctx: Context): Context =
206+
if (ctx.outer != NoContext) root(ctx.outer) else ctx
207+
root(ctx).hashCode
208+
}
209+
196210
}

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import scala.collection.mutable.ListBuffer
3131
import scala.collection.mutable
3232
import config.Printers.pickling
3333
import core.quoted.PickledQuotes
34+
import core.quoted.Quoted
3435

3536
import scala.quoted
3637
import scala.quoted.Types.TreeType
@@ -1272,7 +1273,7 @@ class TreeUnpickler(reader: TastyReader,
12721273
val args = until(end)(readTerm())
12731274
val splice = splices(idx)
12741275
def wrap(arg: Tree) =
1275-
if (arg.isTerm) new TastyTreeExpr(arg)
1276+
if (arg.isTerm) new TastyTreeExpr(arg, PickledQuotes.contextId)
12761277
else new TreeType(arg)
12771278
val reifiedArgs = args.map(wrap)
12781279
val filled = if (isType) {

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ package dotty.tools.dotc.quoted
33
import dotty.tools.dotc.CompilationUnit
44
import dotty.tools.dotc.util.NoSource
55

6-
import scala.quoted.Expr
6+
import scala.quoted.{Expr, StagingContext}
77

88
/* Compilation unit containing the contents of a quoted expression */
9-
class ExprCompilationUnit(val expr: Expr[_]) extends CompilationUnit(NoSource) {
10-
override def toString: String = s"Expr($expr)"
11-
}
9+
class ExprCompilationUnit(val expr: StagingContext => Expr[Any]) extends CompilationUnit(NoSource)

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ import dotty.tools.dotc.core.quoted.PickledQuotes
1717
import dotty.tools.dotc.transform.Staging
1818
import dotty.tools.dotc.util.Spans.Span
1919
import dotty.tools.dotc.util.SourceFile
20-
import dotty.tools.io.{Path, VirtualFile}
20+
import dotty.tools.io.{VirtualFile}
2121

22-
import scala.quoted.{Expr, Type}
22+
import scala.quoted.{Expr, StagingContext, Type}
2323

2424
/** Compiler that takes the contents of a quoted expression `expr` and produces
2525
* a class file with `class ' { def apply: Object = expr }`.
@@ -50,7 +50,7 @@ class QuoteCompiler extends Compiler {
5050
case exprUnit: ExprCompilationUnit =>
5151
val tree =
5252
if (putInClass) inClass(exprUnit.expr)
53-
else PickledQuotes.quotedExprToTree(exprUnit.expr)
53+
else unpickleExpr(exprUnit.expr)
5454
val source = SourceFile.virtual("<quoted.Expr>", "")
5555
CompilationUnit(source, tree, forceTrees = true)
5656
case typeUnit: TypeCompilationUnit =>
@@ -61,11 +61,16 @@ class QuoteCompiler extends Compiler {
6161
}
6262
}
6363

64+
private def unpickleExpr(code: StagingContext => Expr[Any])(implicit ctx: Context) = {
65+
val expr = code(new StagingImpl)
66+
PickledQuotes.quotedExprToTree(expr)
67+
}
68+
6469
/** Places the contents of expr in a compilable tree for a class
6570
* with the following format.
6671
* `package __root__ { class ' { def apply: Any = <expr> } }`
6772
*/
68-
private def inClass(expr: Expr[_])(implicit ctx: Context): Tree = {
73+
private def inClass(code: StagingContext => Expr[_])(implicit ctx: Context): Tree = {
6974
val pos = Span(0)
7075
val assocFile = new VirtualFile("<quote>")
7176

@@ -74,7 +79,7 @@ class QuoteCompiler extends Compiler {
7479
cls.enter(ctx.newDefaultConstructor(cls), EmptyScope)
7580
val meth = ctx.newSymbol(cls, nme.apply, Method, ExprType(defn.AnyType), coord = pos).entered
7681

77-
val quoted = PickledQuotes.quotedExprToTree(expr)(ctx.withOwner(meth))
82+
val quoted = unpickleExpr(code)(ctx.withOwner(meth))
7883

7984
val run = DefDef(meth, quoted)
8085
val classTree = ClassDef(cls, DefDef(cls.primaryConstructor.asTerm), run :: Nil)
@@ -85,7 +90,7 @@ class QuoteCompiler extends Compiler {
8590
}
8691

8792
class ExprRun(comp: Compiler, ictx: Context) extends Run(comp, ictx) {
88-
def compileExpr(expr: Expr[_]): Unit = {
93+
def compileExpr(expr: StagingContext => Expr[_]): Unit = {
8994
val units = new ExprCompilationUnit(expr) :: Nil
9095
compileUnits(units)
9196
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package dotty.tools.dotc.quoted
2+
3+
import dotty.tools.dotc.core.Contexts._
4+
import dotty.tools.dotc.core.quoted.PickledQuotes
5+
import dotty.tools.dotc.tastyreflect.ReflectionImpl
6+
7+
import scala.quoted.{Expr, Type}
8+
import scala.runtime.quoted.Unpickler.Pickled
9+
10+
class StagingImpl(implicit ctx: Context) extends scala.quoted.StagingContext {
11+
12+
def unpickleExpr[T](repr: Pickled, args: Seq[Any]): Expr[T] =
13+
new scala.quoted.Exprs.TastyTreeExpr(PickledQuotes.unpickleExpr(repr, args), PickledQuotes.contextId).asInstanceOf[Expr[T]]
14+
15+
def unpickleType[T](repr: Pickled, args: Seq[Any]): Type[T] =
16+
new scala.quoted.Types.TreeType(PickledQuotes.unpickleType(repr, args)).asInstanceOf[Type[T]]
17+
18+
def show[T](expr: Expr[T]): String = {
19+
val tree = PickledQuotes.quotedExprToTree(expr)
20+
// TODO freshen names
21+
val tree1 =
22+
if (ctx.settings.YshowRawQuoteTrees.value) tree else (new TreeCleaner).transform(tree)
23+
new ReflectionImpl(ctx).showSourceCode.showTree(tree1)
24+
}
25+
26+
def show[T](tpe: Type[T]): String = {
27+
val tree = PickledQuotes.quotedTypeToTree(tpe)
28+
// TODO freshen names
29+
val tree1 = if (ctx.settings.YshowRawQuoteTrees.value) tree else (new TreeCleaner).transform(tree)
30+
new ReflectionImpl(ctx).showSourceCode.showTypeOrBoundsTree(tree1)
31+
}
32+
33+
val reflection: scala.tasty.Reflection = new ReflectionImpl(ctx)
34+
35+
}

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

Lines changed: 0 additions & 19 deletions
This file was deleted.

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

Lines changed: 5 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
package dotty.tools.dotc.quoted
22

3-
import dotty.tools.dotc.ast.tpd
43
import dotty.tools.dotc.Driver
54
import dotty.tools.dotc.core.Contexts.{Context, ContextBase, FreshContext}
6-
import dotty.tools.dotc.tastyreflect.ReflectionImpl
75
import dotty.tools.io.{AbstractFile, Directory, PlainDirectory, VirtualDirectory}
86
import dotty.tools.repl.AbstractFileClassLoader
97
import dotty.tools.dotc.reporting._
10-
import scala.quoted.{Expr, Type}
8+
import scala.quoted.{Expr, StagingContext}
119
import scala.quoted.Toolbox
1210
import java.net.URLClassLoader
1311

12+
import dotty.tools.dotc.core.quoted.PickledQuotes
13+
1414
/** Driver to compile quoted code
1515
*
1616
* @param appClassloader classloader of the application that generated the quotes
@@ -20,7 +20,7 @@ class QuoteDriver(appClassloader: ClassLoader) extends Driver {
2020

2121
private[this] val contextBase: ContextBase = new ContextBase
2222

23-
def run[T](expr: Expr[T], settings: Toolbox.Settings): T = {
23+
def run[T](code: StagingContext => Expr[T], settings: Toolbox.Settings): T = {
2424
val outDir: AbstractFile = settings.outDir match {
2525
case Some(out) =>
2626
val dir = Directory(out)
@@ -34,7 +34,7 @@ class QuoteDriver(appClassloader: ClassLoader) extends Driver {
3434
val ctx = setToolboxSettings(ctx0.fresh.setSetting(ctx0.settings.outputDir, outDir), settings)
3535

3636
val driver = new QuoteCompiler
37-
driver.newRun(ctx).compileExpr(expr)
37+
driver.newRun(ctx).compileExpr(code)
3838

3939
assert(!ctx.reporter.hasErrors)
4040

@@ -47,44 +47,6 @@ class QuoteDriver(appClassloader: ClassLoader) extends Driver {
4747
method.invoke(inst).asInstanceOf[T]
4848
}
4949

50-
private def doShow(tree: Tree, ctx: Context): String = {
51-
implicit val c: Context = ctx
52-
val tree1 =
53-
if (ctx.settings.YshowRawQuoteTrees.value) tree
54-
else (new TreeCleaner).transform(tree)
55-
ReflectionImpl.showTree(tree1)
56-
}
57-
58-
def show(expr: Expr[_], settings: Toolbox.Settings): String =
59-
withTree(expr, doShow, settings)
60-
61-
def show(tpe: Type[_], settings: Toolbox.Settings): String =
62-
withTypeTree(tpe, doShow, settings)
63-
64-
def withTree[T](expr: Expr[_], f: (Tree, Context) => T, settings: Toolbox.Settings): T = {
65-
val ctx = setToolboxSettings(setup(settings.compilerArgs.toArray :+ "dummy.scala", initCtx.fresh)._2.fresh, settings)
66-
67-
var output: Option[T] = None
68-
def registerTree(tree: tpd.Tree)(ctx: Context): Unit = {
69-
assert(output.isEmpty)
70-
output = Some(f(tree, ctx))
71-
}
72-
new QuoteDecompiler(registerTree).newRun(ctx).compileExpr(expr)
73-
output.getOrElse(throw new Exception("Could not extract " + expr))
74-
}
75-
76-
def withTypeTree[T](tpe: Type[_], f: (TypTree, Context) => T, settings: Toolbox.Settings): T = {
77-
val ctx = setToolboxSettings(setup(settings.compilerArgs.toArray :+ "dummy.scala", initCtx.fresh)._2.fresh, settings)
78-
79-
var output: Option[T] = None
80-
def registerTree(tree: tpd.Tree)(ctx: Context): Unit = {
81-
assert(output.isEmpty)
82-
output = Some(f(tree.asInstanceOf[TypTree], ctx))
83-
}
84-
new QuoteDecompiler(registerTree).newRun(ctx).compileType(tpe)
85-
output.getOrElse(throw new Exception("Could not extract " + tpe))
86-
}
87-
8850
override def initCtx: Context = {
8951
val ictx = contextBase.initialCtx
9052
ictx.settings.classpath.update(QuoteDriver.currentClasspath(appClassloader))(ictx)

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

Lines changed: 0 additions & 38 deletions
This file was deleted.

0 commit comments

Comments
 (0)