-
Notifications
You must be signed in to change notification settings - Fork 1.1k
How to generate constructor invocation on generic case class? #19363
-
Currently I have a type class that indicates a type is generate-able and having both a non generic and a generic case class to test the constructor invocation:
trait Generator[T]: def generate(): T given Generator[String] with def generate(): String = "ASK" given Generator[Int] with def generate(): Int = 1 private case class Person(name: String, age: Int) private case class Cont[T: Generator](content: T, name: Int)
And here is my attempt to generate constructor invocation through macro:
import scala.quoted.Expr import scala.quoted.Quotes import java.lang.reflect.Field import scala.quoted.Type import scala.util.Random import scala.util.NotGiven inline def viewTree: Unit = ${ viewTreeImpl } def viewTreeImpl(using Quotes): Expr[Unit] = import quoted.quotes.reflect._ val treeStr = Expr('{ new Cont("KEK", 1) }.show) '{ println($treeStr) } inline def fakeGen[T]: T = ${ fakeGenImpl[T] } def fakeGenImpl[T](using Type[T], Quotes): Expr[T] = import quoted.quotes.reflect._ import scala.util.NotGiven val ownerType = TypeTree.of[T] val ownerTpe = ownerType.tpe val ownerSym = ownerTpe.classSymbol.get val ownerTypeArgs = ownerTpe.typeArgs val ctor = ownerSym.primaryConstructor val appliedCtorType = ownerTpe.memberType(ctor).appliedTo(ownerTypeArgs) val paramGens = getCtorParamGens[T] var ctorExpr: Expr[T] = Apply(Select(New(ownerType), ctor), paramGens.map(_.asTerm)).asExprOf[T] if (!ownerTypeArgs.isEmpty) val genTypeArgs = ownerTypeArgs.map(genTpe => { genTpe.asType match case '[g] => getGen[g].asTerm case _ => report.errorAndAbort( s"Illegal Generator Type: `${Type.show[T]}`, not an valid generator type" ) }) ctorExpr = Apply(ctorExpr.asTerm, genTypeArgs).asExprOf[T] ctorExpr def getCtorParamGens[T](using Type[T], Quotes): List[Expr[Any]] = import quoted.quotes.reflect._ import scala.util.NotGiven val ownerType = TypeTree.of[T] val ownerTpe = ownerType.tpe val ctor = ownerTpe.typeSymbol.primaryConstructor val appliedCtorType = ownerTpe.memberType(ctor).appliedTo(ownerTpe.typeArgs) appliedCtorType match case MethodType(name, params, ret) => { params.map(paramTpe => { paramTpe.asType match case '[p] => val gen = getGen[p] '{ $gen.generate() } case _ => report.errorAndAbort( s"Illegal Generator Type: `${Type.show[T]}`, not an valid generator type" ) }) } case _ => report.errorAndAbort( s"Illegal Generator Type: `${Type.show[T]}`, type does not have primary constructor", Position.ofMacroExpansion ) def getGen[G](using Type[G], Quotes): Expr[Generator[G]] = import quoted.quotes.reflect._ Expr.summon[Generator[G]] match case Some(generator) => generator case None => report.errorAndAbort( s"Illegal Parameter Type: `${Type.show[G]}`, type does not implement `Generator` type class", Position.ofMacroExpansion )
But after I invokes fakeGen
on type Person
and Cont[String]
, the first one works without any issue, however, the second one generates an incomplete constructor invocation based on what dotty told me: constructor Cont in class Cont does not take parameters
, which is even more confusing after I dumped 2 constructor invocations through Expr[T].show
(as shown below):
Person
: new Person(given_Generator_String.generate(), given_Generator_Int.generate())
Cont
: new Cont[scala.Predef.String](given_Generator_String.generate(), given_Generator_Int.generate())(given_Generator_String)
Any suggestion about how to fix or complete the incomplete part of constructor invocation would be appreciated!
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 1 comment
-
The constructors expects type arguments first.
You need to "TypeApply
" it:
val baseCtorTerm = Select(New(ownerType), ctor) val typeAppliedCtorTerm = if ownerTypeArgs.isEmpty then baseCtorTerm else TypeApply(baseCtorTerm, ownerTypeArgs.map(t => TypeTree.ref(t.typeSymbol))) // ^^^^^^^^^^^^^^^^^^^^^^^ here var ctorTerm = Apply(typeAppliedCtorTerm, paramGens.map(_.asTerm)) // drop the .asExprOf[T], it's not valid Expr yet var finalTerm = if !ownerTypeArgs.isEmpty then val genTypeArgs = ownerTypeArgs.map(genTpe => { genTpe.asType match case '[g] => getGen[g].asTerm case _ => report.errorAndAbort( s"Illegal Generator Type: `${Type.show[T]}`, not an valid generator type" ) }) Apply(ctorTerm, genTypeArgs) else ctorTerm finalTerm.asExprOf[T]
Beta Was this translation helpful? Give feedback.