scalareflectionscala-macrosscala-3scala-quasiquotes

Scala 3 macros: create a new polynmorphic function using the reflect api


I intend to create the simple polymorphic function expression below using the Quotes.reflect API:

new PolyFunction {
  def apply[X](a: X): X = a
}

What I have attempted is shown below with parts that I could not implement replaced by ???:

val name: String = "$anon"
val parents = List(TypeTree.of[Object], TypeTree.of[PolyFunction])
def decls(cls: Symbol): List[Symbol] =
  List(
    Symbol.newMethod(
      cls,
      "apply",
      MethodType(List("a"))(
        { mt =>
          ???
        },
        mt => ???
      )
    )
  )
val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfType = None)
val applySym = cls.declaredMethod("apply").head

val applyDef = DefDef(applySym, argss => Some(???))
val clsDef = ClassDef(cls, parents, body = List(applyDef))
val closure = Block(List(clsDef),Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil))
closure.asExpr

The problem, mainly, is I am unable to declare the type parameter X, and its reference in the first value parameter and the functions return type. I have noticed some reflection functions are annotated as experimental.


Solution

  • Try to use PolyType along with MethodType

    import scala.annotation.experimental
    import scala.quoted.*
    
    inline def newPoly: PolyFunction = ${newPolyImpl}
    
    @experimental
    def newPolyImpl(using Quotes): Expr[PolyFunction] = {
      import quotes.reflect.*
    
      val name: String = "$anon"
      val parents = List(TypeTree.of[Object], TypeTree.of[PolyFunction])
    
      def decls(cls: Symbol): List[Symbol] =
        List(
          Symbol.newMethod(
            cls,
            "apply",
            PolyType(List("X"))(_ => List(TypeBounds.empty), polyType => {
              val typeParam = polyType.param(0)
              MethodType(List("a"))(_ => List(typeParam), _ => typeParam)
            })
          )
        )
    
      val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfType = None)
      val applySym = cls.declaredMethod("apply").head
    
      // argss=List(List(TypeTree[TypeRef(NoPrefix,type X)]), List(Ident(a)))
      val applyDef = DefDef(applySym, argss => Some(argss(1)(0).asInstanceOf[Term]))
      val clsDef = ClassDef(cls, parents, body = List(applyDef))
      val closure = Block(List(clsDef), Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil))
      closure.asExprOf[PolyFunction]
    }
    

    Usage:

    newPoly
    
    //scalac: {
    //  class $anon extends java.lang.Object with scala.PolyFunction {
    //    def apply[X](a: X): X = a
    //  }
    //  new $anon()
    //}
    

    Scala 3.2.1

    Method Override with Scala 3 Macros

    Scala3: Crafting Types Through Metaprogramming?

    `tq` equivalent in Scala 3 macros