builderMacro.scala 3.4 KB
Newer Older
梦境迷离's avatar
梦境迷离 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
package io.github.dreamylost.macros

import scala.reflect.macros.whitebox

/**
 *
 * @author 梦境迷离
 * @since 2021/7/7
 * @version 1.0
 */
object builderMacro extends MacroCommon {
  private final val BUFFER_CLASS_NAME_SUFFIX = "Builder"

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

    def getBuilderClassName(classTree: TypeName): TypeName = {
      TypeName(classTree.toTermName.decodedName.toString + BUFFER_CLASS_NAME_SUFFIX)
    }

    def fieldSetMethod(typeName: TypeName, field: Tree): c.Tree = {
      val builderClassName = getBuilderClassName(typeName)
      field match {
        case q"$mods var $tname: $tpt = $expr" =>
          q"""
              def $tname($tname: $tpt): ${builderClassName} = {
                  this.$tname = $tname
                  this
              }
           """
        case q"$mods val $tname: $tpt = $expr" =>
          q"""
              def $tname($tname: $tpt): ${builderClassName} = {
                  this.$tname = $tname
                  this
              }
           """
      }
    }

    def fieldDefinition(field: Tree): Tree = {
      field match {
        case q"$mods val $tname: $tpt = $expr" => q"""private var $tname: $tpt = $expr"""
        case q"$mods var $tname: $tpt = $expr" => q"""private var $tname: $tpt = $expr"""
      }
    }

梦境迷离's avatar
梦境迷离 已提交
48 49
    def builderTemplate(typeName: TypeName, fieldss: List[List[Tree]], isCase: Boolean): Tree = {
      val fields = fieldss.flatten
梦境迷离's avatar
梦境迷离 已提交
50 51 52 53 54 55 56 57 58 59 60 61
      val builderClassName = getBuilderClassName(typeName)
      val builderFieldMethods = fields.map(f => fieldSetMethod(typeName, f))
      val builderFieldDefinitions = fields.map(f => fieldDefinition(f))
      q"""
      def builder(): $builderClassName = new $builderClassName()

      class $builderClassName {

          ..$builderFieldDefinitions

          ..$builderFieldMethods

梦境迷离's avatar
梦境迷离 已提交
62
          def build(): $typeName = ${getConstructorWithCurrying(c)(typeName, fieldss, isCase)}
梦境迷离's avatar
梦境迷离 已提交
63 64 65 66 67 68
      }
       """
    }

    // Why use Any? The dependent type need aux-pattern in scala2. Now let's get around this.
    def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
梦境迷离's avatar
梦境迷离 已提交
69
      val (className, fieldss) = classDecl match {
梦境迷离's avatar
梦境迷离 已提交
70 71 72 73 74 75
        // @see https://scala-lang.org/files/archive/spec/2.13/05-classes-and-objects.html
        case q"$mods class $tpname[..$tparams](...$paramss) extends ..$bases { ..$body }" =>
          c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = true)
          (tpname, paramss)
        case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
      }
梦境迷离's avatar
梦境迷离 已提交
76
      c.info(c.enclosingPosition, s"modifiedDeclaration compDeclOpt: $compDeclOpt, fieldss: $fieldss", force = true)
梦境迷离's avatar
梦境迷离 已提交
77 78 79 80 81

      val cName = className match {
        case t: TypeName => t
      }
      val isCase = isCaseClass(c)(classDecl)
梦境迷离's avatar
梦境迷离 已提交
82
      val builder = builderTemplate(cName, fieldss, isCase)
梦境迷离's avatar
梦境迷离 已提交
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
      val compDecl = modifiedCompanion(c)(compDeclOpt, builder, cName)
      c.info(c.enclosingPosition, s"builderTree: $builder, compDecl: $compDecl", force = true)
      // Return both the class and companion object declarations
      c.Expr(
        q"""
        $classDecl
        $compDecl
      """)
    }

    c.info(c.enclosingPosition, s"builder annottees: $annottees", force = true)

    val resTree = handleWithImplType(c)(annottees: _*)(modifiedDeclaration)
    printTree(c)(force = true, resTree.tree)

    resTree
  }
}