Closed
Description
Compiler version
- 3.2.0-RC4 / main
Minimized code
Note: mb I'm writing a wrong code :/
Usage:
trait Foo[F[_]]:
def foo(id: F[Int]): Unit
// multiple times
// macroMinimized2.derive creates an instance of Foo with the substituted type param, i.e.:
// new Foo[Option] { def foo(id: Option[Int]): Unit = ... }
// new Foo[Try] { def foo(id: Try[Int]): Unit = ... }
// however more than a single call fails
macroMinimized2.derive[Foo, Option]
macroMinimized2.derive[Foo, Try]
Macro code (I tried to minimize it as much as I could, this code makes use of the #11685
import quoted.*
import scala.annotation.experimental
object macroMinimized2:
inline def derive[Alg[_[_]], G[_]] = ${ instanceK[Alg, G] }
// create instance of Alg[F] that has a method foo(i: F[Int]): Unit
@experimental def instanceK[Alg[_[_]]: Type, G[_]: Type](using Quotes): Expr[Alg[G]] =
import quotes.reflect.*
val name = "$anon()"
val parents = List(TypeTree.of[Object], TypeTree.of[Alg[G]])
// uncomment to make it work on the second run
// val parents = List(TypeTree.of[Object], TypeTree.of[Alg[scala.util.Try]])
val method = definedMethodsInType[Alg].head
// this should be used, since it feels like there is a problem in change owner
// method arg types are aligned with the first macro call
def decls(cls: Symbol): List[Symbol] =
method.tree.changeOwner(cls) match
case DefDef(name, clauses, typedTree, _) =>
val tpeRepr = TypeRepr.of(using typedTree.tpe.asType)
val (nms, tpes) = clauses.map(_.params.collect { case v: ValDef => (v.name, v.tpt.tpe) }.unzip).head
println("---------")
println(tpes)
println("---------")
// tpes is always the same across multiple derive calls
val methodType = MethodType(nms)(_ => tpes, _ => tpeRepr)
Symbol.newMethod(
cls,
name,
methodType,
flags = Flags.EmptyFlags /*TODO: method.flags */,
privateWithin = method.privateWithin.fold(Symbol.noSymbol)(_.typeSymbol)
) :: Nil
case _ =>
report.errorAndAbort(s"Cannot detect type of method: ${method.name}")
// uncomment this one to make it work
// def decls(cls: Symbol): List[Symbol] =
// List(Symbol.newMethod(cls, "foo", MethodType(List("id"))(_ => List(TypeRepr.of[G[Int]]), _ => TypeRepr.of[Unit])))
val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfType = None)
val fooSym = cls.declaredMethod("foo").head
val fooDef = DefDef(fooSym, argss => Some('{println(s"Calling foo")}.asTerm))
val clsDef = ClassDef(cls, parents, body = List(fooDef))
val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), TypeTree.of[Alg[G]])
val res = Block(List(clsDef), newCls).asExprOf[Alg[G]]
println("---------")
println(res.show)
println("---------")
res
// function to get methods defined for the type
def definedMethodsInType[Alg[_[_]]: Type](using Quotes): List[quotes.reflect.Symbol] =
import quotes.reflect.*
val cls = TypeRepr.of[Alg].typeSymbol
for {
member <- cls.methodMembers
// is abstract method, not implemented
if member.flags.is(Flags.Deferred)
// TODO: is that public?
// TODO? if member.privateWithin
if !member.flags.is(Flags.Private)
if !member.flags.is(Flags.Protected)
if !member.flags.is(Flags.PrivateLocal)
if !member.isClassConstructor
if !member.flags.is(Flags.Synthetic)
} yield member
Output
- if it's a single call per code base macro works as expected
- If there is more than a single call (
macroMinimized2.derive[Foo, Option]
,macroMinimized2.derive[Foo, Try]
, etc) all the followup calls generate traits with incorrect method signatures:
// Call 1: macroMinimized2.derive[Foo, Option]
// Generated method: def foo(id: scala.Option[scala.Int])
---------
List(AppliedType(TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class <root>)),object scala),class Option),List(TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class <root>)),object scala),class Int))))
---------
{
class $anon() extends java.lang.Object with MinimizedSpec.this.Foo[[A >: scala.Nothing <: scala.Any] => scala.Option[A]] {
def foo(id: scala.Option[scala.Int]): scala.Unit = scala.Predef.println(_root_.scala.StringContext.apply("Calling foo").s())
}
(new $anon()(): MinimizedSpec.this.Foo[[A >: scala.Nothing <: scala.Any] => scala.Option[A]])
}
---------
// Call 2: macroMinimized2.derive[Foo, Try]
// Generated method: def foo(id: scala.Option[scala.Int])
---------
List(AppliedType(TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class <root>)),object scala),class Option),List(TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class <root>)),object scala),class Int))))
---------
{
class $anon() extends java.lang.Object with MinimizedSpec.this.Foo[[T >: scala.Nothing <: scala.Any] => scala.util.Try[T]] {
def foo(id: scala.Option[scala.Int]): scala.Unit = scala.Predef.println(_root_.scala.StringContext.apply("Calling foo").s())
}
(new $anon()(): MinimizedSpec.this.Foo[[T >: scala.Nothing <: scala.Any] => scala.util.Try[T]])
}
---------
Expectation
Should not fail and generate the correct trait body when called multiple times.