@@ -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,50 @@ 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 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
+
66
113
/** Check that the Tree can be spliced. `${'{xyz}}` becomes `xyz`
67
114
* and for `$xyz` the tree of `xyz` is interpreted for which the
68
115
* resulting expression is returned as a `Tree`
0 commit comments