Skip to content

Commit ac2452d

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.
1 parent 3e555e9 commit ac2452d

File tree

219 files changed

+1314
-1259
lines changed

Some content is hidden

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

219 files changed

+1314
-1259
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ class ScalaSettings extends Settings.SettingGroup {
115115
val YprintDebug: Setting[Boolean] = BooleanSetting("-Yprint-debug", "when printing trees, print some extra information useful for debugging.")
116116
val YprintDebugOwners: Setting[Boolean] = BooleanSetting("-Yprint-debug-owners", "when printing trees, print owners of definitions.")
117117
val YshowPrintErrors: Setting[Boolean] = BooleanSetting("-Yshow-print-errors", "don't suppress exceptions thrown during tree printing.")
118-
val YshowRawQuoteTrees: Setting[Boolean] = BooleanSetting("-Yshow-raw-tree", "don't remove quote artifacts")
118+
val YshowRawQuoteTrees: Setting[Boolean] = BooleanSetting("-Yshow-raw-quote-tree", "don't remove quote artifacts")
119119
val YtestPickler: Setting[Boolean] = BooleanSetting("-Ytest-pickler", "self-test for pickling functionality; should be used with -Ystop-after:pickler")
120120
val YcheckReentrant: Setting[Boolean] = BooleanSetting("-Ycheck-reentrant", "check that compiled program does not contain vars that can be accessed from a global root.")
121121
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
@@ -693,15 +693,26 @@ class Definitions {
693693
lazy val QuotedLiftableType: TypeRef = ctx.requiredClassRef("scala.quoted.Liftable")
694694
def QuotedLiftableClass(implicit ctx: Context): ClassSymbol = QuotedLiftableType.symbol.asClass
695695

696-
def Unpickler_unpickleExpr: TermSymbol = ctx.requiredMethod("scala.runtime.quoted.Unpickler.unpickleExpr")
697-
def Unpickler_liftedExpr: TermSymbol = ctx.requiredMethod("scala.runtime.quoted.Unpickler.liftedExpr")
698-
def Unpickler_unpickleType: TermSymbol = ctx.requiredMethod("scala.runtime.quoted.Unpickler.unpickleType")
696+
lazy val UnpicklerModuleRef: TermRef = ctx.requiredModuleRef("scala.runtime.quoted.Unpickler")
697+
def UnpicklerModule(implicit ctx: Context): Symbol = UnpicklerModuleRef.symbol
698+
lazy val UnpicklerType: TypeRef = ctx.requiredClassRef("scala.runtime.quoted.Unpickler")
699+
def UnpicklerClass(implicit ctx: Context): ClassSymbol = UnpicklerType.symbol.asClass
700+
701+
lazy val Unpickler_unpickleExprR: TermRef = UnpicklerModule.requiredMethodRef("unpickleExpr")
702+
def Unpickler_unpickleExpr: Symbol = Unpickler_unpickleExprR.symbol
703+
lazy val Unpickler_liftedExprR: TermRef = UnpicklerModule.requiredMethodRef("liftedExpr")
704+
def Unpickler_liftedExpr: Symbol = Unpickler_liftedExprR.symbol
705+
lazy val Unpickler_unpickleTypeR: TermRef = UnpicklerModule.requiredMethodRef("unpickleType")
706+
def Unpickler_unpickleType: Symbol = Unpickler_unpickleTypeR.symbol
699707

700708
lazy val TastyReflectionType: TypeRef = ctx.requiredClassRef("scala.tasty.Reflection")
701709
def TastyReflectionClass(implicit ctx: Context): ClassSymbol = TastyReflectionType.symbol.asClass
702710

703-
lazy val TastyReflectionModule: TermSymbol = ctx.requiredModule("scala.tasty.Reflection")
704-
lazy val TastyReflection_macroContext: TermSymbol = TastyReflectionModule.requiredMethod("macroContext")
711+
lazy val StagingContextType: TypeRef = ctx.requiredClassRef("scala.quoted.StagingContext")
712+
def StagingContextClass(implicit ctx: Context): ClassSymbol = StagingContextType.symbol.asClass
713+
714+
lazy val StagingContextModule: TermSymbol = ctx.requiredModule("scala.quoted.StagingContext")
715+
lazy val StagingContext_macroContext: TermSymbol = StagingContextModule.requiredMethod("macroContext")
705716

706717
lazy val EqType: TypeRef = ctx.requiredClassRef("scala.Eq")
707718
def EqClass(implicit ctx: Context): ClassSymbol = EqType.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), quotedExprToTree(expr.x))
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
@@ -187,4 +193,12 @@ object PickledQuotes {
187193
case _ => tree
188194
}
189195
}
196+
197+
/** Id of the current compilation (macros) or Expr[_] run */
198+
def contextId(implicit ctx: Context): Int = {
199+
def root(ctx: Context): Context =
200+
if (ctx.outer != NoContext) root(ctx.outer) else ctx
201+
root(ctx).hashCode
202+
}
203+
190204
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package dotty.tools.dotc.core.quoted
22

33
import dotty.tools.dotc.ast.Trees.GenericApply
44
import dotty.tools.dotc.ast.tpd
5-
import dotty.tools.dotc.core.Contexts.Context
5+
import dotty.tools.dotc.core.Contexts._
66
import dotty.tools.dotc.core.Types.Type
77
import dotty.tools.dotc.transform.SymUtils._
88

@@ -17,4 +17,5 @@ object Quoted {
1717
case tree: GenericApply[Type] if tree.symbol.isQuote => Some(tree.args.head)
1818
case _ => None
1919
}
20+
2021
}

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

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

3435
import scala.quoted
3536
import scala.quoted.Types.TreeType
@@ -1257,7 +1258,7 @@ class TreeUnpickler(reader: TastyReader,
12571258
val args = until(end)(readTerm())
12581259
val splice = splices(idx)
12591260
def wrap(arg: Tree) =
1260-
if (arg.isTerm) new TastyTreeExpr(arg)
1261+
if (arg.isTerm) new TastyTreeExpr(arg, PickledQuotes.contextId)
12611262
else new TreeType(arg)
12621263
val reifiedArgs = args.map(wrap)
12631264
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: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ import dotty.tools.dotc.core.Symbols.defn
1414
import dotty.tools.dotc.core.Types.ExprType
1515
import dotty.tools.dotc.core.quoted.PickledQuotes
1616
import dotty.tools.dotc.transform.Staging
17+
import dotty.tools.dotc.tastyreflect.ReflectionImpl
1718
import dotty.tools.dotc.typer.FrontEnd
1819
import dotty.tools.dotc.util.Positions.Position
1920
import dotty.tools.dotc.util.SourceFile
2021
import dotty.tools.io.{Path, VirtualFile}
2122

22-
import scala.quoted.{Expr, Type}
23+
import scala.quoted.{Expr, StagingContext, Type}
24+
import scala.runtime.quoted.Unpickler.Pickled
2325

2426
/** Compiler that takes the contents of a quoted expression `expr` and produces
2527
* a class file with `class ' { def apply: Object = expr }`.
@@ -50,7 +52,7 @@ class QuoteCompiler extends Compiler {
5052
case exprUnit: ExprCompilationUnit =>
5153
val tree =
5254
if (putInClass) inClass(exprUnit.expr)
53-
else PickledQuotes.quotedExprToTree(exprUnit.expr)
55+
else unpickleExpr(exprUnit.expr)
5456
val source = new SourceFile("", "")
5557
CompilationUnit.mkCompilationUnit(source, tree, forceTrees = true)
5658
case typeUnit: TypeCompilationUnit =>
@@ -61,11 +63,16 @@ class QuoteCompiler extends Compiler {
6163
}
6264
}
6365

66+
private def unpickleExpr(code: StagingContext => Expr[Any])(implicit ctx: Context) = {
67+
val expr = code(new StagingImpl)
68+
PickledQuotes.quotedExprToTree(expr)
69+
}
70+
6471
/** Places the contents of expr in a compilable tree for a class
6572
* with the following format.
6673
* `package __root__ { class ' { def apply: Any = <expr> } }`
6774
*/
68-
private def inClass(expr: Expr[_])(implicit ctx: Context): Tree = {
75+
private def inClass(code: StagingContext => Expr[_])(implicit ctx: Context): Tree = {
6976
val pos = Position(0)
7077
val assocFile = new VirtualFile("<quote>")
7178

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

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

7986
val run = DefDef(meth, quoted)
8087
val classTree = ClassDef(cls, DefDef(cls.primaryConstructor.asTerm), run :: Nil)
@@ -83,7 +90,7 @@ class QuoteCompiler extends Compiler {
8390
}
8491

8592
class ExprRun(comp: Compiler, ictx: Context) extends Run(comp, ictx) {
86-
def compileExpr(expr: Expr[_]): Unit = {
93+
def compileExpr(expr: StagingContext => Expr[_]): Unit = {
8794
val units = new ExprCompilationUnit(expr) :: Nil
8895
compileUnits(units)
8996
}
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 & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
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

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
class QuoteDriver extends Driver {
15-
import tpd._
1615

1716
private[this] val contextBase: ContextBase = new ContextBase
1817

19-
def run[T](expr: Expr[T], settings: Toolbox.Settings): T = {
18+
def run[T](code: StagingContext => Expr[T], settings: Toolbox.Settings): T = {
2019
val outDir: AbstractFile = settings.outDir match {
2120
case Some(out) =>
2221
val dir = Directory(out)
@@ -30,7 +29,7 @@ class QuoteDriver extends Driver {
3029
val ctx = setToolboxSettings(ctx0.fresh.setSetting(ctx0.settings.outputDir, outDir), settings)
3130

3231
val driver = new QuoteCompiler
33-
driver.newRun(ctx).compileExpr(expr)
32+
driver.newRun(ctx).compileExpr(code)
3433

3534
val classLoader = new AbstractFileClassLoader(outDir, this.getClass.getClassLoader)
3635

@@ -41,41 +40,6 @@ class QuoteDriver extends Driver {
4140
method.invoke(instance).asInstanceOf[T]
4241
}
4342

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

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

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

0 commit comments

Comments
 (0)