Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

How to generate constructor invocation on generic case class? #19363

Unanswered
ChAoSUnItY asked this question in Metaprogramming
Discussion options

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!

You must be logged in to vote

Replies: 1 comment

Comment options

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]
You must be logged in to vote
0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet

AltStyle によって変換されたページ (->オリジナル) /