Skip to content

Commit 4b0fe4d

Browse files
Fix macro annotations spliceOwner (#16513)
The splice owner of the macro annotation should be the owner of the annotated definition. The issue was that if that owner was a class quotes unpickled with this context accidentally entered definitions in the quotes into the class. Now we unpickle these definitions using a dummy val owner (similar to the LocalDummy).
2 parents ddc96b9 + 9431066 commit 4b0fe4d

File tree

17 files changed

+99
-47
lines changed

17 files changed

+99
-47
lines changed

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

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import dotty.tools.dotc.ast.{TreeTypeMap, tpd}
55
import dotty.tools.dotc.config.Printers._
66
import dotty.tools.dotc.core.Contexts._
77
import dotty.tools.dotc.core.Decorators._
8+
import dotty.tools.dotc.core.Flags._
89
import dotty.tools.dotc.core.Mode
910
import dotty.tools.dotc.core.Symbols._
1011
import dotty.tools.dotc.core.Types._
@@ -248,23 +249,41 @@ object PickledQuotes {
248249
case pickled: String => TastyString.unpickle(pickled)
249250
case pickled: List[String] => TastyString.unpickle(pickled)
250251

251-
quotePickling.println(s"**** unpickling quote from TASTY\n${TastyPrinter.showContents(bytes, ctx.settings.color.value == "never")}")
252+
val unpicklingContext =
253+
if ctx.owner.isClass then
254+
// When a quote is unpickled with a Quotes context that that has a class `spliceOwner`
255+
// we need to use a dummy owner to unpickle it. Otherwise any definitions defined
256+
// in the quoted block would be accidentally entered in the class.
257+
// When splicing this expression, this owner is replaced with the correct owner (see `quotedExprToTree` and `quotedTypeToTree` above).
258+
// On the other hand, if the expression is used as a reflect term, the user must call `changeOwner` (same as with other expressions used within a nested owner).
259+
// `-Xcheck-macros` will check for inconsistent owners and provide the users hints on how to improve them.
260+
//
261+
// Quotes context that that has a class `spliceOwner` can come from a macro annotation
262+
// or a user setting it explicitly using `Symbol.asQuotes`.
263+
ctx.withOwner(newSymbol(ctx.owner, "$quoteOwnedByClass$".toTermName, Private, defn.AnyType, NoSymbol))
264+
else ctx
252265

253-
val mode = if (isType) UnpickleMode.TypeTree else UnpickleMode.Term
254-
val unpickler = new DottyUnpickler(bytes, mode)
255-
unpickler.enter(Set.empty)
266+
inContext(unpicklingContext) {
256267

257-
val tree = unpickler.tree
258-
QuotesCache(pickled) = tree
268+
quotePickling.println(s"**** unpickling quote from TASTY\n${TastyPrinter.showContents(bytes, ctx.settings.color.value == "never")}")
259269

260-
// Make sure trees and positions are fully loaded
261-
new TreeTraverser {
262-
def traverse(tree: Tree)(using Context): Unit = traverseChildren(tree)
263-
}.traverse(tree)
270+
val mode = if (isType) UnpickleMode.TypeTree else UnpickleMode.Term
271+
val unpickler = new DottyUnpickler(bytes, mode)
272+
unpickler.enter(Set.empty)
264273

265-
quotePickling.println(i"**** unpickled quote\n$tree")
274+
val tree = unpickler.tree
275+
QuotesCache(pickled) = tree
276+
277+
// Make sure trees and positions are fully loaded
278+
new TreeTraverser {
279+
def traverse(tree: Tree)(using Context): Unit = traverseChildren(tree)
280+
}.traverse(tree)
281+
282+
quotePickling.println(i"**** unpickled quote\n$tree")
283+
284+
tree
285+
}
266286

267-
tree
268287
}
269288

270289
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ class MacroAnnotations(thisPhase: DenotTransformer):
119119
// TODO: Remove when scala.annaotaion.MacroAnnotation is no longer experimental
120120
assert(annotInstance.getClass.getClassLoader.loadClass("scala.annotation.MacroAnnotation").isInstance(annotInstance))
121121

122-
val quotes = QuotesImpl()(using SpliceScope.contextWithNewSpliceScope(tree.symbol.sourcePos)(using MacroExpansion.context(tree)).withOwner(tree.symbol))
122+
val quotes = QuotesImpl()(using SpliceScope.contextWithNewSpliceScope(tree.symbol.sourcePos)(using MacroExpansion.context(tree)).withOwner(tree.symbol.owner))
123123
annotInstance.transform(using quotes)(tree.asInstanceOf[quotes.reflect.Definition])
124124

125125
/** Check that this tree can be added by the macro annotation and enter it if needed */

compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2662,7 +2662,9 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
26622662

26632663
def show(using printer: Printer[Symbol]): String = printer.show(self)
26642664

2665-
def asQuotes: Nested = new QuotesImpl(using ctx.withOwner(self))
2665+
def asQuotes: Nested =
2666+
assert(self.ownersIterator.contains(ctx.owner), s"$self is not owned by ${ctx.owner}")
2667+
new QuotesImpl(using ctx.withOwner(self))
26662668

26672669
end extension
26682670

library/src/scala/annotation/MacroAnnotation.scala

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,20 @@ trait MacroAnnotation extends StaticAnnotation:
3131
* def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] =
3232
* import quotes.reflect._
3333
* tree match
34-
* case DefDef(name, TermParamClause(param :: Nil) :: Nil, tpt, Some(rhsTree)) =>
35-
* (Ref(param.symbol).asExpr, rhsTree.asExpr) match
36-
* case ('{ $paramRefExpr: t }, '{ $rhsExpr: u }) =>
37-
* val cacheSymbol = Symbol.newUniqueVal(tree.symbol.owner, name + "Cache", TypeRepr.of[mutable.Map[t, u]], Flags.Private, Symbol.noSymbol)
38-
* val cacheRhs = '{ mutable.Map.empty[t, u] }.asTerm
34+
* case DefDef(name, TermParamClause(param :: Nil) :: Nil, tpt, Some(rhsTree)) =>
35+
* (param.tpt.tpe.asType, tpt.tpe.asType) match
36+
* case ('[t], '[u]) =>
37+
* val cacheSymbol = Symbol.newUniqueVal(Symbol.spliceOwner, name + "Cache", TypeRepr.of[mutable.Map[t, u]], Flags.Private, Symbol.noSymbol)
38+
* val cacheRhs =
39+
* given Quotes = cacheSymbol.asQuotes
40+
* '{ mutable.Map.empty[t, u] }.asTerm
3941
* val cacheVal = ValDef(cacheSymbol, Some(cacheRhs))
40-
* val cacheRefExpr = Ref(cacheSymbol).asExprOf[mutable.Map[t, u]]
41-
* val newRhs = '{ $cacheRefExpr.getOrElseUpdate($paramRefExpr, $rhsExpr) }.asTerm
42+
* val newRhs =
43+
* given Quotes = tree.symbol.asQuotes
44+
* val cacheRefExpr = Ref(cacheSymbol).asExprOf[mutable.Map[t, u]]
45+
* val paramRefExpr = Ref(param.symbol).asExprOf[t]
46+
* val rhsExpr = rhsTree.asExprOf[u]
47+
* '{ $cacheRefExpr.getOrElseUpdate($paramRefExpr, $rhsExpr) }.asTerm
4248
* val newTree = DefDef.copy(tree)(name, TermParamClause(param :: Nil) :: Nil, tpt, Some(newRhs))
4349
* List(cacheVal, newTree)
4450
* case _ =>

tests/neg-macros/annot-accessIndirect/Macro_1.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import scala.quoted._
55
class hello extends MacroAnnotation {
66
def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] =
77
import quotes.reflect._
8-
val helloSymbol = Symbol.newUniqueVal(tree.symbol.owner, "hello", TypeRepr.of[String], Flags.EmptyFlags, Symbol.noSymbol)
8+
val helloSymbol = Symbol.newUniqueVal(Symbol.spliceOwner, "hello", TypeRepr.of[String], Flags.EmptyFlags, Symbol.noSymbol)
99
val helloVal = ValDef(helloSymbol, Some(Literal(StringConstant("Hello, World!"))))
1010
List(helloVal, tree)
1111
}

tests/neg-macros/annot-accessIndirect/Macro_2.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class foo extends MacroAnnotation {
77
import quotes.reflect._
88
val s = '{@hello def foo1(x: Int): Int = x + 1;()}.asTerm
99
val fooDef = s.asInstanceOf[Inlined].body.asInstanceOf[Block].statements.head.asInstanceOf[DefDef]
10-
val hello = Ref(tree.symbol.owner.declaredFields("hello").head).asExprOf[String] // error
10+
val hello = Ref(Symbol.spliceOwner.declaredFields("hello").head).asExprOf[String] // error
1111
tree match
1212
case DefDef(name, params, tpt, Some(t)) =>
1313
val rhs = '{

tests/pos-macros/annot-then-inline/Macro_1.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ class useInlinedIdentity extends MacroAnnotation {
77
import quotes.reflect.*
88
tree match
99
case DefDef(name, params, tpt, Some(rhs)) =>
10-
val newRhs = '{ inlinedIdentity(${rhs.asExpr}) }.asTerm
10+
val newRhs =
11+
given Quotes = tree.symbol.asQuotes
12+
'{ inlinedIdentity(${rhs.asExpr}) }.asTerm
1113
List(DefDef.copy(tree)(name, params, tpt, Some(newRhs)))
1214
}
1315

tests/run-macros/annot-annot-order/Macro_1.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class print(msg: String) extends MacroAnnotation:
77
import quotes.reflect._
88
tree match
99
case DefDef(name, params, tpt, Some(rhsTree)) =>
10+
given Quotes = tree.symbol.asQuotes
1011
rhsTree.asExpr match
1112
case '{ $rhsExpr: t } =>
1213
val newRhs = '{ println(${Expr(msg)}); $rhsExpr }.asTerm

tests/run-macros/annot-bind/Macro_1.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class bind(str: String) extends MacroAnnotation:
77
import quotes.reflect._
88
tree match
99
case ValDef(name, tpt, Some(rhsTree)) =>
10-
val valSym = Symbol.newUniqueVal(tree.symbol.owner, str, tpt.tpe, Flags.Private, Symbol.noSymbol)
10+
val valSym = Symbol.newUniqueVal(Symbol.spliceOwner, str, tpt.tpe, Flags.Private, Symbol.noSymbol)
1111
val valDef = ValDef(valSym, Some(rhsTree))
1212
val newRhs = Ref(valSym)
1313
val newTree = ValDef.copy(tree)(name, tpt, Some(newRhs))

tests/run-macros/annot-gen2/Macro_1.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class hello extends MacroAnnotation {
77
import quotes.reflect._
88
tree match
99
case DefDef(name, params, tpt, Some(t)) =>
10+
given Quotes = tree.symbol.asQuotes
1011
val rhs = '{
1112
${t.asExprOf[String]} + "hello"
1213
}.asTerm

0 commit comments

Comments
 (0)