@@ -37,14 +37,17 @@ object Splicer {
3737 *
3838 * See: `Staging`
3939 */
40- def splice (tree : Tree , pos : SourcePosition , classLoader : ClassLoader )(implicit ctx : Context ): Tree = tree match {
40+ def splice (tree : Tree , pos : SourcePosition , classLoader : ClassLoader )(given ctx : Context ): Tree = tree match {
4141 case Quoted (quotedTree) => quotedTree
4242 case _ =>
4343 val interpreter = new Interpreter (pos, classLoader)
44+ val macroOwner = ctx.newSymbol(ctx.owner, NameKinds .UniqueName .fresh(nme.MACROkw ), Synthetic , defn.AnyType , coord = tree.span)
4445 try {
46+ given Context = ctx.withOwner(macroOwner)
4547 // Some parts of the macro are evaluated during the unpickling performed in quotedExprToTree
4648 val interpretedExpr = interpreter.interpret[scala.quoted.QuoteContext => scala.quoted.Expr [Any ]](tree)
47- interpretedExpr.fold(tree)(macroClosure => PickledQuotes .quotedExprToTree(macroClosure(QuoteContext ())))
49+ val interpretedTree = interpretedExpr.fold(tree)(macroClosure => PickledQuotes .quotedExprToTree(macroClosure(QuoteContext ())))
50+ checkEscapedVariables(interpretedTree, macroOwner).changeOwner(macroOwner, ctx.owner)
4851 }
4952 catch {
5053 case ex : CompilationUnit .SuspendException =>
@@ -63,6 +66,50 @@ object Splicer {
6366 }
6467 }
6568
69+ /** Checks that no symbol that whas generated within the macro expansion has an out of scope reference */
70+ def checkEscapedVariables (tree : Tree , expansionOwner : Symbol )(given ctx : Context ): tree.type =
71+ new TreeTraverser {
72+ private [this ] var locals = Set .empty[Symbol ]
73+ private def markDef (tree : Tree )(implicit ctx : Context ): Unit = tree match {
74+ case tree : DefTree =>
75+ val sym = tree.symbol
76+ if (! locals.contains(sym))
77+ locals = locals + sym
78+ case _ =>
79+ }
80+ def traverse (tree : Tree )(given ctx : Context ): Unit =
81+ def traverseOver (lastEntered : Set [Symbol ]) =
82+ try traverseChildren(tree)
83+ finally locals = lastEntered
84+ tree match
85+ case tree : Ident if isEscapedVariable(tree.symbol) =>
86+ val sym = tree.symbol
87+ ctx.error(em " While expanding a macro, a reference to $sym was used outside the scope where it was defined " , tree.sourcePos)
88+ case Block (stats, _) =>
89+ val last = locals
90+ stats.foreach(markDef)
91+ traverseOver(last)
92+ case CaseDef (pat, guard, body) =>
93+ val last = locals
94+ // mark all bindings
95+ new TreeTraverser {
96+ def traverse (tree : Tree )(implicit ctx : Context ): Unit = {
97+ markDef(tree)
98+ traverseChildren(tree)
99+ }
100+ }.traverse(pat)
101+ traverseOver(last)
102+ case _ =>
103+ markDef(tree)
104+ traverseChildren(tree)
105+ private def isEscapedVariable (sym : Symbol )(given ctx : Context ): Boolean =
106+ sym.exists && ! sym.is(Package )
107+ && sym.owner.ownersIterator.contains(expansionOwner) // symbol was generated within the macro expansion
108+ && ! locals.contains(sym) // symbol is not in current scope
109+ }.traverse(tree)
110+ tree
111+
112+
66113 /** Check that the Tree can be spliced. `${'{xyz}}` becomes `xyz`
67114 * and for `$xyz` the tree of `xyz` is interpreted for which the
68115 * resulting expression is returned as a `Tree`
0 commit comments