-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
Copy pathSplicer.scala
269 lines (232 loc) · 11 KB
/
Splicer.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
package dotty.tools.dotc
package transform
import scala.language.unsafeNulls
import java.io.{PrintWriter, StringWriter}
import java.lang.reflect.{InvocationTargetException, Method => JLRMethod}
import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.core.Contexts.*
import dotty.tools.dotc.core.Decorators.*
import dotty.tools.dotc.core.Flags.*
import dotty.tools.dotc.core.NameKinds.FlatName
import dotty.tools.dotc.core.Names.Name
import dotty.tools.dotc.core.StdNames.*
import dotty.tools.dotc.core.Types.*
import dotty.tools.dotc.core.Symbols.*
import dotty.tools.dotc.core.Denotations.staticRef
import dotty.tools.dotc.core.TypeErasure
import dotty.tools.dotc.core.Constants.Constant
import dotty.tools.dotc.quoted.Interpreter
import scala.util.control.NonFatal
import dotty.tools.dotc.util.SrcPos
import dotty.tools.repl.AbstractFileClassLoader
import scala.reflect.ClassTag
import dotty.tools.dotc.quoted.{PickledQuotes, QuoteUtils}
import scala.quoted.Quotes
import scala.quoted.runtime.impl.*
import dotty.tools.dotc.core.NameKinds
/** Utility class to splice quoted expressions */
object Splicer {
import tpd.*
import Interpreter.*
/** Splice the Tree for a Quoted expression. `${'{xyz}}` becomes `xyz`
* and for `$xyz` the tree of `xyz` is interpreted for which the
* resulting expression is returned as a `Tree`
*
* See: `Staging`
*/
def splice(tree: Tree, splicePos: SrcPos, spliceExpansionPos: SrcPos, classLoader: ClassLoader)(using Context): Tree = tree match {
case Quote(quotedTree, Nil) => quotedTree
case _ =>
val macroOwner = newSymbol(ctx.owner, nme.MACROkw, Macro | Synthetic, defn.AnyType, coord = tree.span)
try
val sliceContext = SpliceScope.contextWithNewSpliceScope(splicePos.sourcePos).withOwner(macroOwner)
inContext(sliceContext) {
val oldContextClassLoader = Thread.currentThread().getContextClassLoader
Thread.currentThread().setContextClassLoader(classLoader)
try {
val interpreter = new SpliceInterpreter(splicePos, classLoader)
// Some parts of the macro are evaluated during the unpickling performed in quotedExprToTree
val interpretedExpr = interpreter.interpret[Quotes => scala.quoted.Expr[Any]](tree)
val interpretedTree = interpretedExpr.fold(tree)(macroClosure => PickledQuotes.quotedExprToTree(macroClosure(QuotesImpl())))
checkEscapedVariables(interpretedTree, macroOwner)
} finally {
Thread.currentThread().setContextClassLoader(oldContextClassLoader)
}
}.changeOwner(macroOwner, ctx.owner)
catch {
case ex: CompilationUnit.SuspendException =>
throw ex
case ex: scala.quoted.runtime.StopMacroExpansion =>
if !ctx.reporter.hasErrors then
report.error("Macro expansion was aborted by the macro without any errors reported. Macros should issue errors to end-users when aborting a macro expansion with StopMacroExpansion.", splicePos)
// errors have been emitted
ref(defn.Predef_undefined).withType(ErrorType(em"macro expansion was stopped"))
case ex: StopInterpretation =>
report.error(ex.msg, ex.pos)
ref(defn.Predef_undefined).withType(ErrorType(ex.msg))
case NonFatal(ex) =>
val msg =
em"""Failed to evaluate macro.
| Caused by ${ex.getClass}: ${if (ex.getMessage == null) "" else ex.getMessage}
| ${ex.getStackTrace.takeWhile(_.getClassName != "dotty.tools.dotc.transform.Splicer$").drop(1).mkString("\n ")}
"""
report.error(msg, spliceExpansionPos)
ref(defn.Predef_undefined).withType(ErrorType(msg))
}
}
/** Checks that no symbol that was generated within the macro expansion has an out of scope reference */
def checkEscapedVariables(tree: Tree, expansionOwner: Symbol)(using Context): tree.type =
new TreeTraverser {
private[this] var locals = Set.empty[Symbol]
private def markSymbol(sym: Symbol)(using Context): Unit =
locals = locals + sym
private def markDef(tree: Tree)(using Context): Unit = tree match {
case tree: DefTree => markSymbol(tree.symbol)
case _ =>
}
def traverse(tree: Tree)(using Context): Unit =
def traverseOver(lastEntered: Set[Symbol]) =
try traverseChildren(tree)
finally locals = lastEntered
tree match
case tree: Ident if isEscapedVariable(tree.symbol) =>
val sym = tree.symbol
report.error(em"While expanding a macro, a reference to $sym was used outside the scope where it was defined", tree.srcPos)
case Block(stats, _) =>
val last = locals
stats.foreach(markDef)
traverseOver(last)
case CaseDef(pat, guard, body) =>
val last = locals
tpd.patVars(pat).foreach(markSymbol)
traverseOver(last)
case _ =>
markDef(tree)
traverseChildren(tree)
private def isEscapedVariable(sym: Symbol)(using Context): Boolean =
sym.exists && !sym.is(Package)
&& sym.owner.ownersIterator.exists(x =>
x == expansionOwner || // symbol was generated within this macro expansion
{ // symbol was generated within another macro expansion
isMacroOwner(x) &&
!ctx.owner.ownersIterator.contains(x)
}
)
&& !locals.contains(sym) // symbol is not in current scope
}.traverse(tree)
tree
/** Check that the Tree can be spliced. `${'{xyz}}` becomes `xyz`
* and for `$xyz` the tree of `xyz` is interpreted for which the
* resulting expression is returned as a `Tree`
*
* See: `Staging`
*/
def checkValidMacroBody(tree: Tree)(using Context): Unit = tree match {
case Quote(_, Nil) => // ok
case _ =>
type Env = Set[Symbol]
def checkValidStat(tree: Tree)(using Env): Env = tree match {
case tree: ValDef if tree.symbol.is(Synthetic) =>
// Check val from `foo(j = x, i = y)` which it is expanded to
// `val j$1 = x; val i$1 = y; foo(i = i$1, j = j$1)`
checkIfValidArgument(tree.rhs)
summon[Env] + tree.symbol
case _ =>
report.error("Macro should not have statements", tree.srcPos)
summon[Env]
}
def checkIfValidArgument(tree: Tree)(using Env): Unit = tree match {
case Block(Nil, expr) => checkIfValidArgument(expr)
case Typed(expr, _) => checkIfValidArgument(expr)
case Apply(Select(Quote(body, _), nme.apply), _) =>
val noSpliceChecker = new TreeTraverser {
def traverse(tree: Tree)(using Context): Unit = tree match
case Splice(_) =>
report.error("Quoted argument of macros may not have splices", tree.srcPos)
case _ =>
traverseChildren(tree)
}
noSpliceChecker.traverse(body)
case Apply(TypeApply(fn, List(quoted)), _)if fn.symbol == defn.QuotedTypeModule_of =>
// OK
case Literal(Constant(value)) =>
// OK
case NamedArg(_, arg) =>
checkIfValidArgument(arg)
case SeqLiteral(elems, _) =>
elems.foreach(checkIfValidArgument)
case tree: Ident if summon[Env].contains(tree.symbol) || tree.symbol.is(Inline, butNot = Method) =>
// OK
case _ =>
val extra = if tree.span.isZeroExtent then ": " + tree.show else ""
report.error(
s"""Malformed macro parameter$extra
|
|Parameters may only be:
| * Quoted parameters or fields
| * Literal values of primitive types
| * References to `inline val`s
|""".stripMargin, tree.srcPos)
}
def checkIfValidStaticCall(tree: Tree)(using Env): Unit = tree match {
case closureDef(ddef @ DefDef(_, ValDefs(ev :: Nil) :: Nil, _, _)) if ddef.symbol.info.isContextualMethod =>
checkIfValidStaticCall(ddef.rhs)(using summon[Env] + ev.symbol)
case Block(stats, expr) =>
val newEnv = stats.foldLeft(summon[Env])((env, stat) => checkValidStat(stat)(using env))
checkIfValidStaticCall(expr)(using newEnv)
case Typed(expr, _) =>
checkIfValidStaticCall(expr)
case Apply(Select(Quote(quoted, Nil), nme.apply), _) =>
// OK, canceled and warning emitted
case Call(fn, args)
if (fn.symbol.isConstructor && fn.symbol.owner.owner.is(Package)) ||
fn.symbol.is(Module) || fn.symbol.isStatic ||
(fn.qualifier.symbol.is(Module) && fn.qualifier.symbol.isStatic) =>
if (fn.symbol.flags.is(Inline))
report.error("Macro cannot be implemented with an `inline` method", fn.srcPos)
args.flatten.foreach(checkIfValidArgument)
case Call(fn, args) if fn.symbol.name.is(NameKinds.InlineAccessorName) =>
// TODO suggest use of @binaryAPI once we have the annotation
report.error(
i"""Macro implementation is not statically accessible.
|
|Non-static inline accessor was generated in ${fn.symbol.owner}
|""".stripMargin, tree.srcPos)
case _ =>
report.error(
"""Malformed macro.
|
|Expected the splice ${...} to contain a single call to a static method.
|""".stripMargin, tree.srcPos)
}
checkIfValidStaticCall(tree)(using Set.empty)
}
/** Is this the dummy owner of a macro expansion */
def isMacroOwner(sym: Symbol)(using Context): Boolean =
sym.is(Macro, butNot = Method) && sym.name == nme.MACROkw
/** Is this the dummy owner of a macro expansion */
def inMacroExpansion(using Context) =
ctx.owner.ownersIterator.exists(isMacroOwner)
/** Tree interpreter that evaluates the tree.
* Interpreter is assumed to start at quotation level -1.
*/
private class SpliceInterpreter(pos: SrcPos, classLoader: ClassLoader)(using Context) extends Interpreter(pos, classLoader) {
override protected def interpretTree(tree: Tree)(implicit env: Env): Object = tree match {
// Interpret level -1 quoted code `'{...}` (assumed without level 0 splices)
case Apply(Select(Quote(body, _), nme.apply), _) =>
val body1 = body match {
case expr: Ident if expr.symbol.isAllOf(InlineByNameProxy) =>
// inline proxy for by-name parameter
expr.symbol.defTree.asInstanceOf[DefDef].rhs
case tree: Inlined if tree.inlinedFromOuterScope => tree.expansion
case _ => body
}
new ExprImpl(Inlined(EmptyTree, Nil, QuoteUtils.changeOwnerOfTree(body1, ctx.owner)).withSpan(body1.span), SpliceScope.getCurrent)
// Interpret level -1 `Type.of[T]`
case Apply(TypeApply(fn, quoted :: Nil), _) if fn.symbol == defn.QuotedTypeModule_of =>
new TypeImpl(QuoteUtils.changeOwnerOfTree(quoted, ctx.owner), SpliceScope.getCurrent)
case _ =>
super.interpretTree(tree)
}
}
}