How to obtain complex type in scala macro?

81 views Asked by At

I have following macro, which is constucting some Field class for given accessor of source class:

case class Field[S, F](name: String, lens: Lens[S, F])

class Fields[S] {
  def field[D](property: S => D): Field[S, D] = macro FieldsMacro.impl[S, D]
}

class FieldsMacro(val c: whitebox.Context) {

  import c.universe._

  def impl[S: c.WeakTypeTag, D: c.WeakTypeTag](property: c.Expr[S => D]): c.Expr[Field[S, D]] = {
    val sourceType = weakTypeOf[S]
    val destinationType =  weakTypeOf[D]

    val field = sourceType.decls.collect {
      case m: MethodSymbol if m.isCaseAccessor =>
        val methodName = m.name.decodedName.toString
        val q"($x) => $x2.$name" = property.tree
        if (name.toString().equals(methodName)) {
          Some(q"Field($methodName, Lens[${sourceType.typeSymbol}, ${destinationType.typeSymbol}]($property)(_ => x => x))")
        } else None
    }.filter(_.isDefined).map(_.get).head

    val result =
      q"""
        //some imports

        $field
       """
    println(showCode(result))
    c.Expr[Field[S, D]](result)
  }
}

example of usage:

case class TestClass(i: Int, s: String, seq: Seq[Int])

object TestObject extends Fields[TestClass] {
  val i = field[Int](_.i)
  val seq = field[Seq[Int]](_.seq)
}

The problem is that weakTypeOf[D] returns Seq instead of desired Seq[Int]. So i need to pass complex full types in macro like Map[String,Int] or List[Option[SomeClass]], so i can substitute it in quasiquotes.

1

There are 1 answers

0
Niko Demin On

I just need to remove .typeSymbol call

class FieldsMacro(val c: whitebox.Context) {

  import c.universe._

  def impl[S: c.WeakTypeTag, D: c.WeakTypeTag](property: c.Expr[S => D]): c.Expr[Field[S, D]] = {
    val sourceType = weakTypeOf[S]
    val destinationType =  weakTypeOf[D]

    val field = sourceType.decls.collect {
      case m: MethodSymbol if m.isCaseAccessor =>
        val methodName = m.name.decodedName.toString
        val q"($x) => $x2.$name" = property.tree
        if (name.toString().equals(methodName)) {
          Some(q"Field($methodName, Lens[${sourceType}, ${destinationType}]($property)(_ => x => x))")
        } else None
    }.filter(_.isDefined).map(_.get).head

    val result =
      q"""
        import com.github.nikodemin.validation.Validators.Field
        import com.github.nikodemin.validation.Fields
        import monocle.Lens

        $field
       """
    println(showCode(result))
    c.Expr[Field[S, D]](result)
  }
}