From 39e2f698e62fd2a1cac804704b8b1ad3a73f6243 Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Sun, 25 May 2025 14:14:17 +0300 Subject: [PATCH 1/2] Preserve implicits in Quotes context --- .../tools/dotc/quoted/PickledQuotes.scala | 3 +- tests/run-macros/i22260.check | 5 ++ tests/run-macros/i22260/Macros_1.scala | 49 +++++++++++++++++++ tests/run-macros/i22260/Test_2.scala | 6 +++ 4 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 tests/run-macros/i22260.check create mode 100644 tests/run-macros/i22260/Macros_1.scala create mode 100644 tests/run-macros/i22260/Test_2.scala diff --git a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala index 3ee52624710e..b47c20b3e5cd 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -22,6 +22,7 @@ import scala.collection.mutable import QuoteUtils.* import dotty.tools.io.NoAbstractFile +import dotty.tools.dotc.ast.TreeMapWithImplicits object PickledQuotes { import tpd.* @@ -99,7 +100,7 @@ object PickledQuotes { /** Replace all term holes with the spliced terms */ private def spliceTerms(tree: Tree, typeHole: TypeHole, termHole: ExprHole)(using Context): Tree = { - def evaluateHoles = new TreeMapWithPreciseStatContexts { + def evaluateHoles = new TreeMapWithImplicits { override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match { case Hole(isTerm, idx, args, _) => inContext(SpliceScope.contextWithNewSpliceScope(tree.sourcePos)) { diff --git a/tests/run-macros/i22260.check b/tests/run-macros/i22260.check new file mode 100644 index 000000000000..d186effeb923 --- /dev/null +++ b/tests/run-macros/i22260.check @@ -0,0 +1,5 @@ +42 +42 +42 +42 +42 \ No newline at end of file diff --git a/tests/run-macros/i22260/Macros_1.scala b/tests/run-macros/i22260/Macros_1.scala new file mode 100644 index 000000000000..c67633fe16cf --- /dev/null +++ b/tests/run-macros/i22260/Macros_1.scala @@ -0,0 +1,49 @@ +import scala.quoted.* + +object Macros: + inline def inMethod: Int = ${ insideMethod } + inline def usMethod: Int = ${ usingMethod } + inline def inObject: Int = ${ insideObject } + inline def inClass: Int = ${ insideClass } + inline def usClass: Int = ${ usingClass } + + def summon(using Quotes) = + Expr.summon[Int].getOrElse('{ 0 }) + + def insideMethod(using Quotes): Expr[Int] = '{ + def foo = + given Int = 42 + ${summon} + + foo + } + + def usingMethod(using Quotes): Expr[Int] = '{ + def foo(using Int) = + ${summon} + + foo(using 42) + } + + def insideObject(using Quotes): Expr[Int] = '{ + object Foo: + given Int = 42 + val x = ${summon} + + Foo.x + } + + def insideClass(using Quotes): Expr[Int] = '{ + class Foo: + given Int = 42 + val x = ${summon} + + new Foo().x + } + + def usingClass(using Quotes): Expr[Int] = '{ + class Foo(using Int): + val x = ${summon} + + new Foo(using 42).x + } diff --git a/tests/run-macros/i22260/Test_2.scala b/tests/run-macros/i22260/Test_2.scala new file mode 100644 index 000000000000..a71f5480f3db --- /dev/null +++ b/tests/run-macros/i22260/Test_2.scala @@ -0,0 +1,6 @@ +@main def Test = + println(Macros.inMethod) + println(Macros.usMethod) + println(Macros.inObject) + println(Macros.inClass) + println(Macros.usClass) From ccc5d1aee7fe9e4e64eb5b78d258bebeddb150ce Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Sun, 25 May 2025 15:56:05 +0300 Subject: [PATCH 2/2] Also fix asQuotes implicit context for methods --- .../src/dotty/tools/dotc/core/SymDenotations.scala | 9 +++++++-- .../src/scala/quoted/runtime/impl/QuotesImpl.scala | 13 ++++++++++--- tests/neg-macros/i19842-a.check | 2 +- tests/neg-macros/i19842-b.check | 2 +- tests/neg-macros/i23008.check | 2 +- tests/run-macros/i22260.check | 1 + tests/run-macros/i22260/Macros_1.scala | 10 +++++++++- tests/run-macros/i22260/Test_2.scala | 1 + 8 files changed, 31 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 53f991806641..1e2555024150 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -339,8 +339,13 @@ object SymDenotations { def recurWithoutParamss(info: Type): List[List[Symbol]] = info match case info: LambdaType => - val params = info.paramNames.lazyZip(info.paramInfos).map((pname, ptype) => - newSymbol(symbol, pname, SyntheticParam, ptype)) + val commonFlags = + if info.isContextualMethod then Given | SyntheticParam + else if info.isImplicitMethod then Implicit | SyntheticParam + else SyntheticParam + val params = info.paramNames.lazyZip(info.paramInfos).map: (pname, ptype) => + val flags = if ptype.hasAnnotation(defn.ErasedParamAnnot) then commonFlags | Erased else commonFlags + newSymbol(symbol, pname, flags, ptype) val prefs = params.map(_.namedType) for param <- params do param.info = param.info.substParams(info, prefs) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 6e68ac1a2de3..000dd044464a 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -11,6 +11,7 @@ import dotty.tools.dotc.core.Contexts.* import dotty.tools.dotc.core.Decorators.* import dotty.tools.dotc.core.NameKinds import dotty.tools.dotc.core.NameOps.* +import dotty.tools.dotc.core.Scopes.* import dotty.tools.dotc.core.StdNames.* import dotty.tools.dotc.core.Types import dotty.tools.dotc.NoCompilationUnit @@ -2823,7 +2824,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler modFlags | dotc.core.Flags.ModuleValCreationFlags, clsFlags | dotc.core.Flags.ModuleClassCreationFlags, parents, - dotc.core.Scopes.newScope, + newScope, privateWithin, NoCoord, compUnitInfo = null @@ -2839,7 +2840,9 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler xCheckMacroAssert(!privateWithin.exists || privateWithin.isType, "privateWithin must be a type symbol or `Symbol.noSymbol`") val privateWithin1 = if privateWithin.isTerm then Symbol.noSymbol else privateWithin checkValidFlags(flags.toTermFlags, Flags.validMethodFlags) - dotc.core.Symbols.newSymbol(owner, name.toTermName, flags | dotc.core.Flags.Method, tpe, privateWithin1) + val method = dotc.core.Symbols.newSymbol(owner, name.toTermName, flags | dotc.core.Flags.Method, tpe, privateWithin1) + method.setParamss(method.paramSymss) + method def newVal(owner: Symbol, name: String, tpe: TypeRepr, flags: Flags, privateWithin: Symbol): Symbol = xCheckMacroAssert(!privateWithin.exists || privateWithin.isType, "privateWithin must be a type symbol or `Symbol.noSymbol`") val privateWithin1 = if privateWithin.isTerm then Symbol.noSymbol else privateWithin @@ -3066,7 +3069,11 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def asQuotes: Nested = assert(self.ownersIterator.contains(ctx.owner), s"$self is not owned by ${ctx.owner}") - new QuotesImpl(using ctx.withOwner(self)) + val newCtx = if ctx.owner eq self then ctx else + val newCtx = ctx.fresh.setOwner(self) + if !self.flags.is(Flags.Method) then newCtx + else newCtx.setScope(newScopeWith(self.paramSymss.flatten*)) + new QuotesImpl(using newCtx) end extension diff --git a/tests/neg-macros/i19842-a.check b/tests/neg-macros/i19842-a.check index af628c566c15..4d92739e00ff 100644 --- a/tests/neg-macros/i19842-a.check +++ b/tests/neg-macros/i19842-a.check @@ -9,8 +9,8 @@ | | at scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8) | at dotty.tools.dotc.transform.TreeChecker$.checkParents(TreeChecker.scala:210) + | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:286) | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:285) - | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:284) | at Macros$.makeSerializer(Macro.scala:25) | |--------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg-macros/i19842-b.check b/tests/neg-macros/i19842-b.check index b402006c2d4b..5a2fe6f39d48 100644 --- a/tests/neg-macros/i19842-b.check +++ b/tests/neg-macros/i19842-b.check @@ -9,8 +9,8 @@ | | at scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8) | at dotty.tools.dotc.transform.TreeChecker$.checkParents(TreeChecker.scala:210) + | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:286) | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:285) - | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:284) | at Macros$.makeSerializer(Macro.scala:27) | |--------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg-macros/i23008.check b/tests/neg-macros/i23008.check index c48fdb2fe6fc..b26ef11d201f 100644 --- a/tests/neg-macros/i23008.check +++ b/tests/neg-macros/i23008.check @@ -5,8 +5,8 @@ | Exception occurred while executing macro expansion. | java.lang.IllegalArgumentException: requirement failed: value of StringConstant cannot be `null` | at scala.Predef$.require(Predef.scala:337) + | at scala.quoted.runtime.impl.QuotesImpl$reflect$StringConstant$.apply(QuotesImpl.scala:2534) | at scala.quoted.runtime.impl.QuotesImpl$reflect$StringConstant$.apply(QuotesImpl.scala:2533) - | at scala.quoted.runtime.impl.QuotesImpl$reflect$StringConstant$.apply(QuotesImpl.scala:2532) | at scala.quoted.ToExpr$StringToExpr.apply(ToExpr.scala:80) | at scala.quoted.ToExpr$StringToExpr.apply(ToExpr.scala:78) | at scala.quoted.Expr$.apply(Expr.scala:70) diff --git a/tests/run-macros/i22260.check b/tests/run-macros/i22260.check index d186effeb923..a50c8c916b8c 100644 --- a/tests/run-macros/i22260.check +++ b/tests/run-macros/i22260.check @@ -2,4 +2,5 @@ 42 42 42 +42 42 \ No newline at end of file diff --git a/tests/run-macros/i22260/Macros_1.scala b/tests/run-macros/i22260/Macros_1.scala index c67633fe16cf..44538bfcd521 100644 --- a/tests/run-macros/i22260/Macros_1.scala +++ b/tests/run-macros/i22260/Macros_1.scala @@ -1,6 +1,7 @@ import scala.quoted.* object Macros: + inline def crMethod: Int = ${ createMethod } inline def inMethod: Int = ${ insideMethod } inline def usMethod: Int = ${ usingMethod } inline def inObject: Int = ${ insideObject } @@ -8,7 +9,14 @@ object Macros: inline def usClass: Int = ${ usingClass } def summon(using Quotes) = - Expr.summon[Int].getOrElse('{ 0 }) + Expr.summon[Int].getOrElse('{0}) + + def createMethod(using Quotes): Expr[Int] = + import quotes.reflect.* + val tpe = MethodType(MethodTypeKind.Contextual)("x" :: Nil)(_ => TypeRepr.of[Int] :: Nil, _ => TypeRepr.of[Int]) + val sym = Symbol.newMethod(Symbol.spliceOwner, "foo", tpe) + val method = DefDef(sym, _ => Some(summon(using sym.asQuotes).asTerm)) + Block(method :: Nil, Apply(Ref(sym), '{42}.asTerm :: Nil)).asExprOf[Int] def insideMethod(using Quotes): Expr[Int] = '{ def foo = diff --git a/tests/run-macros/i22260/Test_2.scala b/tests/run-macros/i22260/Test_2.scala index a71f5480f3db..3f512fa6308a 100644 --- a/tests/run-macros/i22260/Test_2.scala +++ b/tests/run-macros/i22260/Test_2.scala @@ -1,4 +1,5 @@ @main def Test = + println(Macros.crMethod) println(Macros.inMethod) println(Macros.usMethod) println(Macros.inObject)