Skip to content

Commit 3fe7e58

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 fb0e3bc commit 3fe7e58

File tree

234 files changed

+1478
-1345
lines changed

Some content is hidden

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

234 files changed

+1478
-1345
lines changed

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

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

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

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

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

707718
lazy val EqType: TypeRef = ctx.requiredClassRef("scala.Eq")
708719
def EqClass(implicit ctx: Context): ClassSymbol = EqType.symbol.asClass

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

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import dotty.tools.dotc.core.StdNames._
1010
import dotty.tools.dotc.core.NameKinds
1111
import dotty.tools.dotc.core.Mode
1212
import dotty.tools.dotc.core.Symbols._
13-
import dotty.tools.dotc.core.Types.Type
13+
import dotty.tools.dotc.core.Types.{MethodType, Type}
1414
import dotty.tools.dotc.core.tasty.TreePickler.Hole
1515
import dotty.tools.dotc.core.tasty.{PositionPickler, TastyPickler, TastyPrinter, TastyString}
1616
import dotty.tools.dotc.core.tasty.TreeUnpickler.UnpickleMode
@@ -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
@@ -128,26 +134,35 @@ object PickledQuotes {
128134
}
129135

130136
private def functionAppliedTo(fn: Tree, args: List[Tree])(implicit ctx: Context): Tree = {
131-
val argVals = args.map(arg => SyntheticValDef(NameKinds.UniqueName.fresh("x".toTermName), arg))
132-
def argRefs() = argVals.map(argVal => ref(argVal.symbol))
133-
def rec(fn: Tree): Tree = fn match {
137+
def rec(fn: Tree): (List[ValDef], Tree) = fn match {
134138
case Inlined(call, bindings, expansion) =>
135139
// this case must go before closureDef to avoid dropping the inline node
136-
cpy.Inlined(fn)(call, bindings, rec(expansion))
140+
val (argVals, expansion1) = rec(expansion)
141+
(argVals, cpy.Inlined(fn)(call, bindings, expansion1))
137142
case closureDef(ddef) =>
138143
val paramSyms = ddef.vparamss.head.map(param => param.symbol)
139-
val paramToVals = paramSyms.zip(argRefs()).toMap
140-
new TreeTypeMap(
144+
val argVals = args.map(arg => SyntheticValDef(NameKinds.UniqueName.fresh("x".toTermName), arg))
145+
val argRefs = argVals.map(argVal => ref(argVal.symbol))
146+
val paramToVals = paramSyms.zip(argRefs).toMap
147+
val inlinedRhs = new TreeTypeMap(
141148
oldOwners = ddef.symbol :: Nil,
142149
newOwners = ctx.owner :: Nil,
143150
treeMap = tree => paramToVals.get(tree.symbol).map(_.withPos(tree.pos)).getOrElse(tree)
144151
).transform(ddef.rhs)
152+
(argVals, inlinedRhs)
145153
case Block(stats, expr) =>
146-
seq(stats, rec(expr))
154+
// this case must go after closureDef to avoid skipping the closure block
155+
val (argVals, expr1) = rec(expr)
156+
(argVals, cpy.Block(fn)(stats, expr1))
147157
case _ =>
148-
fn.select(nme.apply).appliedToArgs(argRefs())
158+
val call = fn.tpe.widenDealias match {
159+
case _: MethodType => fn
160+
case _ => fn.select(nme.apply).withPos(fn.pos)
161+
}
162+
(Nil, call.appliedToArgs(args).withPos(call.pos))
149163
}
150-
Block(argVals, rec(fn))
164+
val (argVals1, expr1) = rec(fn)
165+
seq(argVals1, expr1)
151166
}
152167

153168
private def classToType(clazz: Class[_])(implicit ctx: Context): Type = {
@@ -189,4 +204,12 @@ object PickledQuotes {
189204
case _ => tree
190205
}
191206
}
207+
208+
/** Id of the current compilation (macros) or Expr[_] run */
209+
def contextId(implicit ctx: Context): Int = {
210+
def root(ctx: Context): Context =
211+
if (ctx.outer != NoContext) root(ctx.outer) else ctx
212+
root(ctx).hashCode
213+
}
214+
192215
}

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
@@ -1258,7 +1259,7 @@ class TreeUnpickler(reader: TastyReader,
12581259
val args = until(end)(readTerm())
12591260
val splice = splices(idx)
12601261
def wrap(arg: Tree) =
1261-
if (arg.isTerm) new TastyTreeExpr(arg)
1262+
if (arg.isTerm) new TastyTreeExpr(arg, PickledQuotes.contextId)
12621263
else new TreeType(arg)
12631264
val reifiedArgs = args.map(wrap)
12641265
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.Positions.Position
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 = new SourceFile("", "")
5555
CompilationUnit.mkCompilationUnit(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 = Position(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.

0 commit comments

Comments
 (0)