Сделать класс Scala расширяет типаж / абстрактный класс макросами

Проблема:

Я хочу сделать аннотированный класс подклассом другого класса с помощью макроса scala. Что у меня есть:

Обертка для полей:

class Field(fieldType: DbModelFieldType, fieldName: String) 

Абстрактный класс (базовый класс для всех аннотированных классов):

abstract class DatabaseModel {
  def fields: Seq[Fields]
}

У меня есть класс дела:

Model(num: Int, sym: Char, descr: String)

и если аннотировать этот класс с помощью @GetFromDB

@GetFromDB
Model(num: Int, sym: Char, descr: String)
case class Model(num: Int, sym: Char, descr: String) extends DatabaseModel {
   override def fields: Seq[Fields] = 
       Seq(Field(IntFieldType(), "num"),
           Field(CharFieldType(), "sym"),
           Field(StringFieldType(), "descr")
          ) 
}

мой желаемый результат должен быть примерно таким:

val m: DatabaseModel = Model(1, 'A', "First Name")

Я смотрел на похожие вопросы

Сгенерировать сопутствующий объект для класса case с помощью методов (field = method )

Итак, как я могу расширить это решение для достижения желаемого результата?

import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.whitebox

object Macros {
  @compileTimeOnly("enable macro paradise")
  class GenerateCompanionWithFields extends StaticAnnotation {
    def macroTransform(annottees: Any*): Any = macro Macro.impl
  }

  object Macro {
    def impl(c: whitebox.Context)(annottees: c.Tree*): c.Tree = {
      import c.universe._
      annottees match {
        case (cls @ q"$_ class $tpname[..$_] $_(...$paramss) extends { ..$_ } with ..$_ { $_ => ..$_ }") :: Nil =>

          val newMethods = paramss.flatten.map {
            case q"$_ val $tname: $tpt = $_" =>
              q"def $tname(): String = ${tpt.toString}"
          }

          q"""
             $cls

             object ${tpname.toTermName} {
               ..$newMethods
             }
           """
      }
    }
  }
}

person pureFPevangelist    schedule 05.10.2019    source источник
comment
@ LuisMiguelMejíaSuárez, я хочу сделать это во время компиляции   -  person pureFPevangelist    schedule 05.10.2019
comment
Рассматривали ли вы использование Shapeless для вывода? И только макрос для его запуска.   -  person Luis Miguel Mejía Suárez    schedule 05.10.2019


Ответы (1)


Пытаться

import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.whitebox

object Macros {
  @compileTimeOnly("enable macro paradise")
  class GetFromDB extends StaticAnnotation {
    def macroTransform(annottees: Any*): Any = macro GetFromDBMacro.impl
  }

  object GetFromDBMacro {
    def impl(c: whitebox.Context)(annottees: c.Tree*): c.Tree = {
      import c.universe._
      annottees match {
        case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: tail =>
          val fields = paramss.flatten.map {
            case q"$_ val $tname: $tpt = $_" => q"Field(${TermName(tpt.toString + "FieldType")}.apply(), ${tname.toString})"
          }
          q"""
             $mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..${tq"DatabaseModel" :: parents} { $self =>
               override def fields: _root_.scala.collection.immutable.Seq[Field] = _root_.scala.collection.immutable.Seq.apply(..$fields)

               ..$stats
             }

             ..$tail
           """
      }
    }
  }
}

import Macros._

object App {
  sealed trait DbModelFieldType
  case class IntFieldType() extends DbModelFieldType
  case class CharFieldType() extends DbModelFieldType
  case class StringFieldType() extends DbModelFieldType

  case class Field(fieldType: DbModelFieldType, fieldName: String)

  abstract class DatabaseModel {
    def fields: Seq[Field]
  }

  @GetFromDB
  case class Model(num: Int, sym: Char, descr: String)

//Warning:scalac: {
//  case class Model extends DatabaseModel with scala.Product with scala.Serializable {
//    <caseaccessor> <paramaccessor> val num: Int = _;
//    <caseaccessor> <paramaccessor> val sym: Char = _;
//    <caseaccessor> <paramaccessor> val descr: String = _;
//    def <init>(num: Int, sym: Char, descr: String) = {
//      super.<init>();
//      ()
//    };
//    override def fields: _root_.scala.collection.immutable.Seq[Field] = _root_.scala.collection.immutable.Seq.apply(Field(IntFieldType.apply(), "num"), Field(CharFieldType.apply(), "sym"), Field(StringFieldType.apply(), "descr"))
//  };
//  ()
//}

  val m: DatabaseModel = Model(1, 'A', "First Name")

  def main(args: Array[String]): Unit = {
    println(
      m.fields //List(Field(IntFieldType(),num), Field(CharFieldType(),sym), Field(StringFieldType(),descr))
    )
  }
}
person Dmytro Mitin    schedule 06.10.2019
comment
чувак, ты спас мне жизнь :-) большое спасибо !!! но у меня есть еще вопросы, которые я хочу задать вам, если можно - person pureFPevangelist; 06.10.2019
comment
@pureFPevangelist Stackoverflow как раз для вопросов. - person Dmytro Mitin; 07.10.2019