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

Add `@jacksonEnum` and Big refactor (#93)

1. add Accessor
2. support jacksonEnum
3. big refactor
上级 4028b489
......@@ -31,6 +31,7 @@ Learn Scala macro and abstract syntax tree.
- `@apply`
- `@constructor`
- `@equalsAndHashCode`
- `@jacksonEnum`
> Annotations involving interaction are supported in the idea plug-in (named `Scala-Macro-Tools` in Marketplace).
......
......@@ -46,6 +46,7 @@
- `@apply`
- `@constructor`
- `@equalsAndHashCode`
- `@jacksonEnum`
> 涉及到交互操作的注解在IDEA插件中都得到了支持。在插件市场中搜索`Scala-Macro-Tools`可下载。
......
......@@ -24,7 +24,8 @@ lazy val root = (project in file("."))
"org.apache.logging.log4j" % "log4j-core" % "2.14.1" % Test,
"org.apache.logging.log4j" % "log4j-slf4j-impl" % "2.14.1" % Test,
"com.typesafe.play" %% "play-json" % "2.7.4" % Test,
"org.scalatest" %% "scalatest" % "3.2.9" % Test
"org.scalatest" %% "scalatest" % "3.2.9" % Test,
"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.12.4" %Test
), Compile / scalacOptions ++= {
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, n)) if n <= 12 => Nil
......
......@@ -145,7 +145,7 @@ def getStr(k: Int): String = this.synchronized(k.$plus(""))
## @log
`@log`注解不使用混入和包装,而是直接使用宏生成默认的log对象来操作log。
`@log`注解不使用混入和包装,而是直接使用宏生成默认的log对象来操作log。日志库的依赖需要自己引入。
- 说明
- `verbose` 指定是否开启详细编译日志。可选,默认`false`
......@@ -155,7 +155,7 @@ def getStr(k: Int): String = this.synchronized(k.$plus(""))
- `io.github.dreamylost.logs.LogType.Slf4j` 使用 `org.slf4j.Logger`
- `io.github.dreamylost.logs.LogType.ScalaLoggingLazy` 基于 `scalalogging.LazyLogging` 实现,但字段被重命名为`log`
- `io.github.dreamylost.logs.LogType.ScalaLoggingStrict` 基于 `scalalogging.StrictLogging`实现, 但字段被重命名为`log`
- 支持普通类,样例类,单例对象。
- 支持普通类,单例对象。
- 示例
......@@ -260,3 +260,44 @@ class Person extends scala.AnyRef {
}
}
```
## @jacksonEnum
`@jacksonEnum`注解用于为类的主构造函数中的所有Scala枚举类型的参数提供`Jackson`序列化的支持。(jackson和jackson-scala-module依赖需要自己引入)
- 说明
- `verbose` 指定是否开启详细编译日志。可选,默认`false`
- `nonTypeRefers` 指定不需要创建`Jackson``TypeReference`子类的枚举类型。可选,默认`Nil`
- 支持`case class``class`
- 如果枚举类型存在`TypeReference`的子类,则不会生成新的子类,也不会重复添加`@JsonScalaEnumeration`注解到参数上。这主要用于解决冲突问题。
- 示例
```scala
@jacksonEnum(nonTypeRefers = Seq("EnumType"))
class B(
var enum1: EnumType.EnumType,
enum2: EnumType2.EnumType2 = EnumType2.A,
i: Int)
```
宏生成的中间代码:
```scala
class EnumType2TypeRefer extends _root_.com.fasterxml.jackson.core.`type`.TypeReference[EnumType2.type] {
def <init>() = {
super.<init>();
()
}
};
class B extends scala.AnyRef {
<paramaccessor> var enum1: JacksonEnumTest.this.EnumType.EnumType = _;
@new com.fasterxml.jackson.module.scala.JsonScalaEnumeration(classOf[EnumType2TypeRefer]) <paramaccessor> private[this] val enum2: JacksonEnumTest.this.EnumType2.EnumType2 = _;
<paramaccessor> private[this] val i: Int = _;
def <init>(enum1: JacksonEnumTest.this.EnumType.EnumType, @new com.fasterxml.jackson.module.scala.JsonScalaEnumeration(classOf[EnumType2TypeRefer]) enum2: JacksonEnumTest.this.EnumType2.EnumType2 = EnumType2.A, i: Int) = {
super.<init>();
()
}
};
()
```
......@@ -147,7 +147,7 @@ def getStr(k: Int): String = this.synchronized(k.$plus(""))
## @log
The `@log` does not use mixed or wrapper, but directly uses macro to generate default log object and operate log.
The `@log` does not use mixed or wrapper, but directly uses macro to generate default log object and operate log. (Log dependency needs to be introduced)
- Note
- `verbose` Whether to enable detailed log.
......@@ -157,7 +157,7 @@ The `@log` does not use mixed or wrapper, but directly uses macro to generate de
- `io.github.dreamylost.logs.LogType.Slf4j` use `org.slf4j.Logger`
- `io.github.dreamylost.logs.LogType.ScalaLoggingLazy` implement by `scalalogging.LazyLogging` but field was renamed to `log`
- `io.github.dreamylost.logs.LogType.ScalaLoggingStrict` implement by `scalalogging.StrictLogging` but field was renamed to `log`
- Support `class`, `case class` and `object`.
- Support `class` and `object`.
- Example
......@@ -266,3 +266,45 @@ class Person extends scala.AnyRef {
}
}
```
## @jacksonEnum
The `jacksonEnum` annotation is used to provide `Jackson` serialization support for all Scala enumeration type parameters in the primary constructor of the class. (jackson and jackson-scala-module dependency needs to be introduced)
- Note
- `verbose` Whether to enable detailed log. default is `false`.
- `nonTypeRefers` Specifies the enumeration type of the `TypeReference` subclass of `Jackson` that does not need to be created. default is `Nil`.
- Support `case class` and `class`.
- If the enumeration type has subclasses of `TypeReference`, no new subclasses will be generated,
and `JsonScalaEnumeration` annotation will not be added to the parameters repeatedly. This is mainly used to solve conflict problems.
- Example
```scala
@jacksonEnum(nonTypeRefers = Seq("EnumType"))
class B(
var enum1: EnumType.EnumType,
enum2: EnumType2.EnumType2 = EnumType2.A,
i: Int)
```
Macro expansion code:
```scala
class EnumType2TypeRefer extends _root_.com.fasterxml.jackson.core.`type`.TypeReference[EnumType2.type] {
def <init>() = {
super.<init>();
()
}
};
class B extends scala.AnyRef {
<paramaccessor> var enum1: JacksonEnumTest.this.EnumType.EnumType = _;
@new com.fasterxml.jackson.module.scala.JsonScalaEnumeration(classOf[EnumType2TypeRefer]) <paramaccessor> private[this] val enum2: JacksonEnumTest.this.EnumType2.EnumType2 = _;
<paramaccessor> private[this] val i: Int = _;
def <init>(enum1: JacksonEnumTest.this.EnumType.EnumType, @new com.fasterxml.jackson.module.scala.JsonScalaEnumeration(classOf[EnumType2TypeRefer]) enum2: JacksonEnumTest.this.EnumType2.EnumType2 = EnumType2.A, i: Int) = {
super.<init>();
()
}
};
()
```
/*
* 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 io.github.dreamylost.macros.jacksonEnumMacro
import scala.annotation.{ compileTimeOnly, StaticAnnotation }
/**
* annotation to generate equals and hashcode method for classes.
*
* @author 梦境迷离
* @author choly
*
* @param verbose Whether to enable detailed log.
* @param nonTypeRefers Whether to not generate the subclass of the TypeReference for paramTypes of class.
* @since 2021/8/3
* @version 1.0
*/
@compileTimeOnly("enable macro to expand macro annotations")
final class jacksonEnum(
verbose: Boolean = false,
nonTypeRefers: Seq[String] = Nil
) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro jacksonEnumMacro.JacksonEnumProcessor.impl
}
......@@ -21,6 +21,8 @@
package io.github.dreamylost.macros
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import scala.reflect.macros.whitebox
/**
......@@ -45,7 +47,7 @@ abstract class AbstractMacroProcessor(val c: whitebox.Context) {
* @return c.Expr[Any], 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 = ???
def createCustomExpr(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = ???
/**
* Subclasses must override the method.
......@@ -85,7 +87,7 @@ abstract class AbstractMacroProcessor(val c: whitebox.Context) {
def printTree(force: Boolean, resTree: Tree): Unit = {
c.info(
c.enclosingPosition,
"\n###### Expanded macro ######\n" + resTree.toString() + "\n###### Expanded macro ######\n",
s"\n###### Time: ${ZonedDateTime.now().format(DateTimeFormatter.ISO_ZONED_DATE_TIME)} Expanded macro start ######\n" + resTree.toString() + "\n###### Expanded macro end ######\n",
force = force
)
}
......@@ -96,43 +98,27 @@ abstract class AbstractMacroProcessor(val c: whitebox.Context) {
* @param annottees
* @return Return ClassDef
*/
def checkAndGetClassDef(annottees: Expr[Any]*): ClassDef = {
def checkGetClassDef(annottees: Seq[Expr[Any]]): ClassDef = {
annottees.map(_.tree).toList match {
case (classDecl: ClassDef) :: Nil => classDecl
case (classDecl: ClassDef) :: (compDecl: ModuleDef) :: Nil => classDecl
case (classDecl: ClassDef) :: (_: ModuleDef) :: Nil => classDecl
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
}
}
/**
* Get companion object if it exists.
* Get object if it exists.
*
* @param annottees
* @return
*/
def tryGetCompanionObject(annottees: Expr[Any]*): Option[ModuleDef] = {
def getModuleDefOption(annottees: Seq[Expr[Any]]): Option[ModuleDef] = {
annottees.map(_.tree).toList match {
case (classDecl: ClassDef) :: Nil => None
case (classDecl: ClassDef) :: (compDecl: ModuleDef) :: Nil => Some(compDecl)
case (compDecl: ModuleDef) :: Nil => Some(compDecl)
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
}
}
/**
* Wrap tree result with companion object.
*
* @param resTree class
* @param annottees
* @return
*/
def treeResultWithCompanionObject(resTree: Tree, annottees: Expr[Any]*): Tree = {
val companionOpt = tryGetCompanionObject(annottees: _*)
companionOpt.fold(resTree) { t =>
q"""
$resTree
$t
"""
case (moduleDef: ModuleDef) :: Nil => Some(moduleDef)
case (_: ClassDef) :: Nil => None
case (_: ClassDef) :: (compDecl: ModuleDef) :: Nil => Some(compDecl)
case (moduleDef: ModuleDef) :: (_: ClassDef) :: Nil => Some(moduleDef)
case _ => None
}
}
......@@ -143,13 +129,11 @@ abstract class AbstractMacroProcessor(val c: whitebox.Context) {
* @param modifyAction The actual processing function
* @return Return the result of modifyAction
*/
def handleWithImplType(annottees: Expr[Any]*)
def collectCustomExpr(annottees: Seq[Expr[Any]])
(modifyAction: (ClassDef, Option[ModuleDef]) => Any): Expr[Nothing] = {
annottees.map(_.tree) match {
case (classDecl: ClassDef) :: Nil => modifyAction(classDecl, None).asInstanceOf[Expr[Nothing]]
case (classDecl: ClassDef) :: (compDecl: ModuleDef) :: Nil => modifyAction(classDecl, Some(compDecl)).asInstanceOf[Expr[Nothing]]
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
}
val classDef = checkGetClassDef(annottees)
val compDecl = getModuleDefOption(annottees)
modifyAction(classDef, compDecl).asInstanceOf[Expr[Nothing]]
}
/**
......@@ -162,17 +146,6 @@ abstract class AbstractMacroProcessor(val c: whitebox.Context) {
annotateeClass.mods.hasFlag(Flag.CASE)
}
/**
* Expand the method params and get the param Name.
*
* @param field
* @return
*/
def getMethodParamName(field: Tree): Name = {
val q"$mods val $tname: $tpt = $expr" = field
tpt.asInstanceOf[Ident].name.decodedName
}
/**
* Check whether the mods of the fields has a `private[this]` or `protected[this]`, because it cannot be used out of class.
*
......@@ -199,38 +172,27 @@ abstract class AbstractMacroProcessor(val c: whitebox.Context) {
* @return {{ i: Int}}
*/
def getConstructorParamsNameWithType(annotteeClassParams: Seq[Tree]): Seq[Tree] = {
annotteeClassParams.map {
case v: ValDef => q"${v.name}: ${v.tpt}"
}
annotteeClassParams.map(_.asInstanceOf[ValDef]).map(v => q"${v.name}: ${v.tpt}")
}
/**
* Modify companion objects.
* Modify companion object or object.
*
* @param compDeclOpt
* @param codeBlock
* @param codeBlocks
* @param className
* @return
*/
def modifiedCompanion(
def appendModuleBody(
compDeclOpt: Option[ModuleDef],
codeBlock: Tree, className: TypeName): Tree = {
compDeclOpt map { compDecl =>
val q"$mods object $obj extends ..$bases { ..$body }" = compDecl
val o =
q"""
$mods object $obj extends ..$bases {
..$body
..$codeBlock
}
"""
c.info(c.enclosingPosition, s"modifiedCompanion className: $className, exists obj: $o", force = true)
o
} getOrElse {
// Create a companion object with the builder
val o = q"object ${className.toTermName} { ..$codeBlock }"
c.info(c.enclosingPosition, s"modifiedCompanion className: $className, new obj: $o", force = true)
o
codeBlocks: List[Tree], className: TypeName): Tree = {
compDeclOpt.fold(q"object ${className.toTermName} { ..$codeBlocks }") {
compDecl =>
c.info(c.enclosingPosition, s"appendModuleBody className: $className, exists obj: $compDecl", force = true)
val ModuleDef(mods, name, impl) = compDecl
val Template(parents, self, body) = impl
val newImpl = Template(parents, self, body ++ codeBlocks)
ModuleDef(mods, name, newImpl)
}
}
......@@ -366,4 +328,117 @@ abstract class AbstractMacroProcessor(val c: whitebox.Context) {
superClasses.nonEmpty && !superClasses.forall(sc => SDKClasses.contains(sc.toString()))
}
private[macros] case class ValDefAccessor(
mods: Modifiers,
name: TermName,
tpt: Tree,
rhs: Tree
) {
def typeName: TypeName = symbol.name.toTypeName
def symbol: c.universe.Symbol = paramType.typeSymbol
def paramType = c.typecheck(tq"$tpt", c.TYPEmode).tpe
}
/**
* Retrieves the accessor fields on a class and returns a Seq of ValDefAccessor.
*
* @param params The list of params retrieved from the class
* @return An Sequence of tuples where each tuple encodes the string name and string type of a field
*/
def valDefAccessors(params: Seq[Tree]): Seq[ValDefAccessor] = {
params.map {
case ValDef(mods, name: TermName, tpt: Tree, rhs) =>
ValDefAccessor(mods, name, tpt, rhs)
}
}
/**
* Extract the necessary structure information of the class for macro programming.
*
* @param classDecl
*/
def mapToClassDeclInfo(classDecl: ClassDef): ClassDefinition = {
val q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" = classDecl
val (className, classParamss, classTypeParams) = (tpname.asInstanceOf[TypeName], paramss.asInstanceOf[List[List[Tree]]], tparams.asInstanceOf[List[Tree]])
ClassDefinition(self.asInstanceOf[ValDef], mods.asInstanceOf[Modifiers], className, classParamss, classTypeParams, stats.asInstanceOf[List[Tree]], parents.asInstanceOf[List[Tree]])
}
/**
* Extract the necessary structure information of the moduleDef for macro programming.
*
* @param moduleDef
*/
def mapToModuleDeclInfo(moduleDef: ModuleDef): ClassDefinition = {
val q"$mods object $tpname extends { ..$earlydefns } with ..$parents { $self => ..$stats }" = moduleDef
ClassDefinition(self.asInstanceOf[ValDef], mods.asInstanceOf[Modifiers], tpname.asInstanceOf[TermName].toTypeName, Nil, Nil, stats.asInstanceOf[List[Tree]], parents.asInstanceOf[List[Tree]])
}
/**
* Generate the specified syntax tree and assign it to the tree definition itself.
* Used only when you modify the definition of the class itself. Such as add method/add field.
*
* @param classDecl
* @param classInfoAction Content body added in class definition
* @return
*/
def appendClassBody(classDecl: ClassDef, classInfoAction: ClassDefinition => List[Tree]): c.universe.ClassDef = {
val classInfo = mapToClassDeclInfo(classDecl)
val ClassDef(mods, name, tparams, impl) = classDecl
val Template(parents, self, body) = impl
ClassDef(mods, name, tparams, Template(parents, self, body ++ classInfoAction(classInfo)))
}
// TODO fix, why cannot use ClassDef apply
def prependImplDefBody(implDef: ImplDef, classInfoAction: ClassDefinition => List[Tree]): c.universe.Tree = {
implDef match {
case classDecl: ClassDef =>
val classInfo = mapToClassDeclInfo(classDecl)
val q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" = classDecl
q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${classInfoAction(classInfo) ++ stats} }"
case moduleDef: ModuleDef =>
val classInfo = mapToModuleDeclInfo(moduleDef)
val q"$mods object $tpname extends { ..$earlydefns } with ..$parents { $self => ..$stats }" = moduleDef
q"$mods object $tpname extends { ..$earlydefns } with ..$parents { $self => ..${classInfoAction(classInfo) ++ stats.toList} }"
}
}
def appendImplDefSuper(implDef: ImplDef, classInfoAction: ClassDefinition => List[Tree]): c.universe.Tree = {
implDef match {
case classDecl: ClassDef =>
val classInfo = mapToClassDeclInfo(classDecl)
val q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" = classDecl
q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..${parents ++ classInfoAction(classInfo)} { $self => ..$stats }"
case moduleDef: ModuleDef =>
val classInfo = mapToModuleDeclInfo(moduleDef)
val q"$mods object $tpname extends { ..$earlydefns } with ..$parents { $self => ..$stats }" = moduleDef
q"$mods object $tpname extends { ..$earlydefns } with ..${parents.toList ++ classInfoAction(classInfo)} { $self => ..$stats }"
}
}
/**
* Modify the method body of the method tree.
*
* @param defDef
* @param defBodyAction Method body of final result
* @return
*/
def mapToMethodDef(defDef: DefDef, defBodyAction: => Tree): c.universe.DefDef = {
val DefDef(mods, name, tparams, vparamss, tpt, rhs) = defDef
DefDef(mods, name, tparams, vparamss, tpt, defBodyAction)
}
private[macros] case class ClassDefinition(
self: ValDef,
mods: Modifiers,
className: TypeName,
classParamss: List[List[Tree]],
classTypeParams: List[Tree],
body: List[Tree],
superClasses: List[Tree],
earlydefns: List[Tree] = Nil
)
}
......@@ -43,14 +43,10 @@ object applyMacro {
}
}
override def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val (className, classParamss, classTypeParams) = classDecl match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$bases { ..$body }" =>
(tpname.asInstanceOf[TypeName], paramss.asInstanceOf[List[List[Tree]]], tparams.asInstanceOf[List[Tree]])
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
}
val apply = getApplyMethodWithCurrying(className, classParamss, classTypeParams)
val compDecl = modifiedCompanion(compDeclOpt, apply, className)
override def createCustomExpr(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val classDefinition = mapToClassDeclInfo(classDecl)
val apply = getApplyMethodWithCurrying(classDefinition.className, classDefinition.classParamss, classDefinition.classTypeParams)
val compDecl = appendModuleBody(compDeclOpt, List(apply), classDefinition.className)
c.Expr(
q"""
$classDecl
......@@ -59,9 +55,11 @@ object applyMacro {
}
override def impl(annottees: Expr[Any]*): Expr[Any] = {
val annotateeClass: ClassDef = checkAndGetClassDef(annottees: _*)
if (isCaseClass(annotateeClass)) c.abort(c.enclosingPosition, ErrorMessage.ONLY_CASE_CLASS)
val resTree = handleWithImplType(annottees: _*)(modifiedDeclaration)
val annotateeClass: ClassDef = checkGetClassDef(annottees)
if (isCaseClass(annotateeClass)) {
c.abort(c.enclosingPosition, ErrorMessage.ONLY_CASE_CLASS)
}
val resTree = collectCustomExpr(annottees)(createCustomExpr)
printTree(force = extractArgumentsDetail._1, resTree.tree)
resTree
}
......
......@@ -40,9 +40,8 @@ object builderMacro {
}
private def getFieldDefinition(field: Tree): Tree = {
field match {
case v: ValDef => q"private var ${v.name}: ${v.tpt} = ${v.rhs}"
}
val ValDef(mods, name, tpt, rhs) = field
q"private var $name: $tpt = $rhs"
}
private def getFieldSetMethod(typeName: TypeName, field: Tree, classTypeParams: List[Tree]): Tree = {
......@@ -56,41 +55,35 @@ object builderMacro {
}
"""
}
field match {
case v: ValDef => valDefMapTo(v)
}
valDefMapTo(field.asInstanceOf[ValDef])
}
private def getBuilderClassAndMethod(typeName: TypeName, fieldss: List[List[Tree]], classTypeParams: List[Tree], isCase: Boolean): Tree = {
private def getBuilderClassAndMethod(typeName: TypeName, fieldss: List[List[Tree]], classTypeParams: List[Tree], isCase: Boolean): List[Tree] = {
val fields = fieldss.flatten
val builderClassName = getBuilderClassName(typeName)
val builderFieldMethods = fields.map(f => getFieldSetMethod(typeName, f, classTypeParams))
val builderFieldDefinitions = fields.map(f => getFieldDefinition(f))
val returnTypeParams = extractClassTypeParamsTypeName(classTypeParams)
q"""
def builder[..$classTypeParams](): $builderClassName[..$returnTypeParams] = new $builderClassName()
class $builderClassName[..$classTypeParams] {
val builderMethod = q"def builder[..$classTypeParams](): $builderClassName[..$returnTypeParams] = new $builderClassName()"
val buulderClass =
q"""
class $builderClassName[..$classTypeParams] {
..$builderFieldDefinitions
..$builderFieldMethods
def build(): $typeName[..$returnTypeParams] = ${getConstructorWithCurrying(typeName, fieldss, isCase)}
}
"""
}
"""
List(builderMethod, buulderClass)
}
override def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val (className, annotteeClassParams, 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 }" =>
(tpname.asInstanceOf[TypeName], paramss.asInstanceOf[List[List[Tree]]], tparams.asInstanceOf[List[Tree]])
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
}
val builder = getBuilderClassAndMethod(className, annotteeClassParams, classTypeParams, isCaseClass(classDecl))
val compDecl = modifiedCompanion(compDeclOpt, builder, className)
override def createCustomExpr(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val classDefinition = mapToClassDeclInfo(classDecl)
val builder = getBuilderClassAndMethod(classDefinition.className, classDefinition.classParamss,
classDefinition.classTypeParams, isCaseClass(classDecl))
val compDecl = appendModuleBody(compDeclOpt, builder, classDefinition.className)
// Return both the class and companion object declarations
c.Expr(
q"""
......@@ -100,7 +93,7 @@ object builderMacro {
}
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
val resTree = handleWithImplType(annottees: _*)(modifiedDeclaration)
val resTree = collectCustomExpr(annottees)(createCustomExpr)
printTree(force = true, resTree.tree)
resTree
}
......
......@@ -98,25 +98,23 @@ object constructorMacro {
applyMethod
}
override def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val (annotteeClassParams, annotteeClassDefinitions) = classDecl match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
(paramss.asInstanceOf[List[List[Tree]]], stats.asInstanceOf[Seq[Tree]])
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
}
c.Expr(getThisMethodWithCurrying(annotteeClassParams, annotteeClassDefinitions))
override def createCustomExpr(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val resTree = appendClassBody(
classDecl,
classInfo => List(getThisMethodWithCurrying(classInfo.classParamss, classInfo.body)))
c.Expr(
q"""
${compDeclOpt.fold(EmptyTree)(x => x)}
$resTree
""")
}
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
val annotateeClass: ClassDef = checkAndGetClassDef(annottees: _*)
if (isCaseClass(annotateeClass)) c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $annotateeClass")
val tmpTree = handleWithImplType(annottees: _*)(modifiedDeclaration)
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.:+(tmpTree.tree)} }"
val annotateeClass: ClassDef = checkGetClassDef(annottees)
if (isCaseClass(annotateeClass)) {
c.abort(c.enclosingPosition, ErrorMessage.ONLY_CLASS)
}
val res = c.Expr[Any](treeResultWithCompanionObject(resTree, annottees: _*))
val res = collectCustomExpr(annottees)(createCustomExpr)
printTree(force = extractArgumentsDetail._1, res.tree)
res
}
......
......@@ -44,24 +44,13 @@ object equalsAndHashCodeMacro {
}
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
val annotateeClass: ClassDef = checkAndGetClassDef(annottees: _*)
if (isCaseClass(annotateeClass)) c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $annotateeClass")
val tmpTree = handleWithImplType(annottees: _*)(modifiedDeclaration)
// return with object if it exists
val resTree = annotateeClass match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
val originalStatus = q"{ ..$stats }"
val append =
q"""
..$originalStatus
..$tmpTree
"""
q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${append} }"
val annotateeClass: ClassDef = checkGetClassDef(annottees)
if (isCaseClass(annotateeClass)) {
c.abort(c.enclosingPosition, ErrorMessage.ONLY_CLASS)
}
val res = c.Expr[Any](treeResultWithCompanionObject(resTree, annottees: _*))
printTree(force = extractArgumentsDetail._1, res.tree)
res
val resTree = collectCustomExpr(annottees)(createCustomExpr)
printTree(force = extractArgumentsDetail._1, resTree.tree)
resTree
}
/**
......@@ -76,65 +65,57 @@ object equalsAndHashCodeMacro {
}
// equals method
private def getEqualsMethod(className: TypeName, termNames: Seq[TermName], superClasses: Seq[Tree], annotteeClassDefinitions: Seq[Tree]): Tree = {
private def getEqualsMethod(className: TypeName, termNames: Seq[TermName], superClasses: Seq[Tree], annotteeClassDefinitions: Seq[Tree]): List[Tree] = {
val existsCanEqual = getClassMemberDefDefs(annotteeClassDefinitions).exists {
case tree @ q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr" if tname.asInstanceOf[TermName].decodedName.toString == "canEqual" && paramss.nonEmpty =>
val params = paramss.asInstanceOf[List[List[Tree]]].flatten.map(pp => getMethodParamName(pp))
params.exists(p => p.decodedName.toString == "Any")
case defDef: DefDef if defDef.name.decodedName.toString == "canEqual" && defDef.vparamss.nonEmpty =>
val safeValDefs = valDefAccessors(defDef.vparamss.flatten)
safeValDefs.exists(_.paramType.toString == "Any") && safeValDefs.exists(_.name.decodedName.toString == "that")
case _ => false
}
lazy val getEqualsExpr = (termName: TermName) => {
q"this.$termName.equals(t.$termName)"
}
val equalsExprs = termNames.map(getEqualsExpr)
val equalsExprs = termNames.map(termName => q"this.$termName.equals(t.$termName)")
// Make a rough judgment on whether override is needed.
val modifiers = if (existsSuperClassExcludeSdkClass(superClasses)) 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
val equalsMethod =
q"""
override def equals(that: Any): Boolean =
that match {
case t: $className => t.canEqual(this) && Seq(..$equalsExprs).forall(f => f) && ${if (existsSuperClassExcludeSdkClass(superClasses)) q"super.equals(that)" else q"true"}
case _ => false
}
"""
List(canEqual, equalsMethod)
}
private def getHashcodeMethod(termNames: Seq[TermName], superClasses: Seq[Tree]): Tree = {
// we append super.hashCode by `+`
// the algorithm see https://alvinalexander.com/scala/how-to-define-equals-hashcode-methods-in-scala-object-equality/
if (!existsSuperClassExcludeSdkClass(superClasses)) {
q"""
override def hashCode(): Int = {
val state = Seq(..$termNames)
state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b)
}
"""
} else {
q"""
val superTree = q"super.hashCode"
q"""
override def hashCode(): Int = {
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) + ${if (existsSuperClassExcludeSdkClass(superClasses)) superTree else q"0"}
}
"""
}
"""
}
override def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef]): Any = {
val (className, annotteeClassParams, annotteeClassDefinitions, superClasses) = classDecl match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
(tpname.asInstanceOf[TypeName], paramss.asInstanceOf[List[List[Tree]]], stats.asInstanceOf[Seq[Tree]], parents.asInstanceOf[Seq[Tree]])
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
override def createCustomExpr(classDecl: ClassDef, compDeclOpt: Option[ModuleDef]): Any = {
lazy val map = (classDefinition: ClassDefinition) => {
getClassConstructorValDefsFlatten(classDefinition.classParamss).
filter(cf => isNotLocalClassMember(cf)).
map(_.name.toTermName) ++
getInternalFieldsTermNameExcludeLocal(classDefinition.body)
}
val allFieldsTermName = getClassConstructorValDefsFlatten(annotteeClassParams).filter(cf => isNotLocalClassMember(cf)).map(_.name.toTermName)
val allTernNames = allFieldsTermName ++ getInternalFieldsTermNameExcludeLocal(annotteeClassDefinitions)
val hash = getHashcodeMethod(allTernNames, superClasses)
val equals = getEqualsMethod(className, allTernNames, superClasses, annotteeClassDefinitions)
val classDefinition = mapToClassDeclInfo(classDecl)
val res = appendClassBody(classDecl, classInfo =>
getEqualsMethod(classDefinition.className, map(classInfo), classDefinition.superClasses, classDefinition.body) ++
List(getHashcodeMethod(map(classInfo), classDefinition.superClasses))
)
c.Expr(
q"""
..$equals
$hash
${compDeclOpt.fold(EmptyTree)(x => x)}
$res
""")
}
}
......
/*
* 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.macros
import scala.reflect.macros.whitebox
object jacksonEnumMacro {
class JacksonEnumProcessor(override val c: whitebox.Context) extends AbstractMacroProcessor(c) {
import c.universe._
private val extractArgumentsDetail: Tuple2[Boolean, Seq[String]] = {
extractArgumentsTuple2 {
case q"new jacksonEnum(verbose=$verbose, nonTypeRefers=$nonTypeRefers)" => Tuple2(evalTree(verbose.asInstanceOf[Tree]), evalTree(nonTypeRefers.asInstanceOf[Tree]))
case q"new jacksonEnum(nonTypeRefers=$nonTypeRefers)" => Tuple2(false, evalTree(nonTypeRefers.asInstanceOf[Tree]))
case q"new jacksonEnum()" => Tuple2(false, Nil)
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
}
}
private def getJacksonTypeReferClasses(valDefs: List[ValDef]): Seq[Tree] = {
val safeValDefs = valDefAccessors(valDefs)
// Enum ?
safeValDefs.filter(_.symbol.name.toTermName.toString == "Value").
map(getTypeTermName).
filter(v => !extractArgumentsDetail._2.contains(v.decodedName.toString)).
distinct.
map(c => q"""class ${TypeName(c.decodedName.toString + "TypeRefer")} extends _root_.com.fasterxml.jackson.core.`type`.TypeReference[$c.type]""")
}
private def getTypeTermName(valDefTree: Tree): c.universe.TermName = {
val safeValDef = valDefAccessors(Seq(valDefTree)).head
getTypeTermName(safeValDef)
}
private def getTypeTermName(accessor: ValDefAccessor): c.universe.TermName = {
val paramTypeStr = accessor.paramType.toString
TermName(paramTypeStr.split("\\.").last)
}
private def getAnnotation(valDefTree: Tree): Tree = {
q"new com.fasterxml.jackson.module.scala.JsonScalaEnumeration(classOf[${TypeName(getTypeTermName(valDefTree).decodedName.toString + "TypeRefer")}])"
}
private def replaceAnnotation(valDefTree: Tree): Tree = {
val safeValDef = valDefAccessors(Seq(valDefTree)).head
if (safeValDef.typeName.decodedName.toString == "Value") {
// duplication should be removed
val mods = safeValDef.mods.mapAnnotations(f => {
if (!f.toString().contains("JsonScalaEnumeration") &&
!extractArgumentsDetail._2.contains(getTypeTermName(safeValDef).decodedName.toString)) f ++ List(getAnnotation(valDefTree)) else f
})
ValDef(mods, safeValDef.name, safeValDef.tpt, safeValDef.rhs)
} else {
valDefTree
}
}
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
val res = collectCustomExpr(annottees)(createCustomExpr)
printTree(force = extractArgumentsDetail._1, res.tree)
res
}
override def createCustomExpr(classDecl: c.universe.ClassDef, compDeclOpt: Option[c.universe.ModuleDef]): Any = {
// return all typeReferClasses and new classDef
val classDefinition = mapToClassDeclInfo(classDecl)
val valDefs = classDefinition.classParamss.flatten.map(_.asInstanceOf[ValDef])
val typeReferClasses = getJacksonTypeReferClasses(valDefs).distinct
val q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$bases { ..$body }" = classDecl
val newFieldss = paramss.asInstanceOf[List[List[Tree]]].map(_.map(replaceAnnotation))
val newClass = q"$mods class $tpname[..$tparams] $ctorMods(...$newFieldss) extends ..$bases { ..$body }"
val res =
q"""
..$typeReferClasses
$newClass
"""
c.Expr(
q"""
${compDeclOpt.fold(EmptyTree)(x => x)}
$res
""")
}
}
}
......@@ -44,23 +44,19 @@ object jsonMacro {
}
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
val resTree = handleWithImplType(annottees: _*)(modifiedDeclaration)
val annotateeClass: ClassDef = checkGetClassDef(annottees)
if (!isCaseClass(annotateeClass)) {
c.abort(c.enclosingPosition, ErrorMessage.ONLY_CASE_CLASS)
}
val resTree = collectCustomExpr(annottees)(createCustomExpr)
printTree(force = true, resTree.tree)
resTree
}
override def modifiedDeclaration(classDecl: c.universe.ClassDef, compDeclOpt: Option[c.universe.ModuleDef]): Any = {
val (className, fields) = classDecl match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$bases { ..$body }" =>
if (!mods.asInstanceOf[Modifiers].hasFlag(Flag.CASE)) {
c.abort(c.enclosingPosition, ErrorMessage.ONLY_CASE_CLASS)
} else {
(tpname.asInstanceOf[TypeName], paramss.asInstanceOf[List[List[Tree]]])
}
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
}
val format = jsonFormatter(className, fields.flatten)
val compDecl = modifiedCompanion(compDeclOpt, format, className)
override def createCustomExpr(classDecl: c.universe.ClassDef, compDeclOpt: Option[c.universe.ModuleDef]): Any = {
val classDefinition = mapToClassDeclInfo(classDecl)
val format = jsonFormatter(classDefinition.className, classDefinition.classParamss.flatten)
val compDecl = appendModuleBody(compDeclOpt, List(format), classDefinition.className)
// Return both the class and companion object declarations
c.Expr(
q"""
......
......@@ -21,9 +21,9 @@
package io.github.dreamylost.macros
import io.github.dreamylost.{ PACKAGE, logs }
import io.github.dreamylost.logs.{ LogTransferArgument, LogType }
import io.github.dreamylost.logs.LogType._
import io.github.dreamylost.logs.{ LogTransferArgument, LogType }
import io.github.dreamylost.{ PACKAGE, logs }
import scala.reflect.macros.whitebox
......@@ -53,45 +53,53 @@ object logMacro {
private def getLogType(logType: Tree): LogType = {
if (logType.children.exists(t => t.toString().contains(PACKAGE))) {
evalTree(logType.asInstanceOf[Tree]) // TODO remove asInstanceOf
evalTree(logType)
} else {
LogType.getLogType(logType.toString())
}
}
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
val logTree = annottees.map(_.tree) match {
// Match a class, and expand, get class/object name.
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: _ =>
val argument = LogTransferArgument(tpname.asInstanceOf[TypeName].toTermName.decodedName.toString, isClass = true)
LogType.getLogImpl(extractArgumentsDetail._2).getTemplate(c)(argument)
case q"$mods object $tpname extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: _ =>
val argument = LogTransferArgument(tpname.asInstanceOf[TermName].decodedName.toString, isClass = false)
LogType.getLogImpl(extractArgumentsDetail._2).getTemplate(c)(argument)
private def logTree(annottees: Seq[c.universe.Expr[Any]]): c.universe.Tree = {
val buildArg = (name: Name) => LogTransferArgument(name.toTermName.decodedName.toString, isClass = true)
(annottees.map(_.tree) match {
case (classDef: ClassDef) :: Nil =>
LogType.getLogImpl(extractArgumentsDetail._2).getTemplate(c)(buildArg(classDef.name))
case (moduleDef: ModuleDef) :: Nil =>
LogType.getLogImpl(extractArgumentsDetail._2).getTemplate(c)(buildArg(moduleDef.name).copy(isClass = false))
case (classDef: ClassDef) :: (_: ModuleDef) :: Nil =>
LogType.getLogImpl(extractArgumentsDetail._2).getTemplate(c)(buildArg(classDef.name))
case _ => c.abort(c.enclosingPosition, ErrorMessage.ONLY_OBJECT_CLASS)
}
}).asInstanceOf[Tree]
}
// add result into class
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
val resTree = annottees.map(_.tree) match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: _ =>
extractArgumentsDetail._2 match {
case (classDef: ClassDef) :: _ =>
if (classDef.mods.hasFlag(Flag.CASE)) {
c.abort(c.enclosingPosition, ErrorMessage.ONLY_OBJECT_CLASS)
}
val newClass = extractArgumentsDetail._2 match {
case ScalaLoggingLazy | ScalaLoggingStrict =>
q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..${parents ++ Seq(logTree)} { $self => ..$stats }"
case _ => q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${Seq(logTree) ++ stats} }"
appendImplDefSuper(checkGetClassDef(annottees), _ => List(logTree(annottees)))
case _ =>
prependImplDefBody(checkGetClassDef(annottees), _ => List(logTree(annottees)))
}
case q"$mods object $tpname extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: _ =>
val moduleDef = getModuleDefOption(annottees)
q"""
${if (moduleDef.isEmpty) EmptyTree else moduleDef.get}
$newClass
"""
case (_: ModuleDef) :: _ =>
extractArgumentsDetail._2 match {
case ScalaLoggingLazy | ScalaLoggingStrict =>
q"$mods object $tpname extends { ..$earlydefns } with ..${parents ++ Seq(logTree)} { $self => ..$stats }"
case _ => q"$mods object $tpname extends { ..$earlydefns } with ..$parents { $self => ..${Seq(logTree) ++ stats} }"
case ScalaLoggingLazy | ScalaLoggingStrict => appendImplDefSuper(getModuleDefOption(annottees).get, _ => List(logTree(annottees)))
case _ => prependImplDefBody(getModuleDefOption(annottees).get, _ => List(logTree(annottees)))
}
// 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).
// see https://docs.scala-lang.org/overviews/macros/annotations.html
}
val res = treeResultWithCompanionObject(resTree, annottees: _*)
printTree(force = extractArgumentsDetail._1, res)
printTree(force = extractArgumentsDetail._1, resTree)
c.Expr[Any](resTree)
}
}
......
......@@ -31,10 +31,10 @@ package object macros {
object ErrorMessage {
// common error msg
final val ONLY_CLASS = "Annotation is only supported on class."
final val ONLY_CASE_CLASS = "Annotation is only supported on case class."
final val ONLY_OBJECT_CLASS = "Annotation is only supported on class or object."
final val UNEXPECTED_PATTERN = "Unexpected annotation pattern!"
final lazy val ONLY_CLASS = "Annotation is only supported on class."
final lazy val ONLY_CASE_CLASS = "Annotation is only supported on case class."
final lazy val ONLY_OBJECT_CLASS = "Annotation is only supported on class or object."
final lazy val UNEXPECTED_PATTERN = "Unexpected annotation pattern!"
}
}
......@@ -43,18 +43,18 @@ object synchronizedMacro {
}
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
val resTree = annottees map (_.tree) match {
// Match a method, and expand.
case _@ q"$modrs def $tname[..$tparams](...$paramss): $tpt = $expr" :: _ =>
if (extractArgumentsDetail._2 != null) {
val resTree = annottees.map(_.tree) match {
case (defDef: DefDef) :: Nil =>
val body = if (extractArgumentsDetail._2 != null) {
if (extractArgumentsDetail._2 == "this") {
q"""def $tname[..$tparams](...$paramss): $tpt = ${This(TypeName(""))}.synchronized { $expr }"""
q"${This(TypeName(""))}.synchronized { ${defDef.rhs} }"
} else {
q"""def $tname[..$tparams](...$paramss): $tpt = ${TermName(extractArgumentsDetail._2)}.synchronized { $expr }"""
q"${TermName(extractArgumentsDetail._2)}.synchronized { ${defDef.rhs} }"
}
} else {
c.abort(c.enclosingPosition, "Invalid args, lockName cannot be a null!")
}
mapToMethodDef(defDef, body)
case _ => c.abort(c.enclosingPosition, "Invalid annotation target: not a method")
}
printTree(extractArgumentsDetail._1, resTree)
......
......@@ -67,21 +67,26 @@ object toStringMacro {
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
}
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
override def createCustomExpr(classDecl: c.universe.ClassDef, compDeclOpt: Option[c.universe.ModuleDef]): Any = {
// extract parameters of annotation, must in order
val argument = Argument(extractArgumentsDetail._1, extractArgumentsDetail._2, extractArgumentsDetail._3, extractArgumentsDetail._4)
// Check the type of the class, which can only be defined on the ordinary class
val annotateeClass: ClassDef = checkAndGetClassDef(annottees: _*)
val isCase: Boolean = isCaseClass(annotateeClass)
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 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
""")
}
val res = treeResultWithCompanionObject(resTree, annottees: _*)
printTree(argument.verbose, res)
c.Expr[Any](res)
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
val res = collectCustomExpr(annottees)(createCustomExpr)
printTree(force = extractArgumentsDetail._1, res.tree)
res
}
private def printField(argument: Argument, lastParam: Option[String], field: Tree): Tree = {
......@@ -105,16 +110,11 @@ object toStringMacro {
}
}
private def toStringTemplateImpl(argument: Argument, annotateeClass: ClassDef): Tree = {
private def getToStringTemplate(argument: Argument, classDecl: ClassDef): Tree = {
// For a given class definition, separate the components of the class
val (className, annotteeClassParams, superClasses, annotteeClassDefinitions) = {
annotateeClass match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
(tpname.asInstanceOf[TypeName], paramss.asInstanceOf[List[List[Tree]]], parents, stats.asInstanceOf[List[Tree]])
}
}
val classDefinition = mapToClassDeclInfo(classDecl)
// Check the type of the class, whether it already contains its own toString
val annotteeClassFieldDefinitions = annotteeClassDefinitions.filter(p => p match {
val annotteeClassFieldDefinitions = classDefinition.body.filter(_ match {
case _: ValDef => true
case mem: MemberDef =>
if (mem.name.decodedName.toString.startsWith("toString")) { // TODO better way
......@@ -124,7 +124,7 @@ object toStringMacro {
case _ => false
})
val ctorParams = annotteeClassParams.flatten
val ctorParams = classDefinition.classParamss.flatten
val member = if (argument.includeInternalFields) ctorParams ++ annotteeClassFieldDefinitions else ctorParams
val lastParam = member.lastOption.map {
......@@ -133,17 +133,17 @@ object toStringMacro {
}
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
val toString = q"""override def toString: String = ${className.toTermName.decodedName.toString} + ${"("} + $paramsWithName + ${")"}"""
val toString = q"""override def toString: String = ${classDefinition.className.toTermName.decodedName.toString} + ${"("} + $paramsWithName + ${")"}"""
// Have super class ?
if (argument.callSuper && superClasses.nonEmpty) {
val superClassDef = superClasses.head match {
if (argument.callSuper && classDefinition.superClasses.nonEmpty) {
val superClassDef = classDefinition.superClasses.head match {
case tree: Tree => Some(tree) // TODO type check better
case _ => None
}
superClassDef.fold(toString)(_ => {
val superClass = q"${"super="}"
q"override def toString: String = StringContext(${className.toTermName.decodedName.toString} + ${"("} + $superClass, ${if (member.nonEmpty) ", " else ""}+$paramsWithName + ${")"}).s(super.toString)"
q"override def toString: String = StringContext(${classDefinition.className.toTermName.decodedName.toString} + ${"("} + $superClass, ${if (member.nonEmpty) ", " else ""}+$paramsWithName + ${")"}).s(super.toString)"
}
)
} else {
......
/*
* 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 com.fasterxml.jackson.module.scala.JsonScalaEnumeration
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
/**
*
* @author 梦境迷离
* @version 1.0,2021/8/3
*/
class JacksonEnumTest extends AnyFlatSpec with Matchers {
object EnumType extends Enumeration {
type EnumType = Value
val A = Value(1)
val B = Value(2)
}
object EnumType2 extends Enumeration {
type EnumType2 = Value
val A, B = Value
}
object EnumType3 extends Enumeration {
type EnumType3 = Value
val A, B = Value
}
"jacksonEnum1" should "ok" in {
class EnumTypeTypeRefer extends _root_.com.fasterxml.jackson.core.`type`.TypeReference[EnumType.type]
case class A(
@JsonScalaEnumeration(classOf[EnumTypeTypeRefer]) enum1: EnumType.EnumType,
enum2: EnumType.EnumType = EnumType.A
)
}
"jacksonEnum2" should "ok" in {
@jacksonEnum
case class A(
enum1: EnumType.EnumType,
enum2: EnumType.EnumType = EnumType.A,
i: Int)
}
"jacksonEnum3" should "ok" in {
@jacksonEnum
case class A(
var enum1: EnumType.EnumType,
enum2: EnumType2.EnumType2 = EnumType2.A,
i: Int)
@jacksonEnum(nonTypeRefers = Seq("EnumType", "EnumType2")) // Because it has been created
class B(
var enum1: EnumType.EnumType, // No annotation will add
val enum2: EnumType2.EnumType2 = EnumType2.A,
val enum3: EnumType3.EnumType3,
i: Int)
}
"jacksonEnum4" should "ok when duplication" in {
"""
| @jacksonEnum
| case class A(
| @JsonScalaEnumeration(classOf[EnumTypeTypeRefer]) var enum1: EnumType.EnumType,
| enum2: EnumType2.EnumType2 = EnumType2.A,
| i: Int)
|""".stripMargin should compile
"""
| @jacksonEnum
| class A(
| @JsonScalaEnumeration(classOf[EnumTypeTypeRefer]) var enum1: EnumType.EnumType,
| enum2: EnumType2.EnumType2 = EnumType2.A,
| i: Int)
|""".stripMargin should compile
}
"jacksonEnum5" should "failed on object" in {
"""
| @jacksonEnum
| object A()
|""".stripMargin shouldNot compile
}
"jacksonEnum6" should "failed when input args are invalid" in {
"""
| @jacksonEnum(verbose=true, nonTypeRefers=Nil)
| class A(enum1: EnumType.EnumType)
|""".stripMargin should compile
"""
| @jacksonEnum(true)
| class B(enum1: EnumType.EnumType)
|""".stripMargin shouldNot compile
}
}
......@@ -45,18 +45,6 @@ class LogTest extends AnyFlatSpec with Matchers {
"""@log(verbose=true, logType=io.github.dreamylost.logs.LogType.JLog) class TestClass6(val i: Int = 0, var j: Int)""" should compile
}
"log2" should "ok on case class" in {
"""@log(verbose=true) case class TestClass1(val i: Int = 0, var j: Int) {
log.info("hello")
}""" should compile
"""@log case class TestClass2(val i: Int = 0, var j: Int)""" should compile
"""@log() case class TestClass3(val i: Int = 0, var j: Int)""" should compile
"""@log(verbose=true) case class TestClass4(val i: Int = 0, var j: Int)""" should compile
"""@log(logType=io.github.dreamylost.logs.LogType.JLog) case class TestClass5(val i: Int = 0, var j: Int)""" should compile
"""@log(verbose=true, logType=io.github.dreamylost.logs.LogType.JLog) case class TestClass6(val i: Int = 0, var j: Int)""" should compile
}
"log3" should "ok on object" in {
"""@log(verbose=true) object TestClass1 {
log.info("hello")
......@@ -121,8 +109,8 @@ class LogTest extends AnyFlatSpec with Matchers {
"log8 slf4j" should "ok on class and has object" in {
"""@log(verbose=true) class TestClass1(val i: Int = 0, var j: Int) {
log.info("hello")
}""" should compile
log.info("hello")
}""" should compile
"""@toString @builder @log class TestClass2(val i: Int = 0, var j: Int)""" should compile //Use with multiple annotations
"""@log() class TestClass3(val i: Int = 0, var j: Int)""" should compile
......@@ -131,7 +119,13 @@ class LogTest extends AnyFlatSpec with Matchers {
"""@log(verbose=true, logType=io.github.dreamylost.logs.LogType.Slf4j) class TestClass6(val i: Int = 0, var j: Int)""" should compile
"""@log(verbose=true, logType=io.github.dreamylost.logs.LogType.Slf4j) class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }""" should compile
"""@log(logType = io.github.dreamylost.logs.LogType.Slf4j) @builder class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }
| @log(logType = io.github.dreamylost.logs.LogType.Slf4j) object TestClass6 { log.info("hello world");builder() }""".stripMargin should compile //default verbose is false
| @log(logType = io.github.dreamylost.logs.LogType.Slf4j) object TestClass6 { log.info("hello world");builder() }""".stripMargin should compile //default verbose is false
@log(logType = io.github.dreamylost.logs.LogType.Slf4j)
@builder class TestClass8(val i: Int = 0, var j: Int) {
log.info("hello world")
}
object TestClass8 { builder() }
}
"log9 slf4j" should "ok on class and it object" in {
......@@ -141,15 +135,7 @@ class LogTest extends AnyFlatSpec with Matchers {
|""".stripMargin should compile
}
"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()
}
"log10 slf4j" should "failed on case class" in {
"""
| @log(verbose=false, logType = LogType.JLog)
| @builder case class TestClass6_2(val i: Int = 0, var j: Int) {
......@@ -158,7 +144,7 @@ class LogTest extends AnyFlatSpec with Matchers {
| @log(logType = io.github.dreamylost.logs.LogType.Slf4j) object TestClass6_2 {
| log.info("hello world"); builder()
| }
|""".stripMargin should compile
|""".stripMargin shouldNot compile
}
"log11 slf4j" should "ok on class and it object" in {
......@@ -202,7 +188,6 @@ class LogTest extends AnyFlatSpec with Matchers {
log.info("")
}
}
"log12 slf4j" should "failed when input not in order" in {
"""
| import io.github.dreamylost.logs.LogType
......@@ -242,14 +227,6 @@ class LogTest extends AnyFlatSpec with Matchers {
| log.info("hello world")
| }
|""".stripMargin should compile
"""
| import io.github.dreamylost.logs.LogType
| @log(logType = LogType.ScalaLoggingLazy)
| case class TestClass5(val i: Int = 0, var j: Int) {
| log.info("hello world")
| }
|""".stripMargin should compile
}
"log14 scala loggging strict" should "ok when exists super class" in {
......@@ -281,20 +258,8 @@ class LogTest extends AnyFlatSpec with Matchers {
| log.info("hello world")
| }
|""".stripMargin should compile
"""
| import io.github.dreamylost.logs.LogType
| @log(logType = LogType.ScalaLoggingStrict)
| case class TestClass5(val i: Int = 0, var j: Int) extends Serializable {
| log.info("hello world")
| }
|""".stripMargin should compile
}
// We must define the class outside so that the macro has been compiled before testing.
@log(logType = LogType.ScalaLoggingStrict)
@json case class TestClass1(val i: Int = 0, var j: Int, x: String, o: Option[String] = Some(""))
"log15 add @transient" should "ok" in {
"""
|val str = Json.toJson(TestClass1(1, 1, "hello")).toString()
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册