@@ -37,14 +37,17 @@ object Splicer {
37
37
*
38
38
* See: `Staging`
39
39
*/
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 {
41
41
case Quoted (quotedTree) => quotedTree
42
42
case _ =>
43
43
val interpreter = new Interpreter (pos, classLoader)
44
+ val macroOwner = ctx.newSymbol(ctx.owner, NameKinds .UniqueName .fresh(nme.MACROkw ), Synthetic , defn.AnyType , coord = tree.span)
44
45
try {
46
+ given Context = ctx.withOwner(macroOwner)
45
47
// Some parts of the macro are evaluated during the unpickling performed in quotedExprToTree
46
48
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)
48
51
}
49
52
catch {
50
53
case ex : CompilationUnit .SuspendException =>
@@ -63,6 +66,43 @@ object Splicer {
63
66
}
64
67
}
65
68
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 markSymbol (sym : Symbol )(implicit ctx : Context ): Unit =
74
+ locals = locals + sym
75
+ private def markDef (tree : Tree )(implicit ctx : Context ): Unit = tree match {
76
+ case tree : DefTree => markSymbol(tree.symbol)
77
+ case _ =>
78
+ }
79
+ def traverse (tree : Tree )(given ctx : Context ): Unit =
80
+ def traverseOver (lastEntered : Set [Symbol ]) =
81
+ try traverseChildren(tree)
82
+ finally locals = lastEntered
83
+ tree match
84
+ case tree : Ident if isEscapedVariable(tree.symbol) =>
85
+ val sym = tree.symbol
86
+ ctx.error(em " While expanding a macro, a reference to $sym was used outside the scope where it was defined " , tree.sourcePos)
87
+ case Block (stats, _) =>
88
+ val last = locals
89
+ stats.foreach(markDef)
90
+ traverseOver(last)
91
+ case CaseDef (pat, guard, body) =>
92
+ val last = locals
93
+ tpd.patVars(pat).foreach(markSymbol)
94
+ traverseOver(last)
95
+ case _ =>
96
+ markDef(tree)
97
+ traverseChildren(tree)
98
+ private def isEscapedVariable (sym : Symbol )(given ctx : Context ): Boolean =
99
+ sym.exists && ! sym.is(Package )
100
+ && sym.owner.ownersIterator.contains(expansionOwner) // symbol was generated within the macro expansion
101
+ && ! locals.contains(sym) // symbol is not in current scope
102
+ }.traverse(tree)
103
+ tree
104
+
105
+
66
106
/** Check that the Tree can be spliced. `${'{xyz}}` becomes `xyz`
67
107
* and for `$xyz` the tree of `xyz` is interpreted for which the
68
108
* resulting expression is returned as a `Tree`
0 commit comments