diff --git a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala index 67a354919d5b..3ee52624710e 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -241,7 +241,9 @@ object PickledQuotes { treeOwner(tree) match case Some(owner) => // Copy the cached tree to make sure the all definitions are unique. - TreeTypeMap(oldOwners = List(owner), newOwners = List(owner)).apply(tree) + val treeCpy = TreeTypeMap(oldOwners = List(owner), newOwners = List(owner)).apply(tree) + // Then replace the symbol owner with the one pointed by the quote context. + treeCpy.changeNonLocalOwners(ctx.owner) case _ => tree diff --git a/tests/pos-macros/i20471/Macro_1.scala b/tests/pos-macros/i20471/Macro_1.scala new file mode 100644 index 000000000000..2fd940dbc4e2 --- /dev/null +++ b/tests/pos-macros/i20471/Macro_1.scala @@ -0,0 +1,63 @@ +import scala.annotation.experimental +import scala.quoted.* +import scala.annotation.tailrec + +object FlatMap { + @experimental inline def derived[F[_]]: FlatMap[F] = MacroFlatMap.derive +} +trait FlatMap[F[_]]{ + def tailRecM[A, B](a: A)(f: A => F[Either[A, B]]): F[B] +} + +@experimental +object MacroFlatMap: + + inline def derive[F[_]]: FlatMap[F] = ${ flatMap } + + def flatMap[F[_]: Type](using Quotes): Expr[FlatMap[F]] = '{ + new FlatMap[F]: + def tailRecM[A, B](a: A)(f: A => F[Either[A, B]]): F[B] = + ${ deriveTailRecM('{ a }, '{ f }) } + } + + def deriveTailRecM[F[_]: Type, A: Type, B: Type]( + a: Expr[A], + f: Expr[A => F[Either[A, B]]] + )(using q: Quotes): Expr[F[B]] = + import quotes.reflect.* + + val body: PartialFunction[(Symbol, TypeRepr), Term] = { + case (method, tpe) => { + given q2: Quotes = method.asQuotes + '{ + def step(x: A): B = ??? + ??? + }.asTerm + } + } + + val term = '{ $f($a) }.asTerm + val name = Symbol.freshName("$anon") + val parents = List(TypeTree.of[Object], TypeTree.of[F[B]]) + + extension (sym: Symbol) def overridableMembers: List[Symbol] = + val member1 = sym.methodMember("abstractEffect")(0) + val member2 = sym.methodMember("concreteEffect")(0) + def meth(member: Symbol) = Symbol.newMethod(sym, member.name, This(sym).tpe.memberType(member), Flags.Override, Symbol.noSymbol) + List(meth(member1), meth(member2)) + + val cls = Symbol.newClass(Symbol.spliceOwner, name, parents.map(_.tpe), _.overridableMembers, None) + + def transformDef(method: DefDef)(argss: List[List[Tree]]): Option[Term] = + val sym = method.symbol + Some(body.apply((sym, method.returnTpt.tpe))) + + val members = cls.declarations + .filterNot(_.isClassConstructor) + .map: sym => + sym.tree match + case method: DefDef => DefDef(sym, transformDef(method)) + case _ => report.errorAndAbort(s"Not supported: $sym in ${sym.owner}") + + val newCls = New(TypeIdent(cls)).select(cls.primaryConstructor).appliedToNone + Block(ClassDef(cls, parents, members) :: Nil, newCls).asExprOf[F[B]] diff --git a/tests/pos-macros/i20471/Main_2.scala b/tests/pos-macros/i20471/Main_2.scala new file mode 100644 index 000000000000..bdd1cd32ea26 --- /dev/null +++ b/tests/pos-macros/i20471/Main_2.scala @@ -0,0 +1,7 @@ +import scala.annotation.experimental + +@experimental +object autoFlatMapTests: + trait TestAlgebra[T] derives FlatMap: + def abstractEffect(a: String): T + def concreteEffect(a: String): T = abstractEffect(a + " concreteEffect")