1

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!

Guangie
  • 11
  • 2
  • The code must type check before expansion. I've only tried macro annotations for a few tests, but I wouldn't be surprised if case class members have not been added when macro is invoked. I recently had an issue with something not available for macro annotation. – som-snytt Mar 26 '20 at 05:18
  • Apparently macro annots are expanded before case class apply is synthetized. – som-snytt Mar 26 '20 at 05:37
  • Hmm it does work if I attempt to use literal directly, i.e. `def apply = (utils.XInner.apply _)` Alternatively, doing `val tq"$singleton.type" = tq"utils.XInner.type"` works too for `singleton` interpolation. But it's true that I'm not familiar with the ordering of the "synthesis", so that might affect too. – Guangie Mar 26 '20 at 05:51
  • I forgot to say use `-Vmacro-lite` on 2.13 when debugging. – som-snytt Mar 26 '20 at 17:59
  • BTW, I was wrong: unlike def macros, macro-annots expand early, so it does support your use case (though it's a little too magical for my preference). – som-snytt Mar 26 '20 at 18:52
  • Why not to use class constructor rather than compiler-generated `apply` method of companion object? – Dmytro Mitin Apr 08 '20 at 20:53

0 Answers0