Scala macro: get companion object from class type

361 Views Asked by At

I cannot manage to get the companion object / singleton from a class type in Scala macro / quasiquotes. Tried to follow https://docs.scala-lang.org/overviews/quasiquotes/type-details.html#singleton-type, the given example works but it is based on a literal string to quasiquote to get the companion object directly, which I cannot quite achieve the same thing if I start off from an extracted class type of a param after some quasiquote unlifting.

I have simplified and tried to highlight the intended usage, and current macro implementation below:

// Intended usage
package utils

sealed trait Base

object Base {
  import macros._

  @Component
  final case class X(v: XInner) extends Base
}

final case class XInner(v: Int)

Base.X(123)  // No need to do Base.X(XInner(123))

The current macro implementation

package macros

import scala.reflect.macros.whitebox
import scala.language.experimental.macros
import scala.annotation.StaticAnnotation
import scala.annotation.compileTimeOnly

class Component() extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro ComponentMacro.impl
}

private class ComponentMacro(val c: whitebox.Context) {
  import c.universe._

  // To map function result while allowing the use of params forwarding syntax like `apply _`
  // e.g. `def y = (X.apply _).mapResult(Y(_))`
  implicit class Func1Extra[I1, O1, O2](f: I1 => O1) {
    def mapResult(g: O1 => O2): I1 => O2 = (i1: I1) => g(f(i1))
  }

  def impl(annottees: Tree*): Tree = annottees match {
    case (clsDef: ClassDef) :: Nil =>
      clsDef match {
        case q"final case class $className(..$fields) extends ..$parents" if fields.length == 1 => {
          val fieldType = fields(0).tpt
          val singletonType = tq"$fieldType.type"
          val tq"$singleton.type" = singletonType
          q"""
          $clsDef
          object ${clsDef.name.toTermName} {
            def apply = (${singleton}.apply _).mapResult(new ${clsDef.name}(_))
          }
          """
        }

        case _ => c.abort(c.enclosingPosition, "Invalid annotation target")
      }

    case _ => c.abort(c.enclosingPosition, "Invalid annotation target")
  }
}

The error while compiling is:

value apply is not a member of Utils.XInner

The error message seems to suggest that the apply method was done on the XInner class type, rather than the companion object of XInner.

Any idea as to how to get the component object of the same type name? Thanks in advance!

0

There are 0 best solutions below