toStringMacro.scala 7.0 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
/*
 * Copyright (c) 2021 jxnu-liguobin && contributors
 *
 * 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 23 24 25 26 27 28 29 30 31
package io.github.dreamylost.macros

import scala.reflect.macros.whitebox

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

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

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

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

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

梦境迷离's avatar
梦境迷离 已提交
49 50
    private val extractArgumentsDetail: (Boolean, Boolean, Boolean, Boolean) = extractArgumentsTuple4 {
      case q"new toString(includeInternalFields=$bb, includeFieldNames=$cc, callSuper=$dd)" =>
梦境迷离's avatar
梦境迷离 已提交
51
        extractTree(q"false", bb.asInstanceOf[Tree], cc.asInstanceOf[Tree], dd.asInstanceOf[Tree])
梦境迷离's avatar
梦境迷离 已提交
52
      case q"new toString(verbose=$aa, includeInternalFields=$bb, includeFieldNames=$cc)" =>
梦境迷离's avatar
梦境迷离 已提交
53
        extractTree(aa.asInstanceOf[Tree], bb.asInstanceOf[Tree], cc.asInstanceOf[Tree], q"false")
梦境迷离's avatar
梦境迷离 已提交
54
      case q"new toString($aa, $bb, $cc)" =>
梦境迷离's avatar
梦境迷离 已提交
55
        extractTree(aa.asInstanceOf[Tree], bb.asInstanceOf[Tree], cc.asInstanceOf[Tree], q"false")
梦境迷离's avatar
梦境迷离 已提交
56
      case q"new toString(verbose=$aa, includeInternalFields=$bb, includeFieldNames=$cc, callSuper=$dd)" =>
梦境迷离's avatar
梦境迷离 已提交
57
        extractTree(aa.asInstanceOf[Tree], bb.asInstanceOf[Tree], cc.asInstanceOf[Tree], dd.asInstanceOf[Tree])
梦境迷离's avatar
梦境迷离 已提交
58
      case q"new toString($aa, $bb, $cc, $dd)" =>
梦境迷离's avatar
梦境迷离 已提交
59
        extractTree(aa.asInstanceOf[Tree], bb.asInstanceOf[Tree], cc.asInstanceOf[Tree], dd.asInstanceOf[Tree])
梦境迷离's avatar
梦境迷离 已提交
60
      case q"new toString(includeInternalFields=$bb, includeFieldNames=$cc)" =>
梦境迷离's avatar
梦境迷离 已提交
61
        extractTree(q"false", bb.asInstanceOf[Tree], cc.asInstanceOf[Tree], q"false")
梦境迷离's avatar
梦境迷离 已提交
62
      case q"new toString(includeInternalFields=$bb)" =>
梦境迷离's avatar
梦境迷离 已提交
63
        extractTree(q"false", bb.asInstanceOf[Tree], q"true", q"false")
梦境迷离's avatar
梦境迷离 已提交
64
      case q"new toString(includeFieldNames=$cc)" =>
梦境迷离's avatar
梦境迷离 已提交
65
        extractTree(q"false", q"true", cc.asInstanceOf[Tree], q"false")
梦境迷离's avatar
梦境迷离 已提交
66 67 68 69
      case q"new toString()" => (false, true, true, false)
      case _                 => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
    }

70
    override def createCustomExpr(classDecl: c.universe.ClassDef, compDeclOpt: Option[c.universe.ModuleDef]): Any = {
梦境迷离's avatar
梦境迷离 已提交
71
      // extract parameters of annotation, must in order
72 73 74 75 76 77 78 79 80 81 82 83 84
      val argument = Argument(
        extractArgumentsDetail._1,
        extractArgumentsDetail._2,
        extractArgumentsDetail._3,
        extractArgumentsDetail._4
      )
      val resTree = appendClassBody(classDecl, _ => List(getToStringTemplate(argument, classDecl)))
      c.Expr(
        q"""
          ${compDeclOpt.fold(EmptyTree)(x => x)}
          $resTree
         """)
    }
梦境迷离's avatar
梦境迷离 已提交
85

86 87 88 89
    override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
      val res = collectCustomExpr(annottees)(createCustomExpr)
      printTree(force = extractArgumentsDetail._1, res.tree)
      res
梦境迷离's avatar
梦境迷离 已提交
90 91
    }

梦境迷离's avatar
梦境迷离 已提交
92
    private def printField(argument: Argument, lastParam: Option[String], field: Tree): Tree = {
梦境迷离's avatar
梦境迷离 已提交
93 94 95 96
      // Print one field as <name of the field>+"="+fieldName
      if (argument.includeFieldNames) {
        lastParam.fold(q"$field") { lp =>
          field match {
97 98 99
            case v: ValDef =>
              if (v.name.toTermName.decodedName.toString != lp) q"""${v.name.toTermName.decodedName.toString}+${"="}+this.${v.name}+${", "}"""
              else q"""${v.name.toTermName.decodedName.toString}+${"="}+this.${v.name}"""
梦境迷离's avatar
梦境迷离 已提交
100 101 102 103 104 105
            case _ => q"$field"
          }
        }
      } else {
        lastParam.fold(q"$field") { lp =>
          field match {
106 107
            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
梦境迷离 已提交
108 109
          }
        }
梦境迷离's avatar
梦境迷离 已提交
110 111
      }
    }
梦境迷离's avatar
梦境迷离 已提交
112

113
    private def getToStringTemplate(argument: Argument, classDecl: ClassDef): Tree = {
梦境迷离's avatar
梦境迷离 已提交
114
      // For a given class definition, separate the components of the class
115
      val classDefinition = mapToClassDeclInfo(classDecl)
梦境迷离's avatar
梦境迷离 已提交
116
      // Check the type of the class, whether it already contains its own toString
117
      val annotteeClassFieldDefinitions = classDefinition.body.filter(_ match {
梦境迷离's avatar
梦境迷离 已提交
118 119
        case _: ValDef => true
        case mem: MemberDef =>
120
          if (mem.name.decodedName.toString.startsWith("toString")) { // TODO better way
梦境迷离's avatar
梦境迷离 已提交
121 122 123 124 125 126
            c.abort(mem.pos, "'toString' method has already defined, please remove it or not use'@toString'")
          }
          false
        case _ => false
      })

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

梦境迷离's avatar
梦境迷离 已提交
130 131 132
      val lastParam = member.lastOption.map {
        case v: ValDef => v.name.toTermName.decodedName.toString
        case c         => c.toString
梦境迷离's avatar
梦境迷离 已提交
133
      }
梦境迷离's avatar
梦境迷离 已提交
134 135
      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
136
      val toString = q"""override def toString: String = ${classDefinition.className.toTermName.decodedName.toString} + ${"("} + $paramsWithName + ${")"}"""
梦境迷离's avatar
梦境迷离 已提交
137 138

      // Have super class ?
139 140
      if (argument.callSuper && classDefinition.superClasses.nonEmpty) {
        val superClassDef = classDefinition.superClasses.head match {
梦境迷离's avatar
梦境迷离 已提交
141 142 143 144 145
          case tree: Tree => Some(tree) // TODO type check better
          case _          => None
        }
        superClassDef.fold(toString)(_ => {
          val superClass = q"${"super="}"
146
          q"override def toString: String = StringContext(${classDefinition.className.toTermName.decodedName.toString} + ${"("} + $superClass, ${if (member.nonEmpty) ", " else ""}+$paramsWithName + ${")"}).s(super.toString)"
梦境迷离's avatar
梦境迷离 已提交
147 148 149 150
        }
        )
      } else {
        toString
梦境迷离's avatar
梦境迷离 已提交
151 152 153 154 155
      }
    }
  }

}