未验证 提交 277059da 编写于 作者: 梦境迷离's avatar 梦境迷离 提交者: GitHub

fix bug, refactor (#81)

* 1.code refactor
2.Fixed constructor parameter mismatch when the first code block of coritization has multiple parameters
上级 1d3b801d
...@@ -37,5 +37,5 @@ import scala.annotation.{ StaticAnnotation, compileTimeOnly } ...@@ -37,5 +37,5 @@ import scala.annotation.{ StaticAnnotation, compileTimeOnly }
final class apply( final class apply(
verbose: Boolean = false verbose: Boolean = false
) extends StaticAnnotation { ) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro applyMacro.impl def macroTransform(annottees: Any*): Any = macro applyMacro.applyProcessor.impl
} }
...@@ -34,5 +34,5 @@ import scala.annotation.{ StaticAnnotation, compileTimeOnly } ...@@ -34,5 +34,5 @@ import scala.annotation.{ StaticAnnotation, compileTimeOnly }
*/ */
@compileTimeOnly("enable macro to expand macro annotations") @compileTimeOnly("enable macro to expand macro annotations")
final class builder extends StaticAnnotation { final class builder extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro builderMacro.impl def macroTransform(annottees: Any*): Any = macro builderMacro.BuilderProcessor.impl
} }
...@@ -39,5 +39,5 @@ final class constructor( ...@@ -39,5 +39,5 @@ final class constructor(
verbose: Boolean = false, verbose: Boolean = false,
excludeFields: Seq[String] = Nil excludeFields: Seq[String] = Nil
) extends StaticAnnotation { ) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro constructorMacro.impl def macroTransform(annottees: Any*): Any = macro constructorMacro.ConstructorProcessor.impl
} }
...@@ -40,5 +40,5 @@ final class equalsAndHashCode( ...@@ -40,5 +40,5 @@ final class equalsAndHashCode(
excludeFields: Seq[String] = Nil excludeFields: Seq[String] = Nil
) extends StaticAnnotation { ) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro equalsAndHashCodeMacro.impl def macroTransform(annottees: Any*): Any = macro equalsAndHashCodeMacro.EqualsAndHashCodeProcessor.impl
} }
...@@ -34,5 +34,5 @@ import scala.annotation.{ StaticAnnotation, compileTimeOnly } ...@@ -34,5 +34,5 @@ import scala.annotation.{ StaticAnnotation, compileTimeOnly }
*/ */
@compileTimeOnly("enable macro to expand macro annotations") @compileTimeOnly("enable macro to expand macro annotations")
final class json extends StaticAnnotation { final class json extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro jsonMacro.impl def macroTransform(annottees: Any*): Any = macro jsonMacro.JsonProcessor.impl
} }
...@@ -40,5 +40,5 @@ final class log( ...@@ -40,5 +40,5 @@ final class log(
verbose: Boolean = false, verbose: Boolean = false,
logType: LogType.LogType = LogType.JLog logType: LogType.LogType = LogType.JLog
) extends StaticAnnotation { ) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro logMacro.impl def macroTransform(annottees: Any*): Any = macro logMacro.LogProcessor.impl
} }
...@@ -39,9 +39,11 @@ object LogType extends Enumeration { ...@@ -39,9 +39,11 @@ object LogType extends Enumeration {
} }
def getLogType(shortType: String): LogType = { def getLogType(shortType: String): LogType = {
val tpe = PACKAGE + "." + shortType val tpe1 = s"$PACKAGE.logs.$shortType" //LogType.JLog
val tpe2 = s"$PACKAGE.logs.LogType.$shortType" // JLog
val v = LogType.values.find(p => { val v = LogType.values.find(p => {
s"$PACKAGE.${p.toString}" == tpe || s"$PACKAGE.$LogType.${p.toString}" == tpe s"$PACKAGE.logs.LogType.${p.toString}" == tpe1 ||
s"$PACKAGE.logs.LogType.${p.toString}" == tpe2 || s"$PACKAGE.logs.LogType.${p.toString}" == shortType
}).getOrElse(throw new Exception(s"Not support log type: $shortType")).toString }).getOrElse(throw new Exception(s"Not support log type: $shortType")).toString
LogType.withName(v) LogType.withName(v)
} }
......
...@@ -24,48 +24,44 @@ package io.github.dreamylost.macros ...@@ -24,48 +24,44 @@ package io.github.dreamylost.macros
import scala.reflect.macros.whitebox import scala.reflect.macros.whitebox
/** /**
* Common methods
* *
* @author 梦境迷离 * @author 梦境迷离
* @since 2021/6/28 * @since 2021/7/24
* @version 1.0 * @version 1.0
*/ */
trait MacroCommon { abstract class AbstractMacroProcessor(val c: whitebox.Context) {
import c.universe._
def impl(annottees: Expr[Any]*): Expr[Any]
/** /**
* Eval tree. * Eval tree.
* *
* @param c
* @param tree * @param tree
* @tparam T * @tparam T
* @return * @return
*/ */
def evalTree[T: c.WeakTypeTag](c: whitebox.Context)(tree: c.Tree): T = c.eval(c.Expr[T](c.untypecheck(tree.duplicate))) def evalTree[T: WeakTypeTag](tree: Tree): T = c.eval(c.Expr[T](c.untypecheck(tree.duplicate)))
def extractArgumentsTuple1[T: c.WeakTypeTag](c: whitebox.Context)(partialFunction: PartialFunction[c.Tree, Tuple1[T]]): Tuple1[T] = {
partialFunction.apply(c.prefix.tree)
}
def extractArgumentsTuple2[T1: c.WeakTypeTag, T2: c.WeakTypeTag](c: whitebox.Context)(partialFunction: PartialFunction[c.Tree, (T1, T2)]): (T1, T2) = { def extractArgumentsTuple1[T: WeakTypeTag](partialFunction: PartialFunction[Tree, Tuple1[T]]): Tuple1[T] = {
partialFunction.apply(c.prefix.tree) partialFunction.apply(c.prefix.tree)
} }
def extractArgumentsTuple3[T1: c.WeakTypeTag, T2: c.WeakTypeTag, T3: c.WeakTypeTag](c: whitebox.Context)(partialFunction: PartialFunction[c.Tree, (T1, T2, T3)]): (T1, T2, T3) = { def extractArgumentsTuple2[T1: WeakTypeTag, T2: WeakTypeTag](partialFunction: PartialFunction[Tree, (T1, T2)]): (T1, T2) = {
partialFunction.apply(c.prefix.tree) partialFunction.apply(c.prefix.tree)
} }
def extractArgumentsTuple4[T1: c.WeakTypeTag, T2: c.WeakTypeTag, T3: c.WeakTypeTag, T4: c.WeakTypeTag](c: whitebox.Context)(partialFunction: PartialFunction[c.Tree, (T1, T2, T3, T4)]): (T1, T2, T3, T4) = { def extractArgumentsTuple4[T1: WeakTypeTag, T2: WeakTypeTag, T3: WeakTypeTag, T4: WeakTypeTag](partialFunction: PartialFunction[Tree, (T1, T2, T3, T4)]): (T1, T2, T3, T4) = {
partialFunction.apply(c.prefix.tree) partialFunction.apply(c.prefix.tree)
} }
/** /**
* Output ast result. * Output ast result.
* *
* @param c
* @param force * @param force
* @param resTree * @param resTree
*/ */
def printTree(c: whitebox.Context)(force: Boolean, resTree: c.Tree): Unit = { def printTree(force: Boolean, resTree: Tree): Unit = {
c.info( c.info(
c.enclosingPosition, c.enclosingPosition,
"\n###### Expanded macro ######\n" + resTree.toString() + "\n###### Expanded macro ######\n", "\n###### Expanded macro ######\n" + resTree.toString() + "\n###### Expanded macro ######\n",
...@@ -76,12 +72,10 @@ trait MacroCommon { ...@@ -76,12 +72,10 @@ trait MacroCommon {
/** /**
* Check the class and its companion object, and return the class definition. * Check the class and its companion object, and return the class definition.
* *
* @param c
* @param annottees * @param annottees
* @return Return ClassDef * @return Return ClassDef
*/ */
def checkAndGetClassDef(c: whitebox.Context)(annottees: c.Expr[Any]*): c.universe.ClassDef = { def checkAndGetClassDef(annottees: Expr[Any]*): ClassDef = {
import c.universe._
annottees.map(_.tree).toList match { annottees.map(_.tree).toList match {
case (classDecl: ClassDef) :: Nil => classDecl case (classDecl: ClassDef) :: Nil => classDecl
case (classDecl: ClassDef) :: (compDecl: ModuleDef) :: Nil => classDecl case (classDecl: ClassDef) :: (compDecl: ModuleDef) :: Nil => classDecl
...@@ -89,31 +83,13 @@ trait MacroCommon { ...@@ -89,31 +83,13 @@ trait MacroCommon {
} }
} }
/**
* Get class if it exists.
*
* @param c
* @param annottees
* @return Return ClassDef without verify.
*/
def tryGetClassDef(c: whitebox.Context)(annottees: c.Expr[Any]*): Option[c.universe.ClassDef] = {
import c.universe._
annottees.map(_.tree).toList match {
case (classDecl: ClassDef) :: Nil => Some(classDecl)
case (classDecl: ClassDef) :: (compDecl: ModuleDef) :: Nil => Some(classDecl)
case _ => None
}
}
/** /**
* Get companion object if it exists. * Get companion object if it exists.
* *
* @param c
* @param annottees * @param annottees
* @return * @return
*/ */
def tryGetCompanionObject(c: whitebox.Context)(annottees: c.Expr[Any]*): Option[c.universe.ModuleDef] = { def tryGetCompanionObject(annottees: Expr[Any]*): Option[ModuleDef] = {
import c.universe._
annottees.map(_.tree).toList match { annottees.map(_.tree).toList match {
case (classDecl: ClassDef) :: Nil => None case (classDecl: ClassDef) :: Nil => None
case (classDecl: ClassDef) :: (compDecl: ModuleDef) :: Nil => Some(compDecl) case (classDecl: ClassDef) :: (compDecl: ModuleDef) :: Nil => Some(compDecl)
...@@ -125,14 +101,12 @@ trait MacroCommon { ...@@ -125,14 +101,12 @@ trait MacroCommon {
/** /**
* Wrap tree result with companion object. * Wrap tree result with companion object.
* *
* @param c
* @param resTree class * @param resTree class
* @param annottees * @param annottees
* @return * @return
*/ */
def treeResultWithCompanionObject(c: whitebox.Context)(resTree: c.Tree, annottees: c.Expr[Any]*): c.universe.Tree = { def treeResultWithCompanionObject(resTree: Tree, annottees: Expr[Any]*): Tree = {
import c.universe._ val companionOpt = tryGetCompanionObject(annottees: _*)
val companionOpt = tryGetCompanionObject(c)(annottees: _*)
if (companionOpt.isEmpty) { if (companionOpt.isEmpty) {
resTree resTree
} else { } else {
...@@ -146,17 +120,15 @@ trait MacroCommon { ...@@ -146,17 +120,15 @@ trait MacroCommon {
/** /**
* Modify the associated object itself according to whether there is an associated object. * Modify the associated object itself according to whether there is an associated object.
* *
* @param c
* @param annottees * @param annottees
* @param modifyAction The actual processing function * @param modifyAction The actual processing function
* @return Return the result of modifyAction * @return Return the result of modifyAction
*/ */
def handleWithImplType(c: whitebox.Context)(annottees: c.Expr[Any]*) def handleWithImplType(annottees: Expr[Any]*)
(modifyAction: (c.universe.ClassDef, Option[c.universe.ModuleDef]) => Any): c.Expr[Nothing] = { (modifyAction: (ClassDef, Option[ModuleDef]) => Any): Expr[Nothing] = {
import c.universe._
annottees.map(_.tree) match { annottees.map(_.tree) match {
case (classDecl: ClassDef) :: Nil => modifyAction(classDecl, None).asInstanceOf[c.Expr[Nothing]] case (classDecl: ClassDef) :: Nil => modifyAction(classDecl, None).asInstanceOf[Expr[Nothing]]
case (classDecl: ClassDef) :: (compDecl: ModuleDef) :: Nil => modifyAction(classDecl, Some(compDecl)).asInstanceOf[c.Expr[Nothing]] case (classDecl: ClassDef) :: (compDecl: ModuleDef) :: Nil => modifyAction(classDecl, Some(compDecl)).asInstanceOf[Expr[Nothing]]
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN) case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
} }
} }
...@@ -164,12 +136,10 @@ trait MacroCommon { ...@@ -164,12 +136,10 @@ trait MacroCommon {
/** /**
* Expand the class and check whether the class is a case class. * Expand the class and check whether the class is a case class.
* *
* @param c
* @param annotateeClass classDef * @param annotateeClass classDef
* @return Return true if it is a case class * @return Return true if it is a case class
*/ */
def isCaseClass(c: whitebox.Context)(annotateeClass: c.universe.ClassDef): Boolean = { def isCaseClass(annotateeClass: ClassDef): Boolean = {
import c.universe._
annotateeClass match { annotateeClass match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" => case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
if (mods.asInstanceOf[Modifiers].hasFlag(Flag.CASE)) { if (mods.asInstanceOf[Modifiers].hasFlag(Flag.CASE)) {
...@@ -183,12 +153,10 @@ trait MacroCommon { ...@@ -183,12 +153,10 @@ trait MacroCommon {
/** /**
* Expand the constructor and get the field TermName. * Expand the constructor and get the field TermName.
* *
* @param c
* @param field * @param field
* @return * @return
*/ */
def getFieldTermName(c: whitebox.Context)(field: c.universe.Tree): c.universe.TermName = { def getFieldTermName(field: Tree): TermName = {
import c.universe._
field match { field match {
case q"$mods val $tname: $tpt = $expr" => tname.asInstanceOf[TermName] case q"$mods val $tname: $tpt = $expr" => tname.asInstanceOf[TermName]
case q"$mods var $tname: $tpt = $expr" => tname.asInstanceOf[TermName] case q"$mods var $tname: $tpt = $expr" => tname.asInstanceOf[TermName]
...@@ -200,12 +168,10 @@ trait MacroCommon { ...@@ -200,12 +168,10 @@ trait MacroCommon {
/** /**
* Expand the method params and get the param Name. * Expand the method params and get the param Name.
* *
* @param c
* @param field * @param field
* @return * @return
*/ */
def getMethodParamName(c: whitebox.Context)(field: c.universe.Tree): c.universe.Name = { def getMethodParamName(field: Tree): Name = {
import c.universe._
field match { field match {
case q"$mods val $tname: $tpt = $expr" => tpt.asInstanceOf[Ident].name.decodedName case q"$mods val $tname: $tpt = $expr" => tpt.asInstanceOf[Ident].name.decodedName
} }
...@@ -214,12 +180,10 @@ trait MacroCommon { ...@@ -214,12 +180,10 @@ trait MacroCommon {
/** /**
* Check whether the mods of the fields has a `private[this]`, because it cannot be used in equals method. * Check whether the mods of the fields has a `private[this]`, because it cannot be used in equals method.
* *
* @param c
* @param field * @param field
* @return * @return
*/ */
def classParamsIsPrivate(c: whitebox.Context)(field: c.universe.Tree): Boolean = { def classParamsIsPrivate(field: Tree): Boolean = {
import c.universe._
field match { field match {
case q"$mods val $tname: $tpt = $expr" => if (mods.asInstanceOf[Modifiers].hasFlag(Flag.PRIVATE)) false else true case q"$mods val $tname: $tpt = $expr" => if (mods.asInstanceOf[Modifiers].hasFlag(Flag.PRIVATE)) false else true
case q"$mods var $tname: $tpt = $expr" => true case q"$mods var $tname: $tpt = $expr" => true
...@@ -229,12 +193,10 @@ trait MacroCommon { ...@@ -229,12 +193,10 @@ trait MacroCommon {
/** /**
* Expand the constructor and get the field with assign. * Expand the constructor and get the field with assign.
* *
* @param c
* @param annotteeClassParams * @param annotteeClassParams
* @return * @return
*/ */
def getFieldAssignExprs(c: whitebox.Context)(annotteeClassParams: Seq[c.Tree]): Seq[c.Tree] = { def getFieldAssignExprs(annotteeClassParams: Seq[Tree]): Seq[Tree] = {
import c.universe._
annotteeClassParams.map { annotteeClassParams.map {
case q"$mods var $tname: $tpt = $expr" => q"$tname: $tpt" //Ignore expr case q"$mods var $tname: $tpt = $expr" => q"$tname: $tpt" //Ignore expr
case q"$mods val $tname: $tpt = $expr" => q"$tname: $tpt" case q"$mods val $tname: $tpt = $expr" => q"$tname: $tpt"
...@@ -244,16 +206,14 @@ trait MacroCommon { ...@@ -244,16 +206,14 @@ trait MacroCommon {
/** /**
* Modify companion objects. * Modify companion objects.
* *
* @param c
* @param compDeclOpt * @param compDeclOpt
* @param codeBlock * @param codeBlock
* @param className * @param className
* @return * @return
*/ */
def modifiedCompanion(c: whitebox.Context)( def modifiedCompanion(
compDeclOpt: Option[c.universe.ModuleDef], compDeclOpt: Option[ModuleDef],
codeBlock: c.Tree, className: c.TypeName): c.universe.Tree = { codeBlock: Tree, className: TypeName): Tree = {
import c.universe._
compDeclOpt map { compDecl => compDeclOpt map { compDecl =>
val q"$mods object $obj extends ..$bases { ..$body }" = compDecl val q"$mods object $obj extends ..$bases { ..$body }" = compDecl
val o = val o =
...@@ -276,11 +236,9 @@ trait MacroCommon { ...@@ -276,11 +236,9 @@ trait MacroCommon {
/** /**
* Extract the internal fields of members belonging to the class, but not in primary constructor. * Extract the internal fields of members belonging to the class, but not in primary constructor.
* *
* @param c
* @param annotteeClassDefinitions * @param annotteeClassDefinitions
*/ */
def getClassMemberValDefs(c: whitebox.Context)(annotteeClassDefinitions: Seq[c.Tree]): Seq[c.Tree] = { def getClassMemberValDefs(annotteeClassDefinitions: Seq[Tree]): Seq[Tree] = {
import c.universe._
annotteeClassDefinitions.filter(p => p match { annotteeClassDefinitions.filter(p => p match {
case _: ValDef => true case _: ValDef => true
case _ => false case _ => false
...@@ -290,11 +248,9 @@ trait MacroCommon { ...@@ -290,11 +248,9 @@ trait MacroCommon {
/** /**
* Extract the methods belonging to the class, contains Secondary Constructor. * Extract the methods belonging to the class, contains Secondary Constructor.
* *
* @param c
* @param annotteeClassDefinitions * @param annotteeClassDefinitions
*/ */
def getClassMemberDefDefs(c: whitebox.Context)(annotteeClassDefinitions: Seq[c.Tree]): Seq[c.Tree] = { def getClassMemberDefDefs(annotteeClassDefinitions: Seq[Tree]): Seq[Tree] = {
import c.universe._
annotteeClassDefinitions.filter(p => p match { annotteeClassDefinitions.filter(p => p match {
case _: DefDef => true case _: DefDef => true
case _ => false case _ => false
...@@ -310,9 +266,8 @@ trait MacroCommon { ...@@ -310,9 +266,8 @@ trait MacroCommon {
* @return A constructor with currying, it not contains tpt, provide for calling method. * @return A constructor with currying, it not contains tpt, provide for calling method.
* @example [[new TestClass12(i)(j)(k)(t)]] * @example [[new TestClass12(i)(j)(k)(t)]]
*/ */
def getConstructorWithCurrying(c: whitebox.Context)(typeName: c.TypeName, fieldss: List[List[c.Tree]], isCase: Boolean): c.Tree = { def getConstructorWithCurrying(typeName: TypeName, fieldss: List[List[Tree]], isCase: Boolean): Tree = {
import c.universe._ val allFieldsTermName = fieldss.map(f => f.map(ff => getFieldTermName(ff)))
val allFieldsTermName = fieldss.map(f => f.map(ff => getFieldTermName(c)(ff)))
// not currying // not currying
val constructor = if (fieldss.isEmpty || fieldss.size == 1) { val constructor = if (fieldss.isEmpty || fieldss.size == 1) {
q"${if (isCase) q"${typeName.toTermName}(..${allFieldsTermName.flatten})" else q"new $typeName(..${allFieldsTermName.flatten})"}" q"${if (isCase) q"${typeName.toTermName}(..${allFieldsTermName.flatten})" else q"new $typeName(..${allFieldsTermName.flatten})"}"
...@@ -320,7 +275,7 @@ trait MacroCommon { ...@@ -320,7 +275,7 @@ trait MacroCommon {
// currying // currying
val first = allFieldsTermName.head val first = allFieldsTermName.head
if (isCase) q"${typeName.toTermName}(...$first)(...${allFieldsTermName.tail})" if (isCase) q"${typeName.toTermName}(...$first)(...${allFieldsTermName.tail})"
else q"new $typeName(...$first)(...${allFieldsTermName.tail})" else q"new $typeName(..$first)(...${allFieldsTermName.tail})"
} }
c.info(c.enclosingPosition, s"getConstructorWithCurrying constructor: $constructor, paramss: $fieldss", force = true) c.info(c.enclosingPosition, s"getConstructorWithCurrying constructor: $constructor, paramss: $fieldss", force = true)
constructor constructor
...@@ -334,17 +289,16 @@ trait MacroCommon { ...@@ -334,17 +289,16 @@ trait MacroCommon {
* @return A apply method with currying. * @return A apply method with currying.
* @example [[def apply(int: Int)(j: Int)(k: Option[String])(t: Option[Long]): B3 = new B3(int)(j)(k)(t)]] * @example [[def apply(int: Int)(j: Int)(k: Option[String])(t: Option[Long]): B3 = new B3(int)(j)(k)(t)]]
*/ */
def getApplyMethodWithCurrying(c: whitebox.Context)(typeName: c.TypeName, fieldss: List[List[c.Tree]], classTypeParams: List[c.Tree]): c.Tree = { def getApplyMethodWithCurrying(typeName: TypeName, fieldss: List[List[Tree]], classTypeParams: List[Tree]): Tree = {
import c.universe._ val allFieldsTermName = fieldss.map(f => getFieldAssignExprs(f))
val allFieldsTermName = fieldss.map(f => getFieldAssignExprs(c)(f)) val returnTypeParams = extractClassTypeParamsTypeName(classTypeParams)
val returnTypeParams = extractClassTypeParamsTypeName(c)(classTypeParams)
// not currying // not currying
val applyMethod = if (fieldss.isEmpty || fieldss.size == 1) { val applyMethod = if (fieldss.isEmpty || fieldss.size == 1) {
q"def apply[..$classTypeParams](..${allFieldsTermName.flatten}): $typeName[..$returnTypeParams] = ${getConstructorWithCurrying(c)(typeName, fieldss, isCase = false)}" q"def apply[..$classTypeParams](..${allFieldsTermName.flatten}): $typeName[..$returnTypeParams] = ${getConstructorWithCurrying(typeName, fieldss, isCase = false)}"
} else { } else {
// currying // currying
val first = allFieldsTermName.head val first = allFieldsTermName.head
q"def apply[..$classTypeParams](..$first)(...${allFieldsTermName.tail}): $typeName[..$returnTypeParams] = ${getConstructorWithCurrying(c)(typeName, fieldss, isCase = false)}" q"def apply[..$classTypeParams](..$first)(...${allFieldsTermName.tail}): $typeName[..$returnTypeParams] = ${getConstructorWithCurrying(typeName, fieldss, isCase = false)}"
} }
c.info(c.enclosingPosition, s"getApplyWithCurrying constructor: $applyMethod, paramss: $fieldss", force = true) c.info(c.enclosingPosition, s"getApplyWithCurrying constructor: $applyMethod, paramss: $fieldss", force = true)
applyMethod applyMethod
...@@ -375,14 +329,13 @@ trait MacroCommon { ...@@ -375,14 +329,13 @@ trait MacroCommon {
* Gets a list of generic parameters. * Gets a list of generic parameters.
* This is because the generic parameters of a class cannot be used directly in the return type, and need to be converted. * This is because the generic parameters of a class cannot be used directly in the return type, and need to be converted.
* *
* @param c
* @param tpParams * @param tpParams
* @return * @return
*/ */
def extractClassTypeParamsTypeName(c: whitebox.Context)(tpParams: List[c.Tree]): List[c.TypeName] = { def extractClassTypeParamsTypeName(tpParams: List[Tree]): List[TypeName] = {
import c.universe._
tpParams.map { tpParams.map {
case t: TypeDef => t.name case t: TypeDef => t.name
} }
} }
} }
...@@ -29,45 +29,46 @@ import scala.reflect.macros.whitebox ...@@ -29,45 +29,46 @@ import scala.reflect.macros.whitebox
* @since 2021/7/7 * @since 2021/7/7
* @version 1.0 * @version 1.0
*/ */
object applyMacro extends MacroCommon { object applyMacro {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val args: Tuple1[Boolean] = extractArgumentsTuple1(c) {
case q"new apply(verbose=$verbose)" => Tuple1(evalTree(c)(verbose.asInstanceOf[Tree]))
case q"new apply()" => Tuple1(false)
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
}
c.info(c.enclosingPosition, s"annottees: $annottees, args: $args", force = args._1)
val annotateeClass: ClassDef = checkAndGetClassDef(c)(annottees: _*) class applyProcessor(override val c: whitebox.Context) extends AbstractMacroProcessor(c) {
val isCase: Boolean = isCaseClass(c)(annotateeClass)
c.info(c.enclosingPosition, s"impl argument: $args, isCase: $isCase", force = args._1)
if (isCase) c.abort(c.enclosingPosition, s"Annotation is only supported on 'case class'") import c.universe._
def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = { override def impl(annottees: Expr[Any]*): Expr[Any] = {
val (className, classParams, classTypeParams) = classDecl match { val args: Tuple1[Boolean] = extractArgumentsTuple1 {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$bases { ..$body }" => case q"new apply(verbose=$verbose)" => Tuple1(evalTree(verbose.asInstanceOf[Tree]))
c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = args._1) case q"new apply()" => Tuple1(false)
(tpname, paramss.asInstanceOf[List[List[Tree]]], tparams.asInstanceOf[List[Tree]]) case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
} }
c.info(c.enclosingPosition, s"modifiedDeclaration compDeclOpt: $compDeclOpt, annotteeClassParams: $classParams", force = args._1) val annotateeClass: ClassDef = checkAndGetClassDef(annottees: _*)
val tpName = className.asInstanceOf[TypeName] val isCase: Boolean = isCaseClass(annotateeClass)
val apply = getApplyMethodWithCurrying(c)(tpName, classParams, classTypeParams) c.info(c.enclosingPosition, s"impl argument: $args, isCase: $isCase", force = args._1)
val compDecl = modifiedCompanion(c)(compDeclOpt, apply, tpName)
c.Expr( if (isCase) c.abort(c.enclosingPosition, s"Annotation is only supported on 'case class'")
q"""
def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val (className, classParams, classTypeParams) = classDecl match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$bases { ..$body }" =>
c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = args._1)
(tpname, paramss.asInstanceOf[List[List[Tree]]], tparams.asInstanceOf[List[Tree]])
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
}
c.info(c.enclosingPosition, s"modifiedDeclaration compDeclOpt: $compDeclOpt, annotteeClassParams: $classParams", force = args._1)
val tpName = className.asInstanceOf[TypeName]
val apply = getApplyMethodWithCurrying(tpName, classParams, classTypeParams)
val compDecl = modifiedCompanion(compDeclOpt, apply, tpName)
c.Expr(
q"""
$classDecl $classDecl
$compDecl $compDecl
""") """)
} }
val resTree = handleWithImplType(c)(annottees: _*)(modifiedDeclaration)
printTree(c)(force = args._1, resTree.tree)
resTree val resTree = handleWithImplType(annottees: _*)(modifiedDeclaration)
printTree(force = args._1, resTree.tree)
resTree
}
} }
} }
...@@ -29,51 +29,54 @@ import scala.reflect.macros.whitebox ...@@ -29,51 +29,54 @@ import scala.reflect.macros.whitebox
* @since 2021/7/7 * @since 2021/7/7
* @version 1.0 * @version 1.0
*/ */
object builderMacro extends MacroCommon { object builderMacro {
private final val BUFFER_CLASS_NAME_SUFFIX = "Builder" private final val BUFFER_CLASS_NAME_SUFFIX = "Builder"
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { class BuilderProcessor(override val c: whitebox.Context) extends AbstractMacroProcessor(c) {
import c.universe._ import c.universe._
def getBuilderClassName(classTree: TypeName): TypeName = { override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
TypeName(classTree.toTermName.decodedName.toString + BUFFER_CLASS_NAME_SUFFIX) def getBuilderClassName(classTree: TypeName): TypeName = {
} TypeName(classTree.toTermName.decodedName.toString + BUFFER_CLASS_NAME_SUFFIX)
}
def fieldSetMethod(typeName: TypeName, field: Tree, classTypeParams: List[Tree]): c.Tree = { def fieldSetMethod(typeName: TypeName, field: Tree, classTypeParams: List[Tree]): Tree = {
val builderClassName = getBuilderClassName(typeName) val builderClassName = getBuilderClassName(typeName)
val returnTypeParams = extractClassTypeParamsTypeName(c)(classTypeParams) val returnTypeParams = extractClassTypeParamsTypeName(classTypeParams)
field match { field match {
case q"$mods var $tname: $tpt = $expr" => case q"$mods var $tname: $tpt = $expr" =>
q""" q"""
def $tname($tname: $tpt): $builderClassName[..$returnTypeParams] = { def $tname($tname: $tpt): $builderClassName[..$returnTypeParams] = {
this.$tname = $tname this.$tname = $tname
this this
} }
""" """
case q"$mods val $tname: $tpt = $expr" => case q"$mods val $tname: $tpt = $expr" =>
q""" q"""
def $tname($tname: $tpt): $builderClassName[..$returnTypeParams] = { def $tname($tname: $tpt): $builderClassName[..$returnTypeParams] = {
this.$tname = $tname this.$tname = $tname
this this
} }
""" """
}
} }
}
def fieldDefinition(field: Tree): Tree = { def fieldDefinition(field: Tree): Tree = {
field match { field match {
case q"$mods val $tname: $tpt = $expr" => q"""private var $tname: $tpt = $expr""" 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""" case q"$mods var $tname: $tpt = $expr" => q"""private var $tname: $tpt = $expr"""
}
} }
}
def builderTemplate(typeName: TypeName, fieldss: List[List[Tree]], classTypeParams: List[Tree], isCase: Boolean): Tree = { def builderTemplate(typeName: TypeName, fieldss: List[List[Tree]], classTypeParams: List[Tree], isCase: Boolean): Tree = {
val fields = fieldss.flatten val fields = fieldss.flatten
val builderClassName = getBuilderClassName(typeName) val builderClassName = getBuilderClassName(typeName)
val builderFieldMethods = fields.map(f => fieldSetMethod(typeName, f, classTypeParams)) val builderFieldMethods = fields.map(f => fieldSetMethod(typeName, f, classTypeParams))
val builderFieldDefinitions = fields.map(f => fieldDefinition(f)) val builderFieldDefinitions = fields.map(f => fieldDefinition(f))
val returnTypeParams = extractClassTypeParamsTypeName(c)(classTypeParams) val returnTypeParams = extractClassTypeParamsTypeName(classTypeParams)
q""" q"""
def builder[..$classTypeParams](): $builderClassName[..$returnTypeParams] = new $builderClassName() def builder[..$classTypeParams](): $builderClassName[..$returnTypeParams] = new $builderClassName()
class $builderClassName[..$classTypeParams] { class $builderClassName[..$classTypeParams] {
...@@ -82,40 +85,39 @@ object builderMacro extends MacroCommon { ...@@ -82,40 +85,39 @@ object builderMacro extends MacroCommon {
..$builderFieldMethods ..$builderFieldMethods
def build(): $typeName[..$returnTypeParams] = ${getConstructorWithCurrying(c)(typeName, fieldss, isCase)} def build(): $typeName[..$returnTypeParams] = ${getConstructorWithCurrying(typeName, fieldss, isCase)}
} }
""" """
}
// 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 = {
val (className, fieldss, classTypeParams) = classDecl match {
// @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.asInstanceOf[List[List[Tree]]], tparams.asInstanceOf[List[Tree]])
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
} }
c.info(c.enclosingPosition, s"modifiedDeclaration compDeclOpt: $compDeclOpt, fieldss: $fieldss", force = true)
// Why use Any? The dependent type need aux-pattern in scala2. Now let's get around this.
val cName = className.asInstanceOf[TypeName] def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val isCase = isCaseClass(c)(classDecl) val (className, fieldss, classTypeParams) = classDecl match {
val builder = builderTemplate(cName, fieldss, classTypeParams, isCase) // @see https://scala-lang.org/files/archive/spec/2.13/05-classes-and-objects.html
val compDecl = modifiedCompanion(c)(compDeclOpt, builder, cName) case q"$mods class $tpname[..$tparams](...$paramss) extends ..$bases { ..$body }" =>
c.info(c.enclosingPosition, s"builderTree: $builder, compDecl: $compDecl", force = true) c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = true)
// Return both the class and companion object declarations (tpname, paramss.asInstanceOf[List[List[Tree]]], tparams.asInstanceOf[List[Tree]])
c.Expr( case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
q""" }
val cName = className.asInstanceOf[TypeName]
val isCase = isCaseClass(classDecl)
val builder = builderTemplate(cName, fieldss, classTypeParams, isCase)
val compDecl = modifiedCompanion(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 $classDecl
$compDecl $compDecl
""") """)
} }
c.info(c.enclosingPosition, s"builder annottees: $annottees", force = true)
val resTree = handleWithImplType(c)(annottees: _*)(modifiedDeclaration) val resTree = handleWithImplType(annottees: _*)(modifiedDeclaration)
printTree(c)(force = true, resTree.tree) printTree(force = true, resTree.tree)
resTree resTree
}
} }
} }
...@@ -29,109 +29,111 @@ import scala.reflect.macros.whitebox ...@@ -29,109 +29,111 @@ import scala.reflect.macros.whitebox
* @since 2021/7/7 * @since 2021/7/7
* @version 1.0 * @version 1.0
*/ */
object constructorMacro extends MacroCommon { object constructorMacro {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val args: (Boolean, Seq[String]) = extractArgumentsTuple2(c) {
case q"new constructor(verbose=$verbose)" => (evalTree(c)(verbose.asInstanceOf[Tree]), Nil)
case q"new constructor(excludeFields=$excludeFields)" => (false, evalTree(c)(excludeFields.asInstanceOf[Tree]))
case q"new constructor(verbose=$verbose, excludeFields=$excludeFields)" => (evalTree(c)(verbose.asInstanceOf[Tree]), evalTree(c)(excludeFields.asInstanceOf[Tree]))
case q"new constructor()" => (false, Nil)
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
}
val annotateeClass: ClassDef = checkAndGetClassDef(c)(annottees: _*) class ConstructorProcessor(override val c: whitebox.Context) extends AbstractMacroProcessor(c) {
val isCase: Boolean = isCaseClass(c)(annotateeClass)
if (isCase) {
c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $annotateeClass")
}
c.info(c.enclosingPosition, s"annottees: $annottees, annotateeClass: $annotateeClass", args._1) import c.universe._
def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = { override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
val (annotteeClassParams, annotteeClassDefinitions) = classDecl match { val args: (Boolean, Seq[String]) = extractArgumentsTuple2 {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" => case q"new constructor(verbose=$verbose)" => (evalTree(verbose.asInstanceOf[Tree]), Nil)
c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = args._1) case q"new constructor(excludeFields=$excludeFields)" => (false, evalTree(excludeFields.asInstanceOf[Tree]))
(paramss.asInstanceOf[List[List[Tree]]], stats.asInstanceOf[Seq[Tree]]) case q"new constructor(verbose=$verbose, excludeFields=$excludeFields)" => (evalTree(verbose.asInstanceOf[Tree]), evalTree(excludeFields.asInstanceOf[Tree]))
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl") case q"new constructor()" => (false, Nil)
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
} }
// Extract the internal fields of members belonging to the class, but not in primary constructor. val annotateeClass: ClassDef = checkAndGetClassDef(annottees: _*)
val classFieldDefinitions = getClassMemberValDefs(c)(annotteeClassDefinitions) val isCase: Boolean = isCaseClass(annotateeClass)
val excludeFields = args._2 if (isCase) {
c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $annotateeClass")
}
/** def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
* Extract the internal fields of members belonging to the class, but not in primary constructor and only `var`. val (annotteeClassParams, annotteeClassDefinitions) = classDecl match {
*/ case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
def getClassMemberVarDefOnlyAssignExpr: Seq[c.Tree] = { c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = args._1)
import c.universe._ (paramss.asInstanceOf[List[List[Tree]]], stats.asInstanceOf[Seq[Tree]])
getClassMemberValDefs(c)(annotteeClassDefinitions).filter(_ match { case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
case q"$mods var $tname: $tpt = $expr" if !excludeFields.contains(tname.asInstanceOf[TermName].decodedName.toString) => true
case _ => false
}).map {
case q"$mods var $pat = $expr" =>
// TODO getClass RETURN a java type, maybe we can try use class reflect to get the fields type name.
q"$pat: ${TypeName(toScalaType(evalTree(c)(expr.asInstanceOf[Tree]).getClass.getTypeName))}"
case q"$mods var $tname: $tpt = $expr" => q"$tname: $tpt"
} }
}
val classFieldDefinitionsOnlyAssignExpr = getClassMemberVarDefOnlyAssignExpr // Extract the internal fields of members belonging to the class, but not in primary constructor.
val classFieldDefinitions = getClassMemberValDefs(annotteeClassDefinitions)
val excludeFields = args._2
/**
* Extract the internal fields of members belonging to the class, but not in primary constructor and only `var`.
*/
def getClassMemberVarDefOnlyAssignExpr: Seq[Tree] = {
import c.universe._
getClassMemberValDefs(annotteeClassDefinitions).filter(_ match {
case q"$mods var $tname: $tpt = $expr" if !excludeFields.contains(tname.asInstanceOf[TermName].decodedName.toString) => true
case _ => false
}).map {
case q"$mods var $pat = $expr" =>
// TODO getClass RETURN a java type, maybe we can try use class reflect to get the fields type name.
q"$pat: ${TypeName(toScalaType(evalTree(expr.asInstanceOf[Tree]).getClass.getTypeName))}"
case q"$mods var $tname: $tpt = $expr" => q"$tname: $tpt"
}
}
if (classFieldDefinitionsOnlyAssignExpr.isEmpty) { val classFieldDefinitionsOnlyAssignExpr = getClassMemberVarDefOnlyAssignExpr
c.abort(c.enclosingPosition, s"Annotation is only supported on class when the internal field (declare as 'var') is nonEmpty. classDef: $classDecl")
}
val annotteeClassFieldNames = classFieldDefinitions.filter(_ match { if (classFieldDefinitionsOnlyAssignExpr.isEmpty) {
case q"$mods var $tname: $tpt = $expr" if !excludeFields.contains(tname.asInstanceOf[TermName].decodedName.toString) => true c.abort(c.enclosingPosition, s"Annotation is only supported on class when the internal field (declare as 'var') is nonEmpty. classDef: $classDecl")
case _ => false }
}).map {
case q"$mods var $tname: $tpt = $expr" => tname.asInstanceOf[TermName]
}
c.info(c.enclosingPosition, s"modifiedDeclaration compDeclOpt: $compDeclOpt, annotteeClassParams: $annotteeClassParams", force = args._1) val annotteeClassFieldNames = classFieldDefinitions.filter(_ match {
case q"$mods var $tname: $tpt = $expr" if !excludeFields.contains(tname.asInstanceOf[TermName].decodedName.toString) => true
case _ => false
}).map {
case q"$mods var $tname: $tpt = $expr" => tname.asInstanceOf[TermName]
}
// Extract the field of the primary constructor. c.info(c.enclosingPosition, s"modifiedDeclaration compDeclOpt: $compDeclOpt, annotteeClassParams: $annotteeClassParams", force = args._1)
val allFieldsTermName = annotteeClassParams.map(f => f.map(ff => getFieldTermName(c)(ff)))
/**
* We generate this method with currying, and we have to deal with the first layer of currying alone.
*/
def getThisMethodWithCurrying: c.Tree = {
// not currying
// Extract the field of the primary constructor. // Extract the field of the primary constructor.
val classParamsAssignExpr = getFieldAssignExprs(c)(annotteeClassParams.flatten) val allFieldsTermName = annotteeClassParams.map(f => f.map(ff => getFieldTermName(ff)))
val applyMethod = if (annotteeClassParams.isEmpty || annotteeClassParams.size == 1) {
q""" /**
* We generate this method with currying, and we have to deal with the first layer of currying alone.
*/
def getThisMethodWithCurrying: Tree = {
// not currying
// Extract the field of the primary constructor.
val classParamsAssignExpr = getFieldAssignExprs(annotteeClassParams.flatten)
val applyMethod = if (annotteeClassParams.isEmpty || annotteeClassParams.size == 1) {
q"""
def this(..${classParamsAssignExpr ++ classFieldDefinitionsOnlyAssignExpr}) = { def this(..${classParamsAssignExpr ++ classFieldDefinitionsOnlyAssignExpr}) = {
this(..${allFieldsTermName.flatten}) this(..${allFieldsTermName.flatten})
..${annotteeClassFieldNames.map(f => q"this.$f = $f")} ..${annotteeClassFieldNames.map(f => q"this.$f = $f")}
} }
""" """
} else { } else {
// NOTE: currying constructor overload must be placed in the first bracket block. // NOTE: currying constructor overload must be placed in the first bracket block.
val allClassParamsAssignExpr = annotteeClassParams.map(cc => getFieldAssignExprs(c)(cc)) val allClassParamsAssignExpr = annotteeClassParams.map(cc => getFieldAssignExprs(cc))
q""" q"""
def this(..${allClassParamsAssignExpr.head ++ classFieldDefinitionsOnlyAssignExpr})(...${allClassParamsAssignExpr.tail}) = { def this(..${allClassParamsAssignExpr.head ++ classFieldDefinitionsOnlyAssignExpr})(...${allClassParamsAssignExpr.tail}) = {
this(..${allFieldsTermName.head})(...${allFieldsTermName.tail}) this(..${allFieldsTermName.head})(...${allFieldsTermName.tail})
..${annotteeClassFieldNames.map(f => q"this.$f = $f")} ..${annotteeClassFieldNames.map(f => q"this.$f = $f")}
} }
""" """
}
applyMethod
} }
applyMethod
}
val resTree = annotateeClass match { val resTree = annotateeClass match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" => case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${stats.toList.:+(getThisMethodWithCurrying)} }" q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${stats.toList.:+(getThisMethodWithCurrying)} }"
}
c.Expr[Any](treeResultWithCompanionObject(resTree, annottees: _*))
} }
c.Expr[Any](treeResultWithCompanionObject(c)(resTree, annottees: _*))
}
val resTree = handleWithImplType(c)(annottees: _*)(modifiedDeclaration)
printTree(c)(force = args._1, resTree.tree)
resTree val resTree = handleWithImplType(annottees: _*)(modifiedDeclaration)
printTree(force = args._1, resTree.tree)
resTree
}
} }
} }
...@@ -29,73 +29,76 @@ import scala.reflect.macros.whitebox ...@@ -29,73 +29,76 @@ import scala.reflect.macros.whitebox
* @since 2021/7/18 * @since 2021/7/18
* @version 1.0 * @version 1.0
*/ */
object equalsAndHashCodeMacro extends MacroCommon { object equalsAndHashCodeMacro {
class EqualsAndHashCodeProcessor(override val c: whitebox.Context) extends AbstractMacroProcessor(c) {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._ import c.universe._
val args: (Boolean, Seq[String]) = extractArgumentsTuple2(c) {
case q"new equalsAndHashCode(verbose=$verbose)" => (evalTree(c)(verbose.asInstanceOf[Tree]), Nil)
case q"new equalsAndHashCode(excludeFields=$excludeFields)" => (false, evalTree(c)(excludeFields.asInstanceOf[Tree]))
case q"new equalsAndHashCode(verbose=$verbose, excludeFields=$excludeFields)" => (evalTree(c)(verbose.asInstanceOf[Tree]), evalTree(c)(excludeFields.asInstanceOf[Tree]))
case q"new equalsAndHashCode()" => (false, Nil)
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
}
val annotateeClass: ClassDef = checkAndGetClassDef(c)(annottees: _*) override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
val isCase: Boolean = isCaseClass(c)(annotateeClass) val args: (Boolean, Seq[String]) = extractArgumentsTuple2 {
if (isCase) { case q"new equalsAndHashCode(verbose=$verbose)" => (evalTree(verbose.asInstanceOf[Tree]), Nil)
c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $annotateeClass") case q"new equalsAndHashCode(excludeFields=$excludeFields)" => (false, evalTree(excludeFields.asInstanceOf[Tree]))
} case q"new equalsAndHashCode(verbose=$verbose, excludeFields=$excludeFields)" => (evalTree(verbose.asInstanceOf[Tree]), evalTree(excludeFields.asInstanceOf[Tree]))
val excludeFields = args._2 case q"new equalsAndHashCode()" => (false, Nil)
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val (className, annotteeClassParams, annotteeClassDefinitions, superClasses) = classDecl match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = args._1)
(tpname, paramss.asInstanceOf[List[List[Tree]]], stats.asInstanceOf[Seq[Tree]], parents)
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
}
val ctorFieldNames = annotteeClassParams.flatten.filter(cf => classParamsIsPrivate(c)(cf))
val allFieldsTermName = ctorFieldNames.map(f => getFieldTermName(c)(f))
c.info(c.enclosingPosition, s"modifiedDeclaration compDeclOpt: $compDeclOpt, ctorFieldNames: $ctorFieldNames, " +
s"annotteeClassParams: $superClasses", force = args._1)
/**
* Extract the internal fields of members belonging to the class.
*/
def getClassMemberAllTermName: Seq[c.TermName] = {
getClassMemberValDefs(c)(annotteeClassDefinitions).filter(_ match {
case q"$mods var $tname: $tpt = $expr" if !excludeFields.contains(tname.asInstanceOf[TermName].decodedName.toString) => true
case q"$mods val $tname: $tpt = $expr" if !excludeFields.contains(tname.asInstanceOf[TermName].decodedName.toString) => true
case q"$mods val $pat = $expr" if !excludeFields.contains(pat.asInstanceOf[TermName].decodedName.toString) => true
case q"$mods var $pat = $expr" if !excludeFields.contains(pat.asInstanceOf[TermName].decodedName.toString) => true
case _ => false
}).map(f => getFieldTermName(c)(f))
} }
val existsCanEqual = getClassMemberDefDefs(c)(annotteeClassDefinitions) exists { val annotateeClass: ClassDef = checkAndGetClassDef(annottees: _*)
case q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr" if tname.toString() == "canEqual" && paramss.nonEmpty => val isCase: Boolean = isCaseClass(annotateeClass)
val params = paramss.asInstanceOf[List[List[Tree]]].flatten.map(pp => getMethodParamName(c)(pp)) if (isCase) {
params.exists(p => p.decodedName.toString == "Any") c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $annotateeClass")
case _ => false
} }
val excludeFields = args._2
def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val (className, annotteeClassParams, annotteeClassDefinitions, superClasses) = classDecl match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = args._1)
(tpname, paramss.asInstanceOf[List[List[Tree]]], stats.asInstanceOf[Seq[Tree]], parents)
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
}
val ctorFieldNames = annotteeClassParams.flatten.filter(cf => classParamsIsPrivate(cf))
val allFieldsTermName = ctorFieldNames.map(f => getFieldTermName(f))
c.info(c.enclosingPosition, s"modifiedDeclaration compDeclOpt: $compDeclOpt, ctorFieldNames: $ctorFieldNames, " +
s"annotteeClassParams: $superClasses", force = args._1)
/**
* Extract the internal fields of members belonging to the class.
*/
def getClassMemberAllTermName: Seq[TermName] = {
getClassMemberValDefs(annotteeClassDefinitions).filter(_ match {
case q"$mods var $tname: $tpt = $expr" if !excludeFields.contains(tname.asInstanceOf[TermName].decodedName.toString) => true
case q"$mods val $tname: $tpt = $expr" if !excludeFields.contains(tname.asInstanceOf[TermName].decodedName.toString) => true
case q"$mods val $pat = $expr" if !excludeFields.contains(pat.asInstanceOf[TermName].decodedName.toString) => true
case q"$mods var $pat = $expr" if !excludeFields.contains(pat.asInstanceOf[TermName].decodedName.toString) => true
case _ => false
}).map(f => getFieldTermName(f))
}
// + super.hashCode val existsCanEqual = getClassMemberDefDefs(annotteeClassDefinitions) exists {
val SDKClasses = Set("java.lang.Object", "scala.AnyRef") case q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr" if tname.toString() == "canEqual" && paramss.nonEmpty =>
val canEqualsExistsInSuper = if (superClasses.nonEmpty && !superClasses.forall(sc => SDKClasses.contains(sc.toString()))) { // TODO better way val params = paramss.asInstanceOf[List[List[Tree]]].flatten.map(pp => getMethodParamName(pp))
true params.exists(p => p.decodedName.toString == "Any")
} else false case _ => false
// equals template
def ==(termNames: Seq[TermName]): c.universe.Tree = {
val getEqualsExpr = (termName: TermName) => {
q"this.$termName.equals(t.$termName)"
} }
val equalsExprs = termNames.map(getEqualsExpr)
val modifiers = if (canEqualsExistsInSuper) Modifiers(Flag.OVERRIDE, typeNames.EMPTY, List()) else Modifiers(NoFlags, typeNames.EMPTY, List()) // + super.hashCode
val canEqual = if (existsCanEqual) q"" else q"$modifiers def canEqual(that: Any) = that.isInstanceOf[$className]" val SDKClasses = Set("java.lang.Object", "scala.AnyRef")
q""" val canEqualsExistsInSuper = if (superClasses.nonEmpty && !superClasses.forall(sc => SDKClasses.contains(sc.toString()))) { // TODO better way
true
} else false
// equals template
def ==(termNames: Seq[TermName]): Tree = {
val getEqualsExpr = (termName: TermName) => {
q"this.$termName.equals(t.$termName)"
}
val equalsExprs = termNames.map(getEqualsExpr)
val modifiers = if (canEqualsExistsInSuper) Modifiers(Flag.OVERRIDE, typeNames.EMPTY, List()) else Modifiers(NoFlags, typeNames.EMPTY, List())
val canEqual = if (existsCanEqual) q"" else q"$modifiers def canEqual(that: Any) = that.isInstanceOf[$className]"
q"""
$canEqual $canEqual
override def equals(that: Any): Boolean = override def equals(that: Any): Boolean =
...@@ -104,54 +107,55 @@ object equalsAndHashCodeMacro extends MacroCommon { ...@@ -104,54 +107,55 @@ object equalsAndHashCodeMacro extends MacroCommon {
case _ => false case _ => false
} }
""" """
} }
// hashcode template // hashcode template
def ##(termNames: Seq[TermName]): c.universe.Tree = { def ##(termNames: Seq[TermName]): Tree = {
// the algorithm see https://alvinalexander.com/scala/how-to-define-equals-hashcode-methods-in-scala-object-equality/ // the algorithm see https://alvinalexander.com/scala/how-to-define-equals-hashcode-methods-in-scala-object-equality/
// We use default 1. // We use default 1.
if (!canEqualsExistsInSuper) { if (!canEqualsExistsInSuper) {
q""" q"""
override def hashCode(): Int = { override def hashCode(): Int = {
val state = Seq(..$termNames) val state = Seq(..$termNames)
state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b) state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b)
} }
""" """
} else { } else {
q""" q"""
override def hashCode(): Int = { override def hashCode(): Int = {
val state = Seq(..$termNames) val state = Seq(..$termNames)
state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b) + super.hashCode state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b) + super.hashCode
} }
""" """
}
} }
}
val allTernNames = allFieldsTermName ++ getClassMemberAllTermName val allTernNames = allFieldsTermName ++ getClassMemberAllTermName
val hashcode = ##(allTernNames) val hashcode = ##(allTernNames)
val equals = ==(allTernNames) val equals = ==(allTernNames)
val equalsAndHashcode = val equalsAndHashcode =
q""" q"""
..$equals ..$equals
$hashcode $hashcode
""" """
// return with object if it exists // return with object if it exists
val resTree = annotateeClass match { val resTree = annotateeClass match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" => case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
val originalStatus = q"{ ..$stats }" val originalStatus = q"{ ..$stats }"
val append = val append =
q""" q"""
..$originalStatus ..$originalStatus
..$equalsAndHashcode ..$equalsAndHashcode
""" """
q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${append} }" q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${append} }"
}
c.Expr[Any](treeResultWithCompanionObject(resTree, annottees: _*))
} }
c.Expr[Any](treeResultWithCompanionObject(c)(resTree, annottees: _*))
}
val resTree = handleWithImplType(c)(annottees: _*)(modifiedDeclaration) val resTree = handleWithImplType(annottees: _*)(modifiedDeclaration)
printTree(c)(force = args._1, resTree.tree) printTree(force = args._1, resTree.tree)
resTree resTree
}
} }
} }
...@@ -29,49 +29,52 @@ import scala.reflect.macros.whitebox ...@@ -29,49 +29,52 @@ import scala.reflect.macros.whitebox
* @since 2021/7/7 * @since 2021/7/7
* @version 1.0 * @version 1.0
*/ */
object jsonMacro extends MacroCommon { object jsonMacro {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
class JsonProcessor(override val c: whitebox.Context) extends AbstractMacroProcessor(c) {
import c.universe._ import c.universe._
def jsonFormatter(className: TypeName, fields: List[Tree]): c.universe.Tree = { override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
fields.length match { def jsonFormatter(className: TypeName, fields: List[Tree]): Tree = {
case 0 => c.abort(c.enclosingPosition, "Cannot create json formatter for case class with no fields") fields.length match {
case _ => case 0 => c.abort(c.enclosingPosition, "Cannot create json formatter for case class with no fields")
c.info(c.enclosingPosition, s"jsonFormatter className: $className, field length: ${fields.length}", force = true) case _ =>
q"implicit val jsonAnnotationFormat = play.api.libs.json.Json.format[$className]" c.info(c.enclosingPosition, s"jsonFormatter className: $className, field length: ${fields.length}", force = true)
q"implicit val jsonAnnotationFormat = play.api.libs.json.Json.format[$className]"
}
} }
}
// The dependent type need aux-pattern in scala2. Now let's get around this. // The dependent type need aux-pattern in scala2. Now let's get around this.
def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = { def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val (className, fields) = classDecl match { val (className, fields) = classDecl match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$bases { ..$body }" => case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$bases { ..$body }" =>
if (!mods.asInstanceOf[Modifiers].hasFlag(Flag.CASE)) { if (!mods.asInstanceOf[Modifiers].hasFlag(Flag.CASE)) {
c.abort(c.enclosingPosition, s"Annotation is only supported on case class. classDef: $classDecl, mods: $mods") c.abort(c.enclosingPosition, s"Annotation is only supported on case class. classDef: $classDecl, mods: $mods")
} else { } else {
c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = true) c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = true)
(tpname, paramss.asInstanceOf[List[List[Tree]]]) (tpname, paramss.asInstanceOf[List[List[Tree]]])
} }
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl") case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
} }
c.info(c.enclosingPosition, s"modifiedDeclaration className: $className, fields: $fields", force = true) c.info(c.enclosingPosition, s"modifiedDeclaration className: $className, fields: $fields", force = true)
val cName = className.asInstanceOf[TypeName] val cName = className.asInstanceOf[TypeName]
val format = jsonFormatter(cName, fields.flatten) val format = jsonFormatter(cName, fields.flatten)
val compDecl = modifiedCompanion(c)(compDeclOpt, format, cName) val compDecl = modifiedCompanion(compDeclOpt, format, cName)
c.info(c.enclosingPosition, s"format: $format, compDecl: $compDecl", force = true) c.info(c.enclosingPosition, s"format: $format, compDecl: $compDecl", force = true)
// Return both the class and companion object declarations // Return both the class and companion object declarations
c.Expr( c.Expr(
q""" q"""
$classDecl $classDecl
$compDecl $compDecl
""") """)
} }
c.info(c.enclosingPosition, s"json annottees: $annottees", force = true) val resTree = handleWithImplType(annottees: _*)(modifiedDeclaration)
val resTree = handleWithImplType(c)(annottees: _*)(modifiedDeclaration) printTree(force = true, resTree.tree)
printTree(c)(force = true, resTree.tree)
resTree resTree
}
} }
} }
...@@ -33,55 +33,60 @@ import scala.reflect.macros.whitebox ...@@ -33,55 +33,60 @@ import scala.reflect.macros.whitebox
* @since 2021/7/7 * @since 2021/7/7
* @version 1.0 * @version 1.0
*/ */
object logMacro extends MacroCommon { object logMacro {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
class LogProcessor(override val c: whitebox.Context) extends AbstractMacroProcessor(c) {
import c.universe._ import c.universe._
def getLogType(logType: c.Tree): LogType = {
if (logType.children.exists(t => t.toString().contains(PACKAGE))) { override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
evalTree(c)(logType.asInstanceOf[Tree]) // TODO remove asInstanceOf def getLogType(logType: Tree): LogType = {
} else { if (logType.children.exists(t => t.toString().contains(PACKAGE))) {
LogType.getLogType(logType.toString()) evalTree(logType.asInstanceOf[Tree]) // TODO remove asInstanceOf
} else {
LogType.getLogType(logType.toString())
}
}
val args: (Boolean, LogType) = extractArgumentsTuple2 {
case q"new log(logType=$logType)" =>
val tpe = getLogType(logType.asInstanceOf[Tree])
(false, tpe)
case q"new log(verbose=$verbose)" => (evalTree(verbose.asInstanceOf[Tree]), LogType.JLog)
case q"new log($logType)" =>
val tpe = getLogType(logType.asInstanceOf[Tree])
(false, tpe)
case q"new log(verbose=$verbose, logType=$logType)" =>
val tpe = getLogType(logType.asInstanceOf[Tree])
(evalTree(verbose.asInstanceOf[Tree]), tpe)
case q"new log()" => (false, LogType.JLog)
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
} }
}
val args: (Boolean, LogType) = extractArgumentsTuple2(c) {
case q"new log(logType=$logType)" =>
val tpe = getLogType(logType.asInstanceOf[Tree])
(false, tpe)
case q"new log(verbose=$verbose)" => (evalTree(c)(verbose.asInstanceOf[Tree]), LogType.JLog)
case q"new log($logType)" =>
val tpe = getLogType(logType.asInstanceOf[Tree])
(false, tpe)
case q"new log(verbose=$verbose, logType=$logType)" =>
val tpe = getLogType(logType.asInstanceOf[Tree])
(evalTree(c)(verbose.asInstanceOf[Tree]), tpe)
case q"new log()" => (false, LogType.JLog)
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
}
c.info(c.enclosingPosition, s"annottees: $annottees, args: $args", force = args._1) c.info(c.enclosingPosition, s"annottees: $annottees, args: $args", force = args._1)
val logTree = annottees.map(_.tree) match { val logTree = annottees.map(_.tree) match {
// Match a class, and expand, get class/object name. // Match a class, and expand, get class/object name.
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: _ => case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: _ =>
LogType.getLogImpl(args._2).getTemplate(c)(tpname.asInstanceOf[TypeName].toTermName.decodedName.toString, isClass = true) LogType.getLogImpl(args._2).getTemplate(c)(tpname.asInstanceOf[TypeName].toTermName.decodedName.toString, isClass = true)
case q"$mods object $tpname extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: _ => case q"$mods object $tpname extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: _ =>
LogType.getLogImpl(args._2).getTemplate(c)(tpname.asInstanceOf[TermName].decodedName.toString, isClass = false) LogType.getLogImpl(args._2).getTemplate(c)(tpname.asInstanceOf[TermName].decodedName.toString, isClass = false)
case _ => c.abort(c.enclosingPosition, s"Annotation is only supported on class or object.") case _ => c.abort(c.enclosingPosition, s"Annotation is only supported on class or object.")
} }
// add result into class // add result into class
val resTree = annottees.map(_.tree) match { val resTree = annottees.map(_.tree) match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: _ => case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: _ =>
val resTree = q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${List(logTree) ::: stats.toList} }" q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${List(logTree) ::: stats.toList} }"
treeResultWithCompanionObject(c)(resTree, annottees: _*) //we should return with companion object. Even if we didn't change it. case q"$mods object $tpname extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: _ =>
case q"$mods object $tpname extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: _ => q"$mods object $tpname extends { ..$earlydefns } with ..$parents { $self => ..${List(logTree) ::: stats.toList} }"
q"$mods object $tpname extends { ..$earlydefns } with ..$parents { $self => ..${List(logTree) ::: stats.toList} }" // Note: If a class is annotated and it has a companion, then both are passed into the macro.
// Note: If a class is annotated and it has a companion, then both are passed into the macro. // (But not vice versa - if an object is annotated and it has a companion class, only the object itself is expanded).
// (But not vice versa - if an object is annotated and it has a companion class, only the object itself is expanded). // see https://docs.scala-lang.org/overviews/macros/annotations.html
// see https://docs.scala-lang.org/overviews/macros/annotations.html }
}
printTree(c)(force = args._1, resTree) val res = treeResultWithCompanionObject(resTree, annottees: _*)
c.Expr[Any](resTree) printTree(force = args._1, res)
c.Expr[Any](resTree)
}
} }
} }
...@@ -29,34 +29,37 @@ import scala.reflect.macros.whitebox ...@@ -29,34 +29,37 @@ import scala.reflect.macros.whitebox
* @since 2021/7/7 * @since 2021/7/7
* @version 1.0 * @version 1.0
*/ */
object synchronizedMacro extends MacroCommon { object synchronizedMacro {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val args: (Boolean, String) = extractArgumentsTuple2(c) { class SynchronizedProcessor(override val c: whitebox.Context) extends AbstractMacroProcessor(c) {
case q"new synchronized(verbose=$verbose, lockedName=$lock)" => (evalTree(c)(verbose.asInstanceOf[Tree]), evalTree(c)(lock.asInstanceOf[Tree]))
case q"new synchronized(lockedName=$lock)" => (false, evalTree(c)(lock.asInstanceOf[Tree]))
case q"new synchronized()" => (false, "this")
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
}
c.info(c.enclosingPosition, s"annottees: $annottees", force = args._1) import c.universe._
val resTree = annottees map (_.tree) match { override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
// Match a method, and expand. val args: (Boolean, String) = extractArgumentsTuple2 {
case _@ q"$modrs def $tname[..$tparams](...$paramss): $tpt = $expr" :: _ => case q"new synchronized(verbose=$verbose, lockedName=$lock)" => (evalTree(verbose.asInstanceOf[Tree]), evalTree(lock.asInstanceOf[Tree]))
if (args._2 != null) { case q"new synchronized(lockedName=$lock)" => (false, evalTree(lock.asInstanceOf[Tree]))
if (args._2 == "this") { case q"new synchronized()" => (false, "this")
q"""def $tname[..$tparams](...$paramss): $tpt = ${This(TypeName(""))}.synchronized { $expr }""" case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
}
val resTree = annottees map (_.tree) match {
// Match a method, and expand.
case _@ q"$modrs def $tname[..$tparams](...$paramss): $tpt = $expr" :: _ =>
if (args._2 != null) {
if (args._2 == "this") {
q"""def $tname[..$tparams](...$paramss): $tpt = ${This(TypeName(""))}.synchronized { $expr }"""
} else {
q"""def $tname[..$tparams](...$paramss): $tpt = ${TermName(args._2)}.synchronized { $expr }"""
}
} else { } else {
q"""def $tname[..$tparams](...$paramss): $tpt = ${TermName(args._2)}.synchronized { $expr }""" c.abort(c.enclosingPosition, "Invalid args, lockName cannot be a null!")
} }
} else { case _ => c.abort(c.enclosingPosition, "Invalid annotation target: not a method")
c.abort(c.enclosingPosition, "Invalid args, lockName cannot be a null!") }
} printTree(args._1, resTree)
case _ => c.abort(c.enclosingPosition, "Invalid annotation target: not a method") c.Expr[Any](resTree)
} }
printTree(c)(args._1, resTree)
c.Expr[Any](resTree)
} }
} }
...@@ -29,132 +29,134 @@ import scala.reflect.macros.whitebox ...@@ -29,132 +29,134 @@ import scala.reflect.macros.whitebox
* @since 2021/7/7 * @since 2021/7/7
* @version 1.0 * @version 1.0
*/ */
object toStringMacro extends MacroCommon { object toStringMacro {
private final case class Argument(verbose: Boolean, includeInternalFields: Boolean, includeFieldNames: Boolean, callSuper: Boolean) private final case class Argument(verbose: Boolean, includeInternalFields: Boolean, includeFieldNames: Boolean, callSuper: Boolean)
def printField(c: whitebox.Context)(argument: Argument, lastParam: Option[String], field: c.universe.Tree): c.universe.Tree = { class ToStringProcessor(override val c: whitebox.Context) extends AbstractMacroProcessor(c) {
import c.universe._ import c.universe._
// Print one field as <name of the field>+"="+fieldName
if (argument.includeFieldNames) { override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
lastParam.fold(q"$field") { lp => // extract parameters of annotation, must in order
field match { val arg: (Boolean, Boolean, Boolean, Boolean) = extractArgumentsTuple4 {
case q"$mods var $tname: $tpt = $expr" => case q"new toString(includeInternalFields=$bb, includeFieldNames=$cc, callSuper=$dd)" =>
if (tname.toString() != lp) q"""${tname.toString()}+${"="}+this.$tname+${", "}""" else q"""${tname.toString()}+${"="}+this.$tname""" (false, evalTree(bb.asInstanceOf[Tree]), evalTree(cc.asInstanceOf[Tree]), evalTree(dd.asInstanceOf[Tree]))
case q"$mods val $tname: $tpt = $expr" => case q"new toString($aa, $bb, $cc)" =>
if (tname.toString() != lp) q"""${tname.toString()}+${"="}+this.$tname+${", "}""" else q"""${tname.toString()}+${"="}+this.$tname""" (evalTree(aa.asInstanceOf[Tree]), evalTree(bb.asInstanceOf[Tree]), evalTree(cc.asInstanceOf[Tree]), false)
case _ => q"$field"
} case q"new toString(verbose=$aa, includeInternalFields=$bb, includeFieldNames=$cc, callSuper=$dd)" =>
(evalTree(aa.asInstanceOf[Tree]), evalTree(bb.asInstanceOf[Tree]), evalTree(cc.asInstanceOf[Tree]), evalTree(dd.asInstanceOf[Tree]))
case q"new toString(verbose=$aa, includeInternalFields=$bb, includeFieldNames=$cc)" =>
(evalTree(aa.asInstanceOf[Tree]), evalTree(bb.asInstanceOf[Tree]), evalTree(cc.asInstanceOf[Tree]), false)
case q"new toString($aa, $bb, $cc, $dd)" =>
(evalTree(aa.asInstanceOf[Tree]), evalTree(bb.asInstanceOf[Tree]), evalTree(cc.asInstanceOf[Tree]), evalTree(dd.asInstanceOf[Tree]))
case q"new toString(includeInternalFields=$bb, includeFieldNames=$cc)" =>
(false, evalTree(bb.asInstanceOf[Tree]), evalTree(cc.asInstanceOf[Tree]), false)
case q"new toString(includeInternalFields=$bb)" =>
(false, evalTree(bb.asInstanceOf[Tree]), true, false)
case q"new toString(includeFieldNames=$cc)" =>
(false, true, evalTree(cc.asInstanceOf[Tree]), false)
case q"new toString()" => (false, true, true, false)
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
} }
} else { val argument = Argument(arg._1, arg._2, arg._3, arg._4)
lastParam.fold(q"$field") { lp => c.info(c.enclosingPosition, s"toString annottees: $annottees", force = argument.verbose)
field match { // Check the type of the class, which can only be defined on the ordinary class
case q"$mods var $tname: $tpt = $expr" => if (tname.toString() != lp) q"""$tname+${", "}""" else q"""$tname""" val annotateeClass: ClassDef = checkAndGetClassDef(annottees: _*)
case q"$mods val $tname: $tpt = $expr" => if (tname.toString() != lp) q"""$tname+${", "}""" else q"""$tname""" val isCase: Boolean = isCaseClass(annotateeClass)
case _ => if (field.toString() != lp) q"""$field+${", "}""" else q"""$field"""
} c.info(c.enclosingPosition, s"impl argument: $argument, isCase: $isCase", force = argument.verbose)
val resMethod = toStringTemplateImpl(argument, annotateeClass)
val resTree = annotateeClass match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${stats.toList.:+(resMethod)} }"
} }
val res = treeResultWithCompanionObject(resTree, annottees: _*)
printTree(argument.verbose, res)
c.Expr[Any](res)
} }
}
private def toStringTemplateImpl(c: whitebox.Context)(argument: Argument, annotateeClass: c.universe.ClassDef): c.universe.Tree = { def printField(argument: Argument, lastParam: Option[String], field: Tree): Tree = {
import c.universe._ // Print one field as <name of the field>+"="+fieldName
// For a given class definition, separate the components of the class if (argument.includeFieldNames) {
val (className, annotteeClassParams, superClasses, annotteeClassDefinitions) = { lastParam.fold(q"$field") { lp =>
annotateeClass match { field match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" => case q"$mods var $tname: $tpt = $expr" =>
c.info(c.enclosingPosition, s"parents: $parents", force = argument.verbose) if (tname.toString() != lp) q"""${tname.toString()}+${"="}+this.$tname+${", "}""" else q"""${tname.toString()}+${"="}+this.$tname"""
(tpname, paramss.asInstanceOf[List[List[Tree]]], parents, stats.asInstanceOf[List[Tree]]) case q"$mods val $tname: $tpt = $expr" =>
if (tname.toString() != lp) q"""${tname.toString()}+${"="}+this.$tname+${", "}""" else q"""${tname.toString()}+${"="}+this.$tname"""
case _ => q"$field"
}
}
} else {
lastParam.fold(q"$field") { lp =>
field match {
case q"$mods var $tname: $tpt = $expr" => if (tname.toString() != lp) q"""$tname+${", "}""" else q"""$tname"""
case q"$mods val $tname: $tpt = $expr" => if (tname.toString() != lp) q"""$tname+${", "}""" else q"""$tname"""
case _ => if (field.toString() != lp) q"""$field+${", "}""" else q"""$field"""
}
}
} }
} }
// Check the type of the class, whether it already contains its own toString
val annotteeClassFieldDefinitions = annotteeClassDefinitions.filter(p => p match { private def toStringTemplateImpl(argument: Argument, annotateeClass: ClassDef): Tree = {
case _: ValDef => true // For a given class definition, separate the components of the class
case mem: MemberDef => val (className, annotteeClassParams, superClasses, annotteeClassDefinitions) = {
c.info(c.enclosingPosition, s"MemberDef: ${mem.toString}", force = argument.verbose) annotateeClass match {
if (mem.toString().startsWith("override def toString")) { // TODO better way case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
c.abort(mem.pos, "'toString' method has already defined, please remove it or not use'@toString'") c.info(c.enclosingPosition, s"parents: $parents", force = argument.verbose)
(tpname, paramss.asInstanceOf[List[List[Tree]]], parents, stats.asInstanceOf[List[Tree]])
} }
false }
case _ => false // Check the type of the class, whether it already contains its own toString
}) val annotteeClassFieldDefinitions = annotteeClassDefinitions.filter(p => p match {
case _: ValDef => true
// For the parameters of a given constructor, separate the parameter components and extract the constructor parameters containing val and var case mem: MemberDef =>
val ctorParams = annotteeClassParams.flatten.map { c.info(c.enclosingPosition, s"MemberDef: ${mem.toString}", force = argument.verbose)
case tree @ q"$mods val $tname: $tpt = $expr" => tree if (mem.toString().startsWith("override def toString")) { // TODO better way
case tree @ q"$mods var $tname: $tpt = $expr" => tree c.abort(mem.pos, "'toString' method has already defined, please remove it or not use'@toString'")
} }
c.info(c.enclosingPosition, s"className: $className, ctorParams: ${ctorParams.toString()}, superClasses: $superClasses", force = argument.verbose) false
c.info(c.enclosingPosition, s"className: $className, fields: ${annotteeClassFieldDefinitions.toString()}", force = argument.verbose) case _ => false
val member = if (argument.includeInternalFields) ctorParams ++ annotteeClassFieldDefinitions else ctorParams })
// For the parameters of a given constructor, separate the parameter components and extract the constructor parameters containing val and var
val ctorParams = annotteeClassParams.flatten.map {
case tree @ q"$mods val $tname: $tpt = $expr" => tree
case tree @ q"$mods var $tname: $tpt = $expr" => tree
}
c.info(c.enclosingPosition, s"className: $className, ctorParams: ${ctorParams.toString()}, superClasses: $superClasses", force = argument.verbose)
c.info(c.enclosingPosition, s"className: $className, fields: ${annotteeClassFieldDefinitions.toString()}", force = argument.verbose)
val member = if (argument.includeInternalFields) ctorParams ++ annotteeClassFieldDefinitions else ctorParams
val lastParam = member.lastOption.map { val lastParam = member.lastOption.map {
case v: ValDef => v.name.toTermName.decodedName.toString case v: ValDef => v.name.toTermName.decodedName.toString
case c => c.toString case c => c.toString
}
val paramsWithName = member.foldLeft(q"${""}")((res, acc) => q"$res + ${printField(c)(argument, lastParam, acc)}")
//scala/bug https://github.com/scala/bug/issues/3967 not be 'Foo(i=1,j=2)' in standard library
val toString = q"""override def toString: String = ${className.toString()} + ${"("} + $paramsWithName + ${")"}"""
// Have super class ?
if (argument.callSuper && superClasses.nonEmpty) {
val superClassDef = superClasses.head match {
case tree: Tree => Some(tree) // TODO type check better
case _ => None
} }
superClassDef.fold(toString)(_ => { val paramsWithName = member.foldLeft(q"${""}")((res, acc) => q"$res + ${printField(argument, lastParam, acc)}")
val superClass = q"${"super="}" //scala/bug https://github.com/scala/bug/issues/3967 not be 'Foo(i=1,j=2)' in standard library
c.info(c.enclosingPosition, s"member: $member, superClass: $superClass, superClassDef: $superClassDef, paramsWithName: $paramsWithName", force = argument.verbose) val toString = q"""override def toString: String = ${className.toString()} + ${"("} + $paramsWithName + ${")"}"""
q"override def toString: String = StringContext(${className.toString()} + ${"("} + $superClass, ${if (member.nonEmpty) ", " else ""}+$paramsWithName + ${")"}).s(super.toString)"
// Have super class ?
if (argument.callSuper && superClasses.nonEmpty) {
val superClassDef = superClasses.head match {
case tree: Tree => Some(tree) // TODO type check better
case _ => None
}
superClassDef.fold(toString)(_ => {
val superClass = q"${"super="}"
c.info(c.enclosingPosition, s"member: $member, superClass: $superClass, superClassDef: $superClassDef, paramsWithName: $paramsWithName", force = argument.verbose)
q"override def toString: String = StringContext(${className.toString()} + ${"("} + $superClass, ${if (member.nonEmpty) ", " else ""}+$paramsWithName + ${")"}).s(super.toString)"
}
)
} else {
toString
} }
)
} else {
toString
} }
} }
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
// extract parameters of annotation, must in order
val arg: (Boolean, Boolean, Boolean, Boolean) = extractArgumentsTuple4(c) {
case q"new toString(includeInternalFields=$bb, includeFieldNames=$cc, callSuper=$dd)" =>
(false, evalTree(c)(bb.asInstanceOf[Tree]), evalTree(c)(cc.asInstanceOf[Tree]), evalTree(c)(dd.asInstanceOf[Tree]))
case q"new toString($aa, $bb, $cc)" =>
(evalTree(c)(aa.asInstanceOf[Tree]), evalTree(c)(bb.asInstanceOf[Tree]), evalTree(c)(cc.asInstanceOf[Tree]), false)
case q"new toString(verbose=$aa, includeInternalFields=$bb, includeFieldNames=$cc, callSuper=$dd)" =>
(evalTree(c)(aa.asInstanceOf[Tree]), evalTree(c)(bb.asInstanceOf[Tree]), evalTree(c)(cc.asInstanceOf[Tree]), evalTree(c)(dd.asInstanceOf[Tree]))
case q"new toString(verbose=$aa, includeInternalFields=$bb, includeFieldNames=$cc)" =>
(evalTree(c)(aa.asInstanceOf[Tree]), evalTree(c)(bb.asInstanceOf[Tree]), evalTree(c)(cc.asInstanceOf[Tree]), false)
case q"new toString($aa, $bb, $cc, $dd)" =>
(evalTree(c)(aa.asInstanceOf[Tree]), evalTree(c)(bb.asInstanceOf[Tree]), evalTree(c)(cc.asInstanceOf[Tree]), evalTree(c)(dd.asInstanceOf[Tree]))
case q"new toString(includeInternalFields=$bb, includeFieldNames=$cc)" =>
(false, evalTree(c)(bb.asInstanceOf[Tree]), evalTree(c)(cc.asInstanceOf[Tree]), false)
case q"new toString(includeInternalFields=$bb)" =>
(false, evalTree(c)(bb.asInstanceOf[Tree]), true, false)
case q"new toString(includeFieldNames=$cc)" =>
(false, true, evalTree(c)(cc.asInstanceOf[Tree]), false)
case q"new toString()" => (false, true, true, false)
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
}
val argument = Argument(arg._1, arg._2, arg._3, arg._4)
c.info(c.enclosingPosition, s"toString annottees: $annottees", force = argument.verbose)
// Check the type of the class, which can only be defined on the ordinary class
val annotateeClass: ClassDef = checkAndGetClassDef(c)(annottees: _*)
val isCase: Boolean = isCaseClass(c)(annotateeClass)
c.info(c.enclosingPosition, s"impl argument: $argument, isCase: $isCase", force = argument.verbose)
val resMethod = toStringTemplateImpl(c)(argument, annotateeClass)
val resTree = annotateeClass match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${stats.toList.:+(resMethod)} }"
}
val res = treeResultWithCompanionObject(c)(resTree, annottees: _*)
printTree(c)(argument.verbose, res)
c.Expr[Any](res)
}
} }
...@@ -39,5 +39,5 @@ final class synchronized( ...@@ -39,5 +39,5 @@ final class synchronized(
verbose: Boolean = false, verbose: Boolean = false,
lockedName: String = "this" lockedName: String = "this"
) extends StaticAnnotation { ) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro synchronizedMacro.impl def macroTransform(annottees: Any*): Any = macro synchronizedMacro.SynchronizedProcessor.impl
} }
...@@ -43,5 +43,5 @@ final class toString( ...@@ -43,5 +43,5 @@ final class toString(
includeFieldNames: Boolean = true, includeFieldNames: Boolean = true,
callSuper: Boolean = false callSuper: Boolean = false
) extends StaticAnnotation { ) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro toStringMacro.impl def macroTransform(annottees: Any*): Any = macro toStringMacro.ToStringProcessor.impl
} }
...@@ -54,20 +54,24 @@ class ApplyTest extends AnyFlatSpec with Matchers { ...@@ -54,20 +54,24 @@ class ApplyTest extends AnyFlatSpec with Matchers {
object B3 object B3
println(B3(1, 2, None, None)) println(B3(1, 2, None, None))
} }
"apply2" should "failed at class" in { "apply2" should "failed on case class" in {
// FAILED, not support currying!! """@apply @toString case class C3(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))(o: Int = 1)""" shouldNot compile
"""@apply @toString class C(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))(o: Int = 1)""" shouldNot compile
} }
"apply3" should "ok with currying" in { "apply3" should "ok with currying" in {
"""@apply @toString class C2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))(o: Int = 1)""" should compile
@apply
@toString class C1(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))(o: Int = 1)
@apply @apply
@toString class B3(int: Int)(val j: Int)(var k: Option[String] = None)(t: Option[Long] = Some(1L)) @toString class B3(int: Int)(val j: Int)(var k: Option[String] = None)(t: Option[Long] = Some(1L))
@apply
@toString class B4(int: Int, a: Seq[Seq[String]])(val j: Int, b: Seq[String])(var k: Option[String] = None, c: Seq[Option[String]])(t: Option[Long] = Some(1L))
} }
"apply4" should "ok with generic" in { "apply4" should "ok with generic" in {
@apply @apply
@toString class B3[T, U](int: T)(val j: U) @toString class B3[T, U](int: T, yy: Int)(val j: U)
println(B3(1)(2)) println(B3(1, 2)(2))
@toString @toString
@apply class B4[T, U](int: T, val j: U) @apply class B4[T, U](int: T, val j: U)
......
...@@ -38,6 +38,7 @@ class BuilderTest extends AnyFlatSpec with Matchers { ...@@ -38,6 +38,7 @@ class BuilderTest extends AnyFlatSpec with Matchers {
// field : <caseaccessor> <paramaccessor> val i: Int = 0, so default value is "_" // field : <caseaccessor> <paramaccessor> val i: Int = 0, so default value is "_"
val ret = TestClass1.builder().i(1).j(0).x("x").build() val ret = TestClass1.builder().i(1).j(0).x("x").build()
println(ret) println(ret)
assert(TestClass1.builder().getClass.getTypeName == "io.github.dreamylost.BuilderTest$TestClass1$2$TestClass1Builder")
assert(ret.toString == "TestClass1(1,0,x,Some())") assert(ret.toString == "TestClass1(1,0,x,Some())")
} }
......
...@@ -98,11 +98,41 @@ class EqualsAndHashCodeTest extends AnyFlatSpec with Matchers { ...@@ -98,11 +98,41 @@ class EqualsAndHashCodeTest extends AnyFlatSpec with Matchers {
} }
"equals3" should "ok even if exists a canEqual" in { "equals3" should "ok even if exists a canEqual" in {
@equalsAndHashCode
class Employee1(name: String, age: Int, var role: String) extends Person(name, age) {
override def canEqual(that: Any) = that.getClass == classOf[Employee1];
}
""" """
| @equalsAndHashCode | @equalsAndHashCode
| class Employee(name: String, age: Int, var role: String) extends Person(name, age) { | class Employee2(name: String, age: Int, var role: String) extends Person(name, age) {
| override def canEqual(that: Any) = that.getClass == classOf[Employee]; | override def canEqual(that: Any) = that.getClass == classOf[Employee];
| } | }
|""".stripMargin should compile |""".stripMargin should compile
} }
"equals4" should "ok when there are members" in {
@equalsAndHashCode
class Employee1(name: String, age: Int, var role: String) extends Person(name, age) {
val i = 0
}
"""
| @equalsAndHashCode
| class Employee2(name: String, age: Int, var role: String) extends Person(name, age) {
| val i = 0
| }
|""".stripMargin should compile
@equalsAndHashCode
class Employee3(name: String, age: Int, var role: String) extends Person(name, age) {
val i = 0
def hello: String = ???
}
"""
| @equalsAndHashCode
| class Employee4(name: String, age: Int, var role: String) extends Person(name, age) {
| val i = 0
| def hello: String = ???
| }
|""".stripMargin should compile
}
} }
...@@ -141,22 +141,29 @@ class LogTest extends AnyFlatSpec with Matchers { ...@@ -141,22 +141,29 @@ class LogTest extends AnyFlatSpec with Matchers {
|""".stripMargin should compile |""".stripMargin should compile
} }
"log10 slf4j" should "failed on case class and it object" in { "log10 slf4j" should "ok on case class and it object" in {
@log(logType = LogType.JLog)
@builder case class TestClass6_1(val i: Int = 0, var j: Int) {
log.info("hello world")
}
@log(logType = io.github.dreamylost.logs.LogType.Slf4j) object TestClass6_1 {
log.info("hello world"); builder()
}
""" """
| @log(io.github.dreamylost.logs.LogType.Slf4j) | @log(verbose=false, logType = LogType.JLog)
| @builder case class TestClass6(val i: Int = 0, var j: Int) { | @builder case class TestClass6_2(val i: Int = 0, var j: Int) {
| log.info("hello world") | log.info("hello world")
| } | }
| @log(logType = io.github.dreamylost.logs.LogType.Slf4j) object TestClass6 { | @log(logType = io.github.dreamylost.logs.LogType.Slf4j) object TestClass6_2 {
| log.info("hello world"); builder() | log.info("hello world"); builder()
| } | }
|""".stripMargin shouldNot compile //The context of class was not passed in object macro |""".stripMargin should compile
} }
"log11 slf4j" should "ok on class and it object" in { "log11 slf4j" should "ok on class and it object" in {
""" """
| @log(io.github.dreamylost.logs.LogType.Slf4j) | @log(io.github.dreamylost.logs.LogType.Slf4j)
| @builder class TestClass6(val i: Int = 0, var j: Int) { | @builder class TestClass6(val i: Int = 0, var j: Int) {
| log.info("hello world") | log.info("hello world")
| } | }
| @log(logType = io.github.dreamylost.logs.LogType.Slf4j) object TestClass6 { | @log(logType = io.github.dreamylost.logs.LogType.Slf4j) object TestClass6 {
......
/*
* 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.
*/
package io.github.dreamylost
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
/**
*
* @author 梦境迷离
* @since 2021/7/24
* @version 1.0
*/
class OthersTest extends AnyFlatSpec with Matchers {
"others" should "ok" in {
assert(PACKAGE == "io.github.dreamylost")
"""
| @builder
| object A
|""".stripMargin shouldNot compile
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册