Макро-аннотация Scala - класс case с параметрами типа

Я пытаюсь написать простую макросовую аннотацию для case-классов, которая добавляет метод к объекту-компаньону. Уловка в том, что новый метод должен учитывать параметры типа в аннотированном классе case.

Вот тест, который мне нужно пройти

package my.macros

import org.scalatest._

class DefaultApplyTest extends FlatSpec with Matchers {

  @defaultApply case class Generic[A, B](a: A, b: B)

  it should "define defaultApply method in companion object" in {
    assert(Generic.defaultApply("str", 1) == Generic("str", 1))
  }
}

Вот код, который я написал, чтобы выполнить это

package my.macros

import scala.reflect.macros._
import scala.language.experimental.macros
import scala.annotation.StaticAnnotation

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

object DefaultApply {

  def impl(c: blackbox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
    import c.universe._

    def defaultApplyCompanion(classDecl: ClassDef) = {
      val (name, typeParams, valueParams) = try {
        val q"case class ${name: TypeName}[..${typeParams: Seq[TypeDef]}](..${valueParams: Seq[ValDef]}) extends ..$bases { ..$body }" = classDecl
        (name, typeParams, valueParams)
      } catch {
        case e: MatchError =>
          c.warning(c.enclosingPosition, e.toString)
          c.abort(c.enclosingPosition, "Annotation is only supported on case class")
      }

      val applyDef = q"""${name.toTermName}.apply[..$typeParams]"""
      c.warning(c.enclosingPosition, showRaw(applyDef))

      q"""
        object ${name.toTermName} {
          def defaultApply: (..${valueParams.map(_.tpt)}) => $name[..$typeParams] = $applyDef
        }
      """
    }

    def modifiedDeclaration(classDecl: ClassDef) = {
      val compDecl = defaultApplyCompanion(classDecl)

      c.Expr(q"""
        $classDecl
        $compDecl
      """)
    }

    annottees.map(_.tree) match {
      case (classDecl: ClassDef) :: Nil => modifiedDeclaration(classDecl)
      case _ => c.abort(c.enclosingPosition, "Invalid annottee")
    }
  }
}

Проблема, насколько я понимаю, состоит в том, что когда я пытаюсь поднять список параметров типа в результирующее синтаксическое дерево, они не распознаются как те же параметры типа, что и в исходном дереве.

Так что я сосредоточен на том, что для этой части макроса

  val applyDef = q"""${name.toTermName}.apply[..$typeParams]"""
  c.warning(c.enclosingPosition, showRaw(applyDef))

Необработанное синтаксическое дерево выводится как

TypeApply(Select(Ident(TermName("Generic")), TermName("apply")), List(TypeDef(Modifiers(PARAM), TypeName("A"), List(), TypeBoundsTree(EmptyTree, EmptyTree)), TypeDef(Modifiers(PARAM), TypeName("B"), List(), TypeBoundsTree(EmptyTree, EmptyTree))))

но компилятор не доволен этим

type arguments [<notype>,<notype>] do not conform to method apply's type parameter bounds [A,B]

Конечный вариант использования предназначен для генерации экземпляров класса с кэшируемым типом, который затрагивает более 1 тыс. Строк кода. Не параметризованная версия уже работает, это всего лишь глазурь на торте. Под капюшоном скальпа есть что-то, чего я не понимаю, но хотел бы. Ваше время, потраченное на чтение этого, высоко ценится.

Я использую Scala 2.11.8 с макро-рай 2.1.0

1 ответ

Решение

Кажется, проблема в том, что вы используете параметры типа вместо аргументов типа. Кажется, это работает (мне также пришлось добавить параметры типа в defaultApply объявление метода):

  val typeArgs = typeParams.map(_.name)
  val applyDef = q"""${name.toTermName}.apply[..$typeArgs]"""
  c.warning(c.enclosingPosition, showRaw(applyDef))

  q"""
    object ${name.toTermName} {
      def defaultApply[..$typeParams]:
          (..${valueParams.map(_.tpt)}) => $name[..$typeArgs] = $applyDef
    }
  """
Другие вопросы по тегам