Skip to content

Commit 195597e

Browse files
Merge pull request scala#6881 from dotty-staging/contextualize-splices
Contextualize quote and splice internals
2 parents b3826d0 + 80e42bb commit 195597e

Some content is hidden

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

43 files changed

+257
-201
lines changed
Submodule scalatest updated 36 files

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import dotty.tools.dotc.core.Types._
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
17+
import dotty.tools.dotc.tastyreflect.ReflectionImpl
1718

1819
import scala.internal.quoted._
1920
import scala.reflect.ClassTag
@@ -47,7 +48,7 @@ object PickledQuotes {
4748
forceAndCleanArtefacts.transform(unpickled)
4849
case expr: TastyTreeExpr[Tree] @unchecked => healOwner(expr.tree)
4950
case expr: FunctionAppliedTo[_] =>
50-
functionAppliedTo(quotedExprToTree(expr.f), expr.args.map(arg => quotedExprToTree(arg)).toList)
51+
functionAppliedTo(quotedExprToTree(expr.f), expr.args.map(arg => quotedExprToTree(arg(new scala.quoted.QuoteContext(ReflectionImpl(ctx))))).toList)
5152
}
5253

5354
/** Transform the expression into its fully spliced TypeTree */

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1273,14 +1273,15 @@ class TreeUnpickler(reader: TastyReader,
12731273
val args = until(end)(readTerm())
12741274
val splice = splices(idx)
12751275
def wrap(arg: Tree) =
1276-
if (arg.isTerm) new TastyTreeExpr(arg)
1276+
if (arg.isTerm) given (qctx: scala.quoted.QuoteContext) => new TastyTreeExpr(arg)
12771277
else new TreeType(arg)
12781278
val reifiedArgs = args.map(wrap)
12791279
val filled = if (isType) {
12801280
val quotedType = splice.asInstanceOf[Seq[Any] => quoted.Type[_]](reifiedArgs)
12811281
PickledQuotes.quotedTypeToTree(quotedType)
12821282
} else {
1283-
val quotedExpr = splice.asInstanceOf[Seq[Any] => quoted.Expr[_]](reifiedArgs)
1283+
val splice1 = splice.asInstanceOf[Seq[Any] => given scala.quoted.QuoteContext => quoted.Expr[_]]
1284+
val quotedExpr = splice1(reifiedArgs) given new scala.quoted.QuoteContext(tastyreflect.ReflectionImpl(ctx))
12841285
PickledQuotes.quotedExprToTree(quotedExpr)
12851286
}
12861287
// We need to make sure a hole is created with the source file of the surrounding context, even if

compiler/src/dotty/tools/dotc/tastyreflect/KernelImpl.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,19 @@ import dotty.tools.dotc.core.Decorators._
1414
import dotty.tools.dotc.tastyreflect.FromSymbol.{definitionFromSym, packageDefFromSym}
1515
import dotty.tools.dotc.parsing.Parsers.Parser
1616
import dotty.tools.dotc.typer.Implicits.{AmbiguousImplicits, DivergingImplicit, NoMatchingImplicits, SearchFailure, SearchFailureType}
17-
import dotty.tools.dotc.util.SourceFile
17+
import dotty.tools.dotc.util.{SourceFile, SourcePosition, Spans}
1818

1919
import scala.tasty.reflect.Kernel
2020

21-
class KernelImpl(val rootContext: core.Contexts.Context, val rootPosition: util.SourcePosition) extends Kernel {
21+
class KernelImpl(val rootContext: core.Contexts.Context) extends Kernel {
2222

2323
private implicit def ctx: core.Contexts.Context = rootContext
2424

2525
def settings: Settings = rootContext.settings
2626

27+
def rootPosition: util.SourcePosition =
28+
tastyreflect.MacroExpansion.position.getOrElse(SourcePosition(rootContext.source, Spans.NoSpan))
29+
2730
//
2831
// CONTEXT
2932
//
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package dotty.tools.dotc.tastyreflect
2+
3+
import dotty.tools.dotc.ast.tpd
4+
import dotty.tools.dotc.core._
5+
import dotty.tools.dotc.core.Contexts._
6+
import dotty.tools.dotc.util.{Property, SourcePosition, Spans}
7+
8+
object MacroExpansion {
9+
10+
private val MacroExpansionPosition = new Property.Key[SourcePosition]
11+
12+
def position(implicit ctx: Context): Option[SourcePosition] =
13+
ctx.property(MacroExpansionPosition)
14+
15+
def context(inlinedFrom: tpd.Tree)(implicit ctx: Context): Context =
16+
ctx.fresh.setProperty(MacroExpansionPosition, SourcePosition(inlinedFrom.source, inlinedFrom.span)).withSource(inlinedFrom.source)
17+
18+
}

compiler/src/dotty/tools/dotc/tastyreflect/ReflectionImpl.scala

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,17 @@ package dotty.tools.dotc.tastyreflect
22

33
import dotty.tools.dotc.ast.tpd
44
import dotty.tools.dotc.core._
5-
import dotty.tools.dotc.util.{SourcePosition, Spans}
5+
import dotty.tools.dotc.core.Contexts._
66

77
import scala.quoted.show.SyntaxHighlight
88

99
object ReflectionImpl {
1010

1111
def apply(rootContext: Contexts.Context): scala.tasty.Reflection =
12-
apply(rootContext, SourcePosition(rootContext.source, Spans.NoSpan))
13-
14-
def apply(rootContext: Contexts.Context, rootPosition: SourcePosition): scala.tasty.Reflection =
15-
new scala.tasty.Reflection(new KernelImpl(rootContext, rootPosition))
12+
new scala.tasty.Reflection(new KernelImpl(rootContext))
1613

1714
def showTree(tree: tpd.Tree)(implicit ctx: Contexts.Context): String = {
18-
val refl = new scala.tasty.Reflection(new KernelImpl(ctx, tree.sourcePos))
15+
val refl = new scala.tasty.Reflection(new KernelImpl(MacroExpansion.context(tree)))
1916
val reflCtx = ctx.asInstanceOf[refl.Context]
2017
val reflTree = tree.asInstanceOf[refl.Tree]
2118
val syntaxHighlight =

compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,6 @@ class ReifyQuotes extends MacroTransform {
177177
*/
178178
override protected def transformQuotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = {
179179
val isType = quote.symbol eq defn.InternalQuoted_typeQuote
180-
assert(!(body.symbol.isSplice && (body.isInstanceOf[GenericApply[_]] || body.isInstanceOf[Select])))
181180
if (level > 0) {
182181
val body1 = nested(isQuote = true).transform(body)(quoteContext)
183182
super.transformQuotation(body1, quote)
@@ -209,35 +208,37 @@ class ReifyQuotes extends MacroTransform {
209208
qctx
210209
}
211210

212-
def liftedValue[T](value: T, name: TermName, qctx: Tree) =
213-
ref(defn.LiftableModule).select(name).select("toExpr".toTermName).appliedTo(Literal(Constant(value))).select(nme.apply).appliedTo(qctx)
214-
215-
def pickleAsValue[T](value: T) = value match {
216-
case null => ref(defn.QuotedExprModule).select("nullExpr".toTermName).appliedTo(qctx)
217-
case _: Unit => ref(defn.QuotedExprModule).select("unitExpr".toTermName).appliedTo(qctx)
218-
case _: Boolean => liftedValue(value, "Liftable_Boolean_delegate".toTermName, qctx)
219-
case _: Byte => liftedValue(value, "Liftable_Byte_delegate".toTermName, qctx)
220-
case _: Short => liftedValue(value, "Liftable_Short_delegate".toTermName, qctx)
221-
case _: Int => liftedValue(value, "Liftable_Int_delegate".toTermName, qctx)
222-
case _: Long => liftedValue(value, "Liftable_Long_delegate".toTermName, qctx)
223-
case _: Float => liftedValue(value, "Liftable_Float_delegate".toTermName, qctx)
224-
case _: Double => liftedValue(value, "Liftable_Double_delegate".toTermName, qctx)
225-
case _: Char => liftedValue(value, "Liftable_Char_delegate".toTermName, qctx)
226-
case _: String => liftedValue(value, "Liftable_String_delegate".toTermName, qctx)
211+
def liftedValue[T](value: T, name: TermName) =
212+
ref(defn.LiftableModule).select(name).select("toExpr".toTermName).appliedTo(Literal(Constant(value)))
213+
214+
def pickleAsValue[T](value: T) = {
215+
value match {
216+
case null => ref(defn.QuotedExprModule).select("nullExpr".toTermName)
217+
case _: Unit => ref(defn.QuotedExprModule).select("unitExpr".toTermName)
218+
case _: Boolean => liftedValue(value, "Liftable_Boolean_delegate".toTermName)
219+
case _: Byte => liftedValue(value, "Liftable_Byte_delegate".toTermName)
220+
case _: Short => liftedValue(value, "Liftable_Short_delegate".toTermName)
221+
case _: Int => liftedValue(value, "Liftable_Int_delegate".toTermName)
222+
case _: Long => liftedValue(value, "Liftable_Long_delegate".toTermName)
223+
case _: Float => liftedValue(value, "Liftable_Float_delegate".toTermName)
224+
case _: Double => liftedValue(value, "Liftable_Double_delegate".toTermName)
225+
case _: Char => liftedValue(value, "Liftable_Char_delegate".toTermName)
226+
case _: String => liftedValue(value, "Liftable_String_delegate".toTermName)
227+
}
227228
}
228229

229230
def pickleAsTasty() = {
230231
val meth =
231232
if (isType) ref(defn.Unpickler_unpickleType).appliedToType(originalTp)
232233
else ref(defn.Unpickler_unpickleExpr).appliedToType(originalTp.widen)
233-
def wildcardQuotedType = defn.QuotedTypeClass.typeRef.appliedTo(WildcardType)
234234
val spliceResType =
235-
if (isType) wildcardQuotedType
236-
else defn.QuotedExprClass.typeRef.appliedTo(defn.AnyType) | wildcardQuotedType
237-
meth.appliedTo(
238-
liftList(PickledQuotes.pickleQuote(body).map(x => Literal(Constant(x))), defn.StringType),
239-
liftList(splices, defn.FunctionType(1).appliedTo(defn.SeqType.appliedTo(defn.AnyType), spliceResType)))
235+
if (isType) defn.QuotedTypeClass.typeRef.appliedTo(WildcardType)
236+
else defn.FunctionType(1, isContextual = true).appliedTo(defn.QuoteContextClass.typeRef, defn.QuotedExprClass.typeRef.appliedTo(defn.AnyType)) | defn.QuotedTypeClass.typeRef.appliedTo(WildcardType)
237+
val pickledQuoteStrings = liftList(PickledQuotes.pickleQuote(body).map(x => Literal(Constant(x))), defn.StringType)
238+
val splicesList = liftList(splices, defn.FunctionType(1).appliedTo(defn.SeqType.appliedTo(defn.AnyType), spliceResType))
239+
meth.appliedTo(pickledQuoteStrings, splicesList)
240240
}
241+
241242
if (splices.nonEmpty) pickleAsTasty()
242243
else if (isType) {
243244
def tag(tagName: String) = ref(defn.QuotedTypeModule).select(tagName.toTermName).appliedTo(qctx)
@@ -316,7 +317,7 @@ class ReifyQuotes extends MacroTransform {
316317
assert(tpw.isInstanceOf[ValueType])
317318
val argTpe =
318319
if (tree.isType) defn.QuotedTypeClass.typeRef.appliedTo(tpw)
319-
else defn.QuotedExprClass.typeRef.appliedTo(tpw)
320+
else defn.FunctionType(1, isContextual = true).appliedTo(defn.QuoteContextClass.typeRef, defn.QuotedExprClass.typeRef.appliedTo(tpw))
320321
val selectArg = arg.select(nme.apply).appliedTo(Literal(Constant(i))).cast(argTpe)
321322
val capturedArg = SyntheticValDef(UniqueName.fresh(tree.symbol.name.toTermName).toTermName, selectArg)
322323
i += 1
@@ -357,7 +358,9 @@ class ReifyQuotes extends MacroTransform {
357358
val tree2 = transform(tree)
358359
capturers --= outer.localSymbols
359360

360-
seq(captured.result().valuesIterator.toList, tree2)
361+
val captures = captured.result().valuesIterator.toList
362+
if (captures.isEmpty) tree2
363+
else Block(captures, tree2)
361364
}
362365

363366
/** Returns true if this tree will be captured by `makeLambda`. Checks phase consistency and presence of capturer. */

compiler/src/dotty/tools/dotc/transform/Splicer.scala

Lines changed: 47 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,18 @@ object Splicer {
4141
val interpreter = new Interpreter(pos, classLoader)
4242
try {
4343
// Some parts of the macro are evaluated during the unpickling performed in quotedExprToTree
44-
val interpretedExpr = interpreter.interpret[scala.quoted.Expr[Any]](tree)
45-
interpretedExpr.fold(tree)(x => PickledQuotes.quotedExprToTree(x))
44+
val interpretedExpr = interpreter.interpret[scala.quoted.QuoteContext => scala.quoted.Expr[Any]](tree)
45+
interpretedExpr.fold(tree)(macroClosure => PickledQuotes.quotedExprToTree(macroClosure(new scala.quoted.QuoteContext(ReflectionImpl(ctx)))))
4646
}
4747
catch {
48+
case ex: StopInterpretation =>
49+
ctx.error(ex.msg, ex.pos)
50+
EmptyTree
4851
case NonFatal(ex) =>
4952
val msg =
5053
s"""Failed to evaluate macro.
5154
| Caused by ${ex.getClass}: ${if (ex.getMessage == null) "" else ex.getMessage}
52-
| ${ex.getStackTrace.takeWhile(_.getClassName != "dotty.tools.dotc.transform.Splicer$").init.mkString("\n ")}
55+
| ${ex.getStackTrace.takeWhile(_.getClassName != "dotty.tools.dotc.transform.Splicer$").drop(1).mkString("\n ")}
5356
""".stripMargin
5457
ctx.error(msg, pos)
5558
EmptyTree
@@ -65,19 +68,24 @@ object Splicer {
6568
def checkValidMacroBody(tree: Tree)(implicit ctx: Context): Unit = tree match {
6669
case Quoted(_) => // ok
6770
case _ =>
68-
def checkValidStat(tree: Tree): Unit = tree match {
71+
type Env = Set[Symbol]
72+
73+
def checkValidStat(tree: Tree) given Env: Env = tree match {
6974
case tree: ValDef if tree.symbol.is(Synthetic) =>
7075
// Check val from `foo(j = x, i = y)` which it is expanded to
7176
// `val j$1 = x; val i$1 = y; foo(i = i$1, j = j$1)`
7277
checkIfValidArgument(tree.rhs)
78+
the[Env] + tree.symbol
7379
case _ =>
7480
ctx.error("Macro should not have statements", tree.sourcePos)
81+
the[Env]
7582
}
76-
def checkIfValidArgument(tree: Tree): Unit = tree match {
83+
84+
def checkIfValidArgument(tree: Tree) given Env: Unit = tree match {
7785
case Block(Nil, expr) => checkIfValidArgument(expr)
7886
case Typed(expr, _) => checkIfValidArgument(expr)
7987

80-
case Apply(TypeApply(fn, _), quoted :: Nil) if fn.symbol == defn.InternalQuoted_exprQuote =>
88+
case Apply(Select(Apply(fn, quoted :: Nil), nme.apply), _) if fn.symbol == defn.InternalQuoted_exprQuote =>
8189
// OK
8290

8391
case TypeApply(fn, quoted :: Nil) if fn.symbol == defn.InternalQuoted_typeQuote =>
@@ -101,7 +109,7 @@ object Splicer {
101109
case SeqLiteral(elems, _) =>
102110
elems.foreach(checkIfValidArgument)
103111

104-
case tree: Ident if tree.symbol.is(Inline) || tree.symbol.is(Synthetic) =>
112+
case tree: Ident if tree.symbol.is(Inline) || the[Env].contains(tree.symbol) =>
105113
// OK
106114

107115
case _ =>
@@ -114,10 +122,14 @@ object Splicer {
114122
| * Literal values of primitive types
115123
|""".stripMargin, tree.sourcePos)
116124
}
117-
def checkIfValidStaticCall(tree: Tree): Unit = tree match {
125+
126+
def checkIfValidStaticCall(tree: Tree) given Env: Unit = tree match {
127+
case closureDef(ddef @ DefDef(_, Nil, (ev :: Nil) :: Nil, _, _)) if ddef.symbol.info.isContextualMethod =>
128+
checkIfValidStaticCall(ddef.rhs) given (the[Env] + ev.symbol)
129+
118130
case Block(stats, expr) =>
119-
stats.foreach(checkValidStat)
120-
checkIfValidStaticCall(expr)
131+
val newEnv = stats.foldLeft(the[Env])((env, stat) => checkValidStat(stat) given env)
132+
checkIfValidStaticCall(expr) given newEnv
121133

122134
case Typed(expr, _) =>
123135
checkIfValidStaticCall(expr)
@@ -126,6 +138,8 @@ object Splicer {
126138
if (fn.symbol.isConstructor && fn.symbol.owner.owner.is(Package)) ||
127139
fn.symbol.is(Module) || fn.symbol.isStatic ||
128140
(fn.qualifier.symbol.is(Module) && fn.qualifier.symbol.isStatic) =>
141+
if (fn.symbol.flags.is(Inline))
142+
ctx.error("Macro cannot be implemented with an `inline` method", fn.sourcePos)
129143
args.flatten.foreach(checkIfValidArgument)
130144

131145
case _ =>
@@ -136,35 +150,29 @@ object Splicer {
136150
|""".stripMargin, tree.sourcePos)
137151
}
138152

139-
checkIfValidStaticCall(tree)
153+
checkIfValidStaticCall(tree) given Set.empty
140154
}
141155

142156
/** Tree interpreter that evaluates the tree */
143157
private class Interpreter(pos: SourcePosition, classLoader: ClassLoader)(implicit ctx: Context) {
144158

145-
type Env = Map[Name, Object]
159+
type Env = Map[Symbol, Object]
146160

147161
/** Returns the interpreted result of interpreting the code a call to the symbol with default arguments.
148162
* Return Some of the result or None if some error happen during the interpretation.
149163
*/
150164
def interpret[T](tree: Tree)(implicit ct: ClassTag[T]): Option[T] = {
151-
try {
152-
interpretTree(tree)(Map.empty) match {
153-
case obj: T => Some(obj)
154-
case obj =>
155-
// TODO upgrade to a full type tag check or something similar
156-
ctx.error(s"Interpreted tree returned a result of an unexpected type. Expected ${ct.runtimeClass} but was ${obj.getClass}", pos)
157-
None
158-
}
159-
} catch {
160-
case ex: StopInterpretation =>
161-
ctx.error(ex.msg, ex.pos)
165+
interpretTree(tree)(Map.empty) match {
166+
case obj: T => Some(obj)
167+
case obj =>
168+
// TODO upgrade to a full type tag check or something similar
169+
ctx.error(s"Interpreted tree returned a result of an unexpected type. Expected ${ct.runtimeClass} but was ${obj.getClass}", pos)
162170
None
163171
}
164172
}
165173

166174
def interpretTree(tree: Tree)(implicit env: Env): Object = tree match {
167-
case Apply(TypeApply(fn, _), quoted :: Nil) if fn.symbol == defn.InternalQuoted_exprQuote =>
175+
case Apply(Select(Apply(TypeApply(fn, _), quoted :: Nil), nme.apply), _) if fn.symbol == defn.InternalQuoted_exprQuote =>
168176
val quoted1 = quoted match {
169177
case quoted: Ident if quoted.symbol.isAllOf(InlineByNameProxy) =>
170178
// inline proxy for by-name parameter
@@ -199,14 +207,17 @@ object Splicer {
199207
val staticMethodCall = interpretedStaticMethodCall(fn.qualifier.symbol.moduleClass, fn.symbol)
200208
staticMethodCall(args.flatten.map(interpretTree))
201209
}
202-
} else if (env.contains(fn.name)) {
203-
env(fn.name)
210+
} else if (env.contains(fn.symbol)) {
211+
env(fn.symbol)
204212
} else if (tree.symbol.is(InlineProxy)) {
205213
interpretTree(tree.symbol.defTree.asInstanceOf[ValOrDefDef].rhs)
206214
} else {
207215
unexpectedTree(tree)
208216
}
209217

218+
case closureDef((ddef @ DefDef(_, _, (arg :: Nil) :: Nil, _, _))) =>
219+
(obj: AnyRef) => interpretTree(ddef.rhs) given env.updated(arg.symbol, obj)
220+
210221
// Interpret `foo(j = x, i = y)` which it is expanded to
211222
// `val j$1 = x; val i$1 = y; foo(i = i$1, j = j$1)`
212223
case Block(stats, expr) => interpretBlock(stats, expr)
@@ -228,7 +239,7 @@ object Splicer {
228239
var unexpected: Option[Object] = None
229240
val newEnv = stats.foldLeft(env)((accEnv, stat) => stat match {
230241
case stat: ValDef =>
231-
accEnv.updated(stat.name, interpretTree(stat.rhs)(accEnv))
242+
accEnv.updated(stat.symbol, interpretTree(stat.rhs)(accEnv))
232243
case stat =>
233244
if (unexpected.isEmpty)
234245
unexpected = Some(unexpectedTree(stat))
@@ -250,7 +261,7 @@ object Splicer {
250261
args.toSeq
251262

252263
private def interpretQuoteContext()(implicit env: Env): Object =
253-
new scala.quoted.QuoteContext(ReflectionImpl(ctx, pos))
264+
new scala.quoted.QuoteContext(ReflectionImpl(ctx))
254265

255266
private def interpretedStaticMethodCall(moduleClass: Symbol, fn: Symbol)(implicit env: Env): List[Object] => Object = {
256267
val (inst, clazz) =
@@ -339,10 +350,13 @@ object Splicer {
339350
throw new StopInterpretation(sw.toString, pos)
340351
case ex: InvocationTargetException =>
341352
val sw = new StringWriter()
342-
sw.write("An exception occurred while executing macro expansion\n")
353+
sw.write("An exception occurred while executing macro expansion: ")
343354
sw.write(ex.getTargetException.getMessage)
344355
sw.write("\n")
345-
ex.getTargetException.printStackTrace(new PrintWriter(sw))
356+
for (stack <- ex.getTargetException.getStackTrace.iterator.takeWhile(_.getClassName != this.getClass.getName).drop(1)) {
357+
sw.write(stack.toString)
358+
sw.write("\n")
359+
}
346360
sw.write("\n")
347361
throw new StopInterpretation(sw.toString, pos)
348362
}
@@ -404,11 +418,12 @@ object Splicer {
404418
allParams.map(paramClass)
405419
}
406420

407-
/** Exception that stops interpretation if some issue is found */
408-
private class StopInterpretation(val msg: String, val pos: SourcePosition) extends Exception
409421

410422
}
411423

424+
/** Exception that stops interpretation if some issue is found */
425+
private class StopInterpretation(val msg: String, val pos: SourcePosition) extends Exception
426+
412427
object Call {
413428
/** Matches an expression that is either a field access or an application
414429
* It retruns a TermRef containing field accessed or a method reference and the arguments passed to it.

0 commit comments

Comments
 (0)