Scala Macro:使用Option类型创建新类

时间:2015-02-04 09:33:45

标签: scala scala-macros scala-2.11 scala-macro-paradise scala-meta

我想写一个宏:

@MetaRest case class User(
  @get               id            : Int,
  @get @post @patch  name          : String,
  @get @post         email         : String,
                     registeredOn  : DateTime
)

生成以下代码:

object User {
  case class Get(id: Int, name: String, email: String)
  case class Post(name: String, email: String
  case class Patch(name: Option[String])   // Note - the Option type here!
}

我在这里取得了不错的进展:https://github.com/pathikrit/metarest

我的尝试:https://github.com/pathikrit/metarest/blob/master/src/main/scala/com/github/pathikrit/MetaRest.scala

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

object MetaRest {
  sealed trait MethodAnnotations extends StaticAnnotation
  class get extends MethodAnnotations
  class put extends MethodAnnotations
  class post extends MethodAnnotations
  class patch extends MethodAnnotations

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

    def modifiedCompanion(compDeclOpt: Option[ModuleDef], className: TypeName, fields: List[ValDef]) = {
      val result = fields.flatMap {field =>
        field.mods.annotations.collect {   // TODO: shorten this - make use of the fact that all extend sealed trait MethodAnnotations
          case q"new get" => "get" -> field
          case q"new put" => "put" -> field
          case q"new post" => "post" -> field
          case q"new patch" => "patch" -> field
        }
      } groupBy (_._1) mapValues(_ map (_._2.duplicate))

      println(result("get"))

      // TODO: ----------------------------------------------------------------------------------------
      val getRequestModel = q"""case class Get(id: Int, name: String, email: String)"""
      val postRequestModel = q"""case class Post(name: String, email: String)"""
      val patchRequestModel = q"""case class Patch(name: Option[String])"""
      // ------------------------------------------------------------------------------------------------

      compDeclOpt map { compDecl =>
        val q"object $obj extends ..$bases { ..$body }" = compDecl
        q"""
          object $obj extends ..$bases {
            ..$body
            $getRequestModel
            $postRequestModel
            $patchRequestModel
          }
        """
      } getOrElse {
        q"""
          object ${className.toTermName} {
            $getRequestModel
            $postRequestModel
            $patchRequestModel
          }
         """
      }
    }

    def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None) = {
      val q"case class $className(..$fields) extends ..$bases { ..$body }" = classDecl

      val compDecl = modifiedCompanion(compDeclOpt, className, fields)

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

    annottees.map(_.tree) match {
      case (classDecl: ClassDef) :: Nil => modifiedDeclaration(classDecl)
      case (classDecl: ClassDef) :: (compDecl: ModuleDef) :: Nil => modifiedDeclaration(classDecl, Some(compDecl))
      case _ => c.abort(c.enclosingPosition, "@MetaRest must annotate a class")
    }
  }
}

测试在这里:https://github.com/pathikrit/metarest/blob/master/src/test/scala/com/github/pathikrit/MetaRestSpec.scala

通过注释对字段进行干净分组并生成Get/Post类的最佳方法是什么?另外,对于Patch类 - 如何将所有字段转换为Option[original.type]

1 个答案:

答案 0 :(得分:1)

实际上我做到了: https://github.com/pathikrit/metarest/commit/1d1af92b71179385c8521d00a5059dd21996c448?diff=split

这很简单:

q"$accessor val $vname: $tpe" => q"$accessor val $vname: Option[$tpe]"