toStringMacro.scala 6.0 KB
Newer Older
1
/*
2
 * Copyright (c) 2022 bitlap
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

梦境迷离's avatar
梦境迷离 已提交
22
package org.bitlap.tools.macros
梦境迷离's avatar
梦境迷离 已提交
23 24 25 26 27 28 29 30

import scala.reflect.macros.whitebox

/**
 * @author 梦境迷离
 * @since 2021/7/7
 * @version 1.0
 */
梦境迷离's avatar
梦境迷离 已提交
31
object toStringMacro {
梦境迷离's avatar
梦境迷离 已提交
32

33
  private final case class Argument(includeInternalFields: Boolean, includeFieldNames: Boolean, callSuper: Boolean)
梦境迷离's avatar
梦境迷离 已提交
34

梦境迷离's avatar
梦境迷离 已提交
35 36
  class ToStringProcessor(override val c: whitebox.Context) extends AbstractMacroProcessor(c) {

梦境迷离's avatar
梦境迷离 已提交
37
    import c.universe._
梦境迷离's avatar
梦境迷离 已提交
38

梦境迷离's avatar
梦境迷离 已提交
39
    private def extractTree(aa: Tree, bb: Tree, cc: Tree): (Boolean, Boolean, Boolean) =
梦境迷离's avatar
梦境迷离 已提交
40 41 42
      (
        evalTree[Boolean](aa),
        evalTree[Boolean](bb),
43
        evalTree[Boolean](cc)
梦境迷离's avatar
梦境迷离 已提交
44 45
      )

46 47 48 49 50 51 52 53 54 55 56 57
    private val extractArgumentsDetail: (Boolean, Boolean, Boolean) = c.prefix.tree match {
      case q"new toString(includeInternalFields=$aa, includeFieldNames=$bb, callSuper=$cc)" =>
        extractTree(aa.asInstanceOf[Tree], bb.asInstanceOf[Tree], cc.asInstanceOf[Tree])
      case q"new toString(includeInternalFields=$aa, includeFieldNames=$bb)" =>
        extractTree(aa.asInstanceOf[Tree], bb.asInstanceOf[Tree], q"false")
      case q"new toString(includeInternalFields=$aa)" =>
        extractTree(aa.asInstanceOf[Tree], q"true", q"false")
      case q"new toString(includeFieldNames=$aa)" =>
        extractTree(q"true", aa.asInstanceOf[Tree], q"false")
      case q"new toString(callSuper=$aa)" =>
        extractTree(q"true", q"true", aa.asInstanceOf[Tree])
      case q"new toString()" => (true, true, false)
梦境迷离's avatar
梦境迷离 已提交
58 59 60
      case _                 => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
    }

61
    override def createCustomExpr(classDecl: c.universe.ClassDef, compDeclOpt: Option[c.universe.ModuleDef]): Any = {
梦境迷离's avatar
梦境迷离 已提交
62
      // extract parameters of annotation, must in order
63 64 65
      val argument = Argument(
        extractArgumentsDetail._1,
        extractArgumentsDetail._2,
66
        extractArgumentsDetail._3
67 68
      )
      val resTree = appendClassBody(classDecl, _ => List(getToStringTemplate(argument, classDecl)))
梦境迷离's avatar
梦境迷离 已提交
69
      c.Expr(q"""
70 71 72 73
          ${compDeclOpt.fold(EmptyTree)(x => x)}
          $resTree
         """)
    }
梦境迷离's avatar
梦境迷离 已提交
74

梦境迷离's avatar
梦境迷离 已提交
75
    private def printField(argument: Argument, lastParam: Option[String], field: Tree): Tree =
梦境迷离's avatar
梦境迷离 已提交
76 77 78 79
      // Print one field as <name of the field>+"="+fieldName
      if (argument.includeFieldNames) {
        lastParam.fold(q"$field") { lp =>
          field match {
80
            case v: ValDef =>
梦境迷离's avatar
梦境迷离 已提交
81 82
              if (v.name.toTermName.decodedName.toString != lp)
                q"""${v.name.toTermName.decodedName.toString}+${"="}+this.${v.name}+${", "}"""
83
              else q"""${v.name.toTermName.decodedName.toString}+${"="}+this.${v.name}"""
梦境迷离's avatar
梦境迷离 已提交
84 85 86 87 88 89
            case _ => q"$field"
          }
        }
      } else {
        lastParam.fold(q"$field") { lp =>
          field match {
梦境迷离's avatar
梦境迷离 已提交
90 91 92
            case v: ValDef =>
              if (v.name.toTermName.decodedName.toString != lp) q"""${v.name}+${", "}""" else q"""${v.name}"""
            case _ => if (field.toString() != lp) q"""$field+${", "}""" else q"""$field"""
梦境迷离's avatar
梦境迷离 已提交
93 94
          }
        }
梦境迷离's avatar
梦境迷离 已提交
95
      }
梦境迷离's avatar
梦境迷离 已提交
96

97
    private def getToStringTemplate(argument: Argument, classDecl: ClassDef): Tree = {
梦境迷离's avatar
梦境迷离 已提交
98
      // For a given class definition, separate the components of the class
99
      val classDefinition = mapToClassDeclInfo(classDecl)
梦境迷离's avatar
梦境迷离 已提交
100
      // Check the type of the class, whether it already contains its own toString
101
      val annotteeClassFieldDefinitions = classDefinition.body.filter(_ match {
梦境迷离's avatar
梦境迷离 已提交
102 103
        case _: ValDef => true
        case mem: MemberDef =>
104
          if (mem.name.decodedName.toString.startsWith("toString")) { // TODO better way
梦境迷离's avatar
梦境迷离 已提交
105 106 107 108 109 110
            c.abort(mem.pos, "'toString' method has already defined, please remove it or not use'@toString'")
          }
          false
        case _ => false
      })

111
      val ctorParams = classDefinition.classParamss.flatten
梦境迷离's avatar
梦境迷离 已提交
112
      val member = if (argument.includeInternalFields) ctorParams ++ annotteeClassFieldDefinitions else ctorParams
梦境迷离's avatar
梦境迷离 已提交
113

梦境迷离's avatar
梦境迷离 已提交
114 115 116
      val lastParam = member.lastOption.map {
        case v: ValDef => v.name.toTermName.decodedName.toString
        case c         => c.toString
梦境迷离's avatar
梦境迷离 已提交
117
      }
梦境迷离's avatar
梦境迷离 已提交
118 119
      val paramsWithName = member.foldLeft(q"${""}")((res, acc) => q"$res + ${printField(argument, lastParam, acc)}")
      //scala/bug https://github.com/scala/bug/issues/3967 not be 'Foo(i=1,j=2)' in standard library
梦境迷离's avatar
梦境迷离 已提交
120 121
      val toString =
        q"""override def toString: String = ${classDefinition.className.toTermName.decodedName.toString} + ${"("} + $paramsWithName + ${")"}"""
梦境迷离's avatar
梦境迷离 已提交
122 123

      // Have super class ?
124 125
      if (argument.callSuper && classDefinition.superClasses.nonEmpty) {
        val superClassDef = classDefinition.superClasses.head match {
梦境迷离's avatar
梦境迷离 已提交
126 127 128
          case tree: Tree => Some(tree) // TODO type check better
          case _          => None
        }
梦境迷离's avatar
梦境迷离 已提交
129
        superClassDef.fold(toString) { _ =>
梦境迷离's avatar
梦境迷离 已提交
130
          val superClass = q"${"super="}"
梦境迷离's avatar
梦境迷离 已提交
131 132
          q"override def toString: String = StringContext(${classDefinition.className.toTermName.decodedName.toString} + ${"("} + $superClass, ${if (member.nonEmpty) ", "
          else ""}+$paramsWithName + ${")"}).s(super.toString)"
梦境迷离's avatar
梦境迷离 已提交
133 134 135
        }
      } else {
        toString
梦境迷离's avatar
梦境迷离 已提交
136 137 138 139 140
      }
    }
  }

}