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

add `@constructor` (#37)

上级 beda0927
# scala-macro-tools [![Build](https://github.com/jxnu-liguobin/scala-macro-tools/actions/workflows/ScalaCI.yml/badge.svg)](https://github.com/jxnu-liguobin/scala-macro-tools/actions/workflows/ScalaCI.yml) # scala-macro-tools [![Build](https://github.com/jxnu-liguobin/scala-macro-tools/actions/workflows/ScalaCI.yml/badge.svg)](https://github.com/jxnu-liguobin/scala-macro-tools/actions/workflows/ScalaCI.yml) [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22io.github.jxnu-liguobin%22%20AND%20a:%22scala-macro-tools_2.13%22)
我写该库的动机 我写该库的动机
-- --
...@@ -18,6 +18,14 @@ ...@@ -18,6 +18,14 @@
- `@synchronized` - `@synchronized`
- `@log` - `@log`
- `@apply` - `@apply`
- `@constructor`
## 已知问题
- 不支持柯里化。
- 不支持泛型。
- `@constructor``@toString`同时使用,必须放最后。
- IDEA对宏的支持不是很好,所以会出现标红,不过编译没问题,调用结果也符合预期。
## @toString ## @toString
...@@ -52,8 +60,8 @@ println(new TestClass(1, 2)); ...@@ -52,8 +60,8 @@ println(new TestClass(1, 2));
`@json`注解是向Play项目的样例类添加json format对象的最快方法。 `@json`注解是向Play项目的样例类添加json format对象的最快方法。
- 说明 - 说明
- 此注释启发来自[json-annotation](https://github.com/kifi/json-annotation),并做了优化,现在它可以与其他注解同时使用。 - 此注释启发来自[json-annotation](https://github.com/kifi/json-annotation),并做了优化,现在它可以与其他注解同时使用。
- 只有一个隐式的`val`值会被自动生成(如果伴生对象不存在的话,还会生成一个伴生对象用于存放该隐式值),此外没有其他的操作。 - 只有一个隐式的`val`值会被自动生成(如果伴生对象不存在的话,还会生成一个伴生对象用于存放该隐式值),此外没有其他的操作。
- 示例 - 示例
...@@ -76,11 +84,8 @@ Json.fromJson[Person](json) ...@@ -76,11 +84,8 @@ Json.fromJson[Person](json)
`@builder`注解用于为Scala类生成构造器模式。 `@builder`注解用于为Scala类生成构造器模式。
- 说明 - 说明
- 支持普通类和样例类。 - 支持普通类和样例类。
- 如果该类没有伴生对象,将生成一个伴生对象来存储`builder`方法和类。 - 如果该类没有伴生对象,将生成一个伴生对象来存储`builder`方法和类。
- 目前不支持主构造函数是柯里化的。
> IDEA对宏的支持不是很好,所以会出现标红,不过编译没问题,调用结果也符合预期。这意味着,目前不支持语法提示。
- 示例 - 示例
...@@ -92,7 +97,7 @@ val ret = TestClass1.builder().i(1).j(0).x("x").build() ...@@ -92,7 +97,7 @@ val ret = TestClass1.builder().i(1).j(0).x("x").build()
assert(ret.toString == "TestClass1(1,0,x,Some())") assert(ret.toString == "TestClass1(1,0,x,Some())")
``` ```
宏生成的中间代码 宏生成的中间代码
```scala ```scala
object TestClass1 extends scala.AnyRef { object TestClass1 extends scala.AnyRef {
...@@ -136,7 +141,7 @@ object TestClass1 extends scala.AnyRef { ...@@ -136,7 +141,7 @@ object TestClass1 extends scala.AnyRef {
`@synchronized`注解是一个更方便、更灵活的用于同步方法的注解。 `@synchronized`注解是一个更方便、更灵活的用于同步方法的注解。
- 说明 - 说明
- `lockedName` 指定自定义的锁对象的名称。可选,默认`this` - `lockedName` 指定自定义的锁对象的名称。可选,默认`this`
- 支持静态方法(`object`中的函数)和实例方法(`class`中的函数)。 - 支持静态方法(`object`中的函数)和实例方法(`class`中的函数)。
- 示例 - 示例
...@@ -157,7 +162,7 @@ def getStr(k: Int): String = { ...@@ -157,7 +162,7 @@ def getStr(k: Int): String = {
} }
``` ```
Compiler intermediate code: 宏生成的中间代码:
```scala ```scala
// 注意,它不会判断synchronized是否已经存在,因此如果synchronized已经存在,它将被使用两次。如下 // 注意,它不会判断synchronized是否已经存在,因此如果synchronized已经存在,它将被使用两次。如下
...@@ -177,9 +182,6 @@ def getStr(k: Int): String = this.synchronized(k.$plus("")) ...@@ -177,9 +182,6 @@ def getStr(k: Int): String = this.synchronized(k.$plus(""))
- `io.github.dreamylost.LogType.Log4j2` 使用 `org.apache.logging.log4j.Logger` - `io.github.dreamylost.LogType.Log4j2` 使用 `org.apache.logging.log4j.Logger`
- `io.github.dreamylost.LogType.Slf4j` 使用 `org.slf4j.Logger` - `io.github.dreamylost.LogType.Slf4j` 使用 `org.slf4j.Logger`
- 支持普通类,样例类,单例对象。 - 支持普通类,样例类,单例对象。
> IDEA对宏的支持不是很好,所以会出现标红,不过编译没问题,调用结果也符合预期。这意味着,目前不支持语法提示。
- 示例 - 示例
...@@ -197,17 +199,46 @@ def getStr(k: Int): String = this.synchronized(k.$plus("")) ...@@ -197,17 +199,46 @@ def getStr(k: Int): String = this.synchronized(k.$plus(""))
- 说明 - 说明
- `verbose` 指定是否开启详细编译日志。可选,默认`false` - `verbose` 指定是否开启详细编译日志。可选,默认`false`
- 仅支持在`class`上使用。 - 仅支持在`class`上使用且仅支持主构造函数。
- 仅支持主构造函数。
- 目前不支持主构造函数是柯里化的。
> IDEA对宏的支持不是很好,所以会出现标红,不过编译没问题,调用结果也符合预期。这意味着,目前不支持语法提示。
- 示例 - 示例
```scala ```scala
@apply @toString class B2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L)) @apply @toString class B2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))
println(B2(1, 2)) println(B2(1, 2, None, None)) //0.1.0,不携带字段的默认值到apply参数中,所以参数都是必传
```
## @constructor
`@constructor`注解用于为普通类生成辅助构造函数。
- 说明
- `verbose` 指定是否开启详细编译日志。可选,默认`false`
- `excludeFields` 指定是否需要排除不需要用于构造函数的`var`字段。可选,默认空(所有class内部的`var`字段都将作为构造函数的入参)。
- 仅支持在`class`上使用。
- 示例
```scala
@constructor(excludeFields = Seq("c")) //排除c字段。其中,a是val的不需要手动指定,自动排除。
class A2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L)) {
private val a: Int = 1
var b: Int = 1 // 不携带字段的默认值到apply参数中,所以参数都是必传
protected var c: Int = _
def helloWorld: String = "hello world"
}
println(new A2(1, 2, None, None, 100))
```
宏生成的中间代码(仅构造函数部分):
```scala
def <init>(int: Int, j: Int, k: Option[String], t: Option[Long], b: Int) = {
<init>(int, j, k, t);
this.b = b
}
``` ```
# 如何使用 # 如何使用
......
# scala-macro-tools [![Build](https://github.com/jxnu-liguobin/scala-macro-tools/actions/workflows/ScalaCI.yml/badge.svg)](https://github.com/jxnu-liguobin/scala-macro-tools/actions/workflows/ScalaCI.yml) # scala-macro-tools [![Build](https://github.com/jxnu-liguobin/scala-macro-tools/actions/workflows/ScalaCI.yml/badge.svg)](https://github.com/jxnu-liguobin/scala-macro-tools/actions/workflows/ScalaCI.yml) [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22io.github.jxnu-liguobin%22%20AND%20a:%22scala-macro-tools_2.13%22)
Motivation Motivation
-- --
...@@ -17,6 +17,16 @@ Learn Scala macro and abstract syntax tree. ...@@ -17,6 +17,16 @@ Learn Scala macro and abstract syntax tree.
- `@synchronized` - `@synchronized`
- `@log` - `@log`
- `@apply` - `@apply`
- `@constructor`
## Known Issues
- Currying is not supported.
- Generic is not supported.
- When `@constructor` and `@toString` are used together, the former must be put last.
- IDE support is not very good, a red prompt will appear, but the compilation is OK.
## @toString ## @toString
...@@ -28,7 +38,6 @@ The `@toString` used to generate `toString` for Scala classes or a `toString` wi ...@@ -28,7 +38,6 @@ The `@toString` used to generate `toString` for Scala classes or a `toString` wi
- `includeInternalFields` Whether to include the fields defined within a class. Not in a primary constructor, default is `true`. - `includeInternalFields` Whether to include the fields defined within a class. Not in a primary constructor, default is `true`.
- `callSuper` Whether to include the super's `toString`, default is `false`. Not support if super class is a trait. - `callSuper` Whether to include the super's `toString`, default is `false`. Not support if super class is a trait.
- Support `case class` and `class`. - Support `case class` and `class`.
- Currying is not supported in constructors at present.
- Example - Example
...@@ -52,11 +61,11 @@ println(new TestClass(1, 2)); ...@@ -52,11 +61,11 @@ println(new TestClass(1, 2));
The `@json` scala macro annotation is the quickest way to add a JSON format to your Play project's case classes. The `@json` scala macro annotation is the quickest way to add a JSON format to your Play project's case classes.
- Note - Note
- This annotation is drawn from [json-annotation](https://github.com/kifi/json-annotation) and have some - This annotation is drawn from [json-annotation](https://github.com/kifi/json-annotation) and have some
optimization. optimization.
- It can also be used when there are other annotations on the case classes. - It can also be used when there are other annotations on the case classes.
- Only an implicit `val` was generated automatically(Maybe generate a companion object if it not exists), and there are no other - Only an implicit `val` was generated automatically(Maybe generate a companion object if it not exists), and there are no other
operations. operations.
- Example - Example
```scala ```scala
...@@ -78,11 +87,8 @@ Json.fromJson[Person](json) ...@@ -78,11 +87,8 @@ Json.fromJson[Person](json)
The `@builder` used to generate builder pattern for Scala classes. The `@builder` used to generate builder pattern for Scala classes.
- Note - Note
- Support `case class` / `class`. - Support `case class` / `class`.
- If there is no companion object, one will be generated to store the `builder` class and method. - If there is no companion object, one will be generated to store the `builder` class and method.
- Currying is not supported in constructors at present.
> IDE support is not very good, a red prompt will appear, but the compilation is OK. It only for the fields in the primary constructor
- Example - Example
...@@ -94,7 +100,7 @@ val ret = TestClass1.builder().i(1).j(0).x("x").build() ...@@ -94,7 +100,7 @@ val ret = TestClass1.builder().i(1).j(0).x("x").build()
assert(ret.toString == "TestClass1(1,0,x,Some())") assert(ret.toString == "TestClass1(1,0,x,Some())")
``` ```
Compiler intermediate code: Compiler macro code:
```scala ```scala
object TestClass1 extends scala.AnyRef { object TestClass1 extends scala.AnyRef {
...@@ -138,7 +144,7 @@ object TestClass1 extends scala.AnyRef { ...@@ -138,7 +144,7 @@ object TestClass1 extends scala.AnyRef {
The `@synchronized` is a more convenient and flexible synchronous annotation. The `@synchronized` is a more convenient and flexible synchronous annotation.
- Note - Note
- `lockedName` The name of the custom lock obj, default is `this`. - `lockedName` The name of the custom lock obj, default is `this`.
- Support static and instance methods. - Support static and instance methods.
- Example - Example
...@@ -159,7 +165,7 @@ def getStr(k: Int): String = { ...@@ -159,7 +165,7 @@ def getStr(k: Int): String = {
} }
``` ```
Compiler intermediate code: Compiler macro code:
```scala ```scala
// Note that it will not judge whether synchronized already exists, so if synchronized already exists, it will be used twice. // Note that it will not judge whether synchronized already exists, so if synchronized already exists, it will be used twice.
...@@ -178,10 +184,7 @@ The `@log` does not use mixed or wrapper, but directly uses macro to generate de ...@@ -178,10 +184,7 @@ The `@log` does not use mixed or wrapper, but directly uses macro to generate de
- `io.github.dreamylost.LogType.JLog` use `java.util.logging.Logger` - `io.github.dreamylost.LogType.JLog` use `java.util.logging.Logger`
- `io.github.dreamylost.LogType.Log4j2` use `org.apache.logging.log4j.Logger` - `io.github.dreamylost.LogType.Log4j2` use `org.apache.logging.log4j.Logger`
- `io.github.dreamylost.LogType.Slf4j` use `org.slf4j.Logger` - `io.github.dreamylost.LogType.Slf4j` use `org.slf4j.Logger`
- Support `class`, `case class` and `object`. - Support `class`, `case class` and `object`.
> IDE support is not very good, a red prompt will appear, but the compilation is OK. You need to provide their dependencies and configuration, please refer to the test.
- Example - Example
...@@ -201,9 +204,6 @@ The `@apply` used to generate `apply` method for primary construction of ordinar ...@@ -201,9 +204,6 @@ The `@apply` used to generate `apply` method for primary construction of ordinar
- `verbose` Whether to enable detailed log. - `verbose` Whether to enable detailed log.
- Only support `class`. - Only support `class`.
- Only support **primary construction**. - Only support **primary construction**.
- Currying is not supported for constructors at present.
> IDE support is not very good, a red prompt will appear, but the compilation is OK. You need to provide their dependencies and configuration, please refer to the test.
- Example - Example
...@@ -212,6 +212,39 @@ The `@apply` used to generate `apply` method for primary construction of ordinar ...@@ -212,6 +212,39 @@ The `@apply` used to generate `apply` method for primary construction of ordinar
println(B2(1, 2)) println(B2(1, 2))
``` ```
## @constructor
The `@constructor` used to generate secondary constructor method for classes.
- Note
- `verbose` Whether to enable detailed log.
- `excludeFields` Whether to exclude the specified `var` fields, default is `Nil`.
- Only support `class`.
- Example
```scala
@constructor(excludeFields = Seq("c"))
class A2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L)) {
private val a: Int = 1
var b: Int = 1 //The default value of the field is not carried to the apply parameter, so all parameters are required.
protected var c: Int = _
def helloWorld: String = "hello world"
}
println(new A2(1, 2, None, None, 100))
```
Compiler macro code(Only constructor def):
```scala
def <init>(int: Int, j: Int, k: Option[String], t: Option[Long], b: Int) = {
<init>(int, j, k, t);
this.b = b
}
```
# How to use # How to use
Add library dependency Add library dependency
......
version in ThisBuild := "0.1.0-SNAPSHOT" version in ThisBuild := "0.0.5"
...@@ -18,4 +18,17 @@ object Main extends App { ...@@ -18,4 +18,17 @@ object Main extends App {
val s = new TestClass(1, 2).toString val s = new TestClass(1, 2).toString
println(s) println(s)
@toString(includeInternalFields = false, includeFieldNames = true)
@apply
@builder class A2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L)) {
private val a: Int = 1
var b: Int = 1
protected var c: Int = _
def helloWorld: String = "hello world"
}
println(A2(1, 2, None, None)) //use apply and toString
println(A2.builder().int(1).j(2).k(Option("hello")).t(None).build()) //use builder and toString
} }
...@@ -26,14 +26,29 @@ trait MacroCommon { ...@@ -26,14 +26,29 @@ trait MacroCommon {
* @param annottees * @param annottees
* @return Return ClassDef * @return Return ClassDef
*/ */
def checkAndReturnClass(c: whitebox.Context)(annottees: c.Expr[Any]*): c.universe.ClassDef = { def checkAndGetClassDef(c: whitebox.Context)(annottees: c.Expr[Any]*): c.universe.ClassDef = {
import c.universe._ import c.universe._
val annotateeClass: ClassDef = 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
case _ => c.abort(c.enclosingPosition, "Unexpected annottee. Only applicable to class definitions.") case _ => c.abort(c.enclosingPosition, "Unexpected annottee. Only applicable to class definitions.")
} }
annotateeClass }
/**
* 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
}
} }
/** /**
...@@ -43,12 +58,35 @@ trait MacroCommon { ...@@ -43,12 +58,35 @@ trait MacroCommon {
* @param annottees * @param annottees
* @return * @return
*/ */
def getCompanionObject(c: whitebox.Context)(annottees: c.Expr[Any]*): Option[c.universe.ModuleDef] = { def tryGetCompanionObject(c: whitebox.Context)(annottees: c.Expr[Any]*): Option[c.universe.ModuleDef] = {
import c.universe._ 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)
case _ => c.abort(c.enclosingPosition, "Unexpected annottee. Only applicable to class definitions.") case (compDecl: ModuleDef) :: Nil => Some(compDecl)
case _ => None
}
}
/**
* Wrap tree result with companion object.
* @param c
* @param resTree class
* @param annottees
* @return
*/
def treeResultWithCompanionObject(c: whitebox.Context)(resTree: c.Tree, annottees: c.Expr[Any]*): c.universe.Tree = {
import c.universe._
val companionOpt = tryGetCompanionObject(c)(annottees: _*)
if (companionOpt.isEmpty) {
resTree
} else {
val q"$mods object $obj extends ..$bases { ..$body }" = companionOpt.get
val companion = q"$mods object $obj extends ..$bases { ..$body }"
q"""
$resTree
$companion
"""
} }
} }
...@@ -90,23 +128,46 @@ trait MacroCommon { ...@@ -90,23 +128,46 @@ trait MacroCommon {
} }
/** /**
* Expand the constructor and get the field TermName * Expand the constructor and get the field TermName.
* *
* @param c * @param c
* @param field * @param field
* @return * @return
*/ */
def fieldTermNameMethod(c: whitebox.Context)(field: c.universe.Tree): c.universe.Tree = { def fieldTermName(c: whitebox.Context)(field: c.universe.Tree): c.universe.TermName = {
import c.universe._ import c.universe._
field match { field match {
case q"$mods val $tname: $tpt = $expr" => q"""$tname""" case q"$mods val $tname: $tpt = $expr" => tname.asInstanceOf[TermName]
case q"$mods var $tname: $tpt = $expr" => q"""$tname""" case q"$mods var $tname: $tpt = $expr" => tname.asInstanceOf[TermName]
} }
} }
/**
* Expand the constructor and get the field with assign.
* @param c
* @param annotteeClassParams
* @return
*/
def fieldAssignExpr(c: whitebox.Context)(annotteeClassParams: Seq[c.Tree]): Seq[c.Tree] = {
import c.universe._
annotteeClassParams.map {
case q"$mods var $tname: $tpt = $expr" => q"$tname: $tpt" //Ignore expr
case q"$mods val $tname: $tpt = $expr" => q"$tname: $tpt"
}
}
/**
* Modify companion objects.
*
* @param c
* @param compDeclOpt
* @param codeBlock
* @param className
* @return
*/
def modifiedCompanion(c: whitebox.Context)( def modifiedCompanion(c: whitebox.Context)(
compDeclOpt: Option[c.universe.ModuleDef], compDeclOpt: Option[c.universe.ModuleDef],
apply: c.Tree, className: c.TypeName): c.universe.Tree = { codeBlock: c.Tree, className: c.TypeName): c.universe.Tree = {
import c.universe._ 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
...@@ -114,16 +175,29 @@ trait MacroCommon { ...@@ -114,16 +175,29 @@ trait MacroCommon {
q""" q"""
$mods object $obj extends ..$bases { $mods object $obj extends ..$bases {
..$body ..$body
..$apply ..$codeBlock
} }
""" """
c.info(c.enclosingPosition, s"modifiedCompanion className: $className, exists obj: $o", force = true) c.info(c.enclosingPosition, s"modifiedCompanion className: $className, exists obj: $o", force = true)
o o
} getOrElse { } getOrElse {
// Create a companion object with the builder // Create a companion object with the builder
val o = q"object ${className.toTermName} { ..$apply }" val o = q"object ${className.toTermName} { ..$codeBlock }"
c.info(c.enclosingPosition, s"modifiedCompanion className: $className, new obj: $o", force = true) c.info(c.enclosingPosition, s"modifiedCompanion className: $className, new obj: $o", force = true)
o o
} }
} }
/**
* Extract the internal fields of members belonging to the class, but not in primary constructor.
*
* @param c
*/
def getClassMemberValDef(c: whitebox.Context)(annotteeClassDefinitions: Seq[c.Tree]): Seq[c.Tree] = {
import c.universe._
annotteeClassDefinitions.filter(p => p match {
case _: ValDef => true
case _ => false
})
}
} }
...@@ -31,23 +31,24 @@ object applyMacro extends MacroCommon { ...@@ -31,23 +31,24 @@ object applyMacro extends MacroCommon {
c.info(c.enclosingPosition, s"annottees: $annottees, args: $args", force = args) c.info(c.enclosingPosition, s"annottees: $annottees, args: $args", force = args)
val annotateeClass: ClassDef = checkAndReturnClass(c)(annottees: _*) val annotateeClass: ClassDef = checkAndGetClassDef(c)(annottees: _*)
val isCase: Boolean = isCaseClass(c)(annotateeClass) val isCase: Boolean = isCaseClass(c)(annotateeClass)
c.info(c.enclosingPosition, s"impl argument: $args, isCase: $isCase", force = args) c.info(c.enclosingPosition, s"impl argument: $args, isCase: $isCase", force = args)
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, annotteeClassParams) = classDecl match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$bases { ..$body }" => case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$bases { ..$body }" =>
c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = args) c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = args)
(tpname, paramss) (tpname, paramss)
case _ => c.abort(c.enclosingPosition, s"Annotation is only supported on class. classDef: $classDecl") case _ => c.abort(c.enclosingPosition, s"Annotation is only supported on class. classDef: $classDecl")
} }
c.info(c.enclosingPosition, s"modifiedDeclaration compDeclOpt: $compDeclOpt, fields: $fields", force = args) c.info(c.enclosingPosition, s"modifiedDeclaration compDeclOpt: $compDeclOpt, annotteeClassParams: $annotteeClassParams", force = args)
val fieldNames = fields.asInstanceOf[List[List[Tree]]].flatten.map(f => fieldTermNameMethod(c)(f)) val fieldNames = annotteeClassParams.asInstanceOf[List[List[Tree]]].flatten.map(f => fieldTermName(c)(f))
val cName = className match { val cName = className match {
case t: TypeName => t case t: TypeName => t
} }
val compDecl = modifiedCompanion(c)(compDeclOpt, q"""def apply(...$fields): $className = new $className(..$fieldNames)""", cName) val annotteeClassParamsOnlyAssignExpr = fieldAssignExpr(c)(annotteeClassParams.asInstanceOf[List[List[Tree]]].flatten)
val compDecl = modifiedCompanion(c)(compDeclOpt, q"""def apply(..$annotteeClassParamsOnlyAssignExpr): $className = new $className(..$fieldNames)""", cName)
c.Expr( c.Expr(
q""" q"""
$classDecl $classDecl
......
...@@ -43,7 +43,7 @@ object builderMacro extends MacroCommon { ...@@ -43,7 +43,7 @@ object builderMacro extends MacroCommon {
} }
} }
def fieldDefinitionMethod(c: whitebox.Context)(field: c.universe.Tree): c.universe.Tree = { def fieldDefinition(c: whitebox.Context)(field: c.universe.Tree): c.universe.Tree = {
import c.universe._ import c.universe._
field match { field match {
case tree @ q"$mods val $tname: $tpt = $expr" => q"""private var $tname: $tpt = $expr""" case tree @ q"$mods val $tname: $tpt = $expr" => q"""private var $tname: $tpt = $expr"""
...@@ -51,19 +51,11 @@ object builderMacro extends MacroCommon { ...@@ -51,19 +51,11 @@ object builderMacro extends MacroCommon {
} }
} }
def fieldTermNameMethod(c: whitebox.Context)(field: c.universe.Tree): c.universe.Tree = {
import c.universe._
field match {
case tree @ q"$mods val $tname: $tpt = $expr" => q"""$tname"""
case tree @ q"$mods var $tname: $tpt = $expr" => q"""$tname"""
}
}
def builderTemplate(typeName: TypeName, fields: List[Tree], isCase: Boolean): c.universe.Tree = { def builderTemplate(typeName: TypeName, fields: List[Tree], isCase: Boolean): c.universe.Tree = {
val termName = typeName.toTermName.toTermName val termName = typeName.toTermName.toTermName
val builderFieldMethods = fields.map(f => fieldSetMethod(c)(f)) val builderFieldMethods = fields.map(f => fieldSetMethod(c)(f))
val builderFieldDefinitions = fields.map(f => fieldDefinitionMethod(c)(f)) val builderFieldDefinitions = fields.map(f => fieldDefinition(c)(f))
val allFieldsTermName = fields.map(f => fieldTermNameMethod(c)(f)) val allFieldsTermName = fields.map(f => fieldTermName(c)(f))
q""" q"""
def builder(): Builder = new Builder() def builder(): Builder = new Builder()
......
package io.github.dreamylost
import scala.annotation.{ StaticAnnotation, compileTimeOnly }
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
/**
* annotation to generate secondary constructor method for classes.
*
* @author 梦境迷离
* @param verbose Whether to enable detailed log.
* @param excludeFields Whether to exclude the specified var fields.
* @since 2021/7/3
* @version 1.0
*/
@compileTimeOnly("enable macro to expand macro annotations")
final class constructor(
verbose: Boolean = false,
excludeFields: Seq[String] = Nil
) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro constructorMacro.impl
}
object constructorMacro extends MacroCommon {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val args = c.prefix.tree match {
case q"new constructor(verbose=$verbose)" => (c.eval[Boolean](c.Expr(verbose)), Nil)
case q"new constructor(excludeFields=$excludeFields)" => (false, c.eval[Seq[String]](c.Expr(excludeFields)))
case q"new constructor(verbose=$verbose, excludeFields=$excludeFields)" => (c.eval[Boolean](c.Expr(verbose)), c.eval[Seq[String]](c.Expr(excludeFields)))
case q"new constructor()" => (false, Nil)
case _ => c.abort(c.enclosingPosition, "unexpected annotation pattern!")
}
val annotateeClass: ClassDef = checkAndGetClassDef(c)(annottees: _*)
val isCase: Boolean = isCaseClass(c)(annotateeClass)
if (isCase) {
c.abort(c.enclosingPosition, s"Annotation is not supported on case class. classDef: $annotateeClass")
}
c.info(c.enclosingPosition, s"annottees: $annottees, annotateeClass: $annotateeClass", args._1)
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 }" =>
c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = args._1)
(paramss, stats.asInstanceOf[Seq[Tree]])
case _ => c.abort(c.enclosingPosition, s"Annotation is only supported on class. classDef: $classDecl")
}
// Extract the field of the primary constructor.
val annotteeClassParamsOnlyAssignExpr = fieldAssignExpr(c)(annotteeClassParams.asInstanceOf[List[List[Tree]]].flatten)
// Extract the internal fields of members belonging to the class, but not in primary constructor.
val annotteeClassFieldDefinitions = getClassMemberValDef(c)(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[c.Tree] = {
import c.universe._
getClassMemberValDef(c)(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 $tname: $tpt = $expr" => q"$tname: $tpt"
}
}
val annotteeClassFieldDefinitionsOnlyAssignExpr = getClassMemberVarDefOnlyAssignExpr()
if (annotteeClassFieldDefinitionsOnlyAssignExpr.isEmpty) {
c.abort(c.enclosingPosition, s"Annotation is only supported on class when the internal field (declare as 'var') is nonEmpty. classDef: $classDecl")
}
val annotteeClassFieldNames = annotteeClassFieldDefinitions.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]
}
c.info(c.enclosingPosition, s"modifiedDeclaration compDeclOpt: $compDeclOpt, annotteeClassParams: $annotteeClassParams", force = args._1)
// not suppport currying
val ctorFieldNames = annotteeClassParams.asInstanceOf[List[List[Tree]]].flatten.map(f => fieldTermName(c)(f))
def getConstructorTemplate(): c.universe.Tree = {
q"""
def this(..${annotteeClassParamsOnlyAssignExpr ++ annotteeClassFieldDefinitionsOnlyAssignExpr}){
this(..$ctorFieldNames)
..${annotteeClassFieldNames.map(f => q"this.$f = $f")}
}
"""
}
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.:+(getConstructorTemplate())} }"
}
c.Expr[Any](treeResultWithCompanionObject(c)(resTree, annottees: _*))
}
val resTree = handleWithImplType(c)(annottees: _*)(modifiedDeclaration)
printTree(c)(force = args._1, resTree.tree)
resTree
}
}
...@@ -107,15 +107,25 @@ object logMacro extends MacroCommon { ...@@ -107,15 +107,25 @@ object logMacro extends MacroCommon {
case (classDef @ q"$mods object $tpname extends { ..$earlydefns } with ..$parents { $self => ..$stats }") :: _ => case (classDef @ 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 }" :: _ =>
q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${List(logTree) ::: stats.toList} }" val resTree = 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} }"
//we should return with class def. Even if we didn't change it, but the context class was not passed in.
// val annotateeClassOpt: Option[ClassDef] = getClassDef(c)(annottees: _*)
// if(annotateeClassOpt.isEmpty){
// resTree
// } else {
// q"""
// ${annotateeClassOpt.get}
// $resTree
// """
// }
} }
printTree(c)(force = args._1, resTree) printTree(c)(force = args._1, resTree)
......
package io.github.dreamylost package io.github.dreamylost
import io.github.dreamylost.constructorMacro.treeResultWithCompanionObject
import scala.annotation.{ StaticAnnotation, compileTimeOnly } import scala.annotation.{ StaticAnnotation, compileTimeOnly }
import scala.language.experimental.macros import scala.language.experimental.macros
import scala.reflect.macros.whitebox import scala.reflect.macros.whitebox
...@@ -101,7 +103,7 @@ object stringMacro extends MacroCommon { ...@@ -101,7 +103,7 @@ object stringMacro extends MacroCommon {
case tree: Tree => Some(tree) // TODO type check better case tree: Tree => Some(tree) // TODO type check better
case _ => None case _ => None
} }
superClassDef.fold(toString)(sc => { superClassDef.fold(toString)(_ => {
val superClass = q"${"super="}" val superClass = q"${"super="}"
c.info(c.enclosingPosition, s"member: $member, superClass: $superClass, superClassDef: $superClassDef, paramsWithName: $paramsWithName", force = argument.verbose) 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)" q"override def toString: String = StringContext(${className.toString()} + ${"("} + $superClass, ${if (member.nonEmpty) ", " else ""}+$paramsWithName + ${")"}).s(super.toString)"
...@@ -125,13 +127,15 @@ object stringMacro extends MacroCommon { ...@@ -125,13 +127,15 @@ object stringMacro extends MacroCommon {
case q"new toString($aa, $bb, $cc, $dd)" => (c.eval[Boolean](c.Expr(aa)), c.eval[Boolean](c.Expr(bb)), c.eval[Boolean](c.Expr(cc)), c.eval[Boolean](c.Expr(dd))) case q"new toString($aa, $bb, $cc, $dd)" => (c.eval[Boolean](c.Expr(aa)), c.eval[Boolean](c.Expr(bb)), c.eval[Boolean](c.Expr(cc)), c.eval[Boolean](c.Expr(dd)))
case q"new toString(includeInternalFields=$bb, includeFieldNames=$cc)" => (false, c.eval[Boolean](c.Expr(bb)), c.eval[Boolean](c.Expr(cc)), false) case q"new toString(includeInternalFields=$bb, includeFieldNames=$cc)" => (false, c.eval[Boolean](c.Expr(bb)), c.eval[Boolean](c.Expr(cc)), false)
case q"new toString(includeInternalFields=$bb)" => (false, c.eval[Boolean](c.Expr(bb)), true, false)
case q"new toString(includeFieldNames=$cc)" => (false, true, c.eval[Boolean](c.Expr(cc)), false)
case q"new toString()" => (false, true, true, false) case q"new toString()" => (false, true, true, false)
case _ => c.abort(c.enclosingPosition, "unexpected annotation pattern!") case _ => c.abort(c.enclosingPosition, "unexpected annotation pattern!")
} }
val argument = Argument(arg._1, arg._2, arg._3, arg._4) val argument = Argument(arg._1, arg._2, arg._3, arg._4)
c.info(c.enclosingPosition, s"toString annottees: $annottees", force = argument.verbose) 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 // Check the type of the class, which can only be defined on the ordinary class
val annotateeClass: ClassDef = checkAndReturnClass(c)(annottees: _*) val annotateeClass: ClassDef = checkAndGetClassDef(c)(annottees: _*)
val isCase: Boolean = isCaseClass(c)(annotateeClass) val isCase: Boolean = isCaseClass(c)(annotateeClass)
c.info(c.enclosingPosition, s"impl argument: $argument, isCase: $isCase", force = argument.verbose) c.info(c.enclosingPosition, s"impl argument: $argument, isCase: $isCase", force = argument.verbose)
...@@ -140,17 +144,8 @@ object stringMacro extends MacroCommon { ...@@ -140,17 +144,8 @@ object stringMacro extends MacroCommon {
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.:+(resMethod)} }" q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${stats.toList.:+(resMethod)} }"
} }
val companionOpt = getCompanionObject(c)(annottees: _*)
val res = if (companionOpt.isEmpty) { val res = treeResultWithCompanionObject(c)(resTree, annottees: _*)
resTree
} else {
val q"$mods object $obj extends ..$bases { ..$body }" = companionOpt.get
val companion = q"$mods object $obj extends ..$bases { ..$body }"
q"""
$resTree
$companion
"""
}
printTree(c)(argument.verbose, res) printTree(c)(argument.verbose, res)
c.Expr[Any](res) c.Expr[Any](res)
} }
......
...@@ -19,21 +19,20 @@ class ApplyTest extends AnyFlatSpec with Matchers { ...@@ -19,21 +19,20 @@ class ApplyTest extends AnyFlatSpec with Matchers {
"""@toString @apply class A(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))""" should compile """@toString @apply class A(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))""" should compile
@toString @toString
@apply class A2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L)) @apply class A2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))
println(A2(1, 2)) println(A2(1, 2, None, None))
"""@apply @toString class B(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))""" should compile """@apply @toString class B(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))""" should compile
@apply @apply
@toString class B2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L)) @toString class B2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))
println(B2(1, 2)) println(B2(1, 2, None, None))
// exists object // exists object
"""@apply @toString class B(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L));object B3""" should compile """@apply @toString class B(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L));object B3""" should compile
@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))
object B3 object B3
println(B3(1, 2)) println(B3(1, 2, None, None))
} }
"apply2" should "failed at class" in { "apply2" should "failed at class" in {
// FAILED, not support currying!! // FAILED, not support currying!!
"""@apply @toString class C(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
......
package io.github.dreamylost
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
/**
*
* @author 梦境迷离
* @since 2021/7/3
* @version 1.0
*/
class ConstructorTest extends AnyFlatSpec with Matchers {
"constructor1" should "failed at object" in {
""" @constructor
| object A2 {
| private val a: Int = 1
| var b: Int = 1
| def helloWorld: String = "hello world"
| }""".stripMargin shouldNot compile
""" @apply @toString @builder @constructor(excludeFields=Seq("c"))
| class A2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L)) {
| private val a: Int = 1
| var b: Int = 1
| protected var c: Int = _
|
| def helloWorld: String = "hello world"
| }
| A2(1, 2, None, None).c
| """.stripMargin shouldNot compile
}
"constructor2" should "ok at class" in {
""" @constructor(verbose = true)
| class A2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L)) {
| private val a: Int = 1
| private var b: Int = 1
| protected var c: Int = _
|
| def helloWorld: String = "hello world"
| }""".stripMargin should compile
""" @constructor
| class A2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L)) {
| private val a: Int = 1
| var b: Int = 1
| protected var c: Int = _
|
| def helloWorld: String = "hello world"
| }""".stripMargin should compile
""" @apply @builder @toString
| class A2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L)) {
| private val a: Int = 1
| var b: Int = 1
| protected var c: Int = _
|
| def helloWorld: String = "hello world"
| }""".stripMargin should compile
""" @apply @builder @toString @constructor
| class A2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L)) {
| private val a: Int = 1
| var b: Int = 1
| protected var c: Int = _
|
| def helloWorld: String = "hello world"
| }""".stripMargin should compile
""" @builder @apply @toString @constructor
| class A2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L)) {
| private val a: Int = 1
| var b: Int = 1
| protected var c: Int = _
|
| def helloWorld: String = "hello world"
| }""".stripMargin should compile
""" @builder @toString @apply @constructor
| class A2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L)) {
| private val a: Int = 1
| var b: Int = 1
| protected var c: Int = _
|
| def helloWorld: String = "hello world"
| }""".stripMargin should compile
""" @apply @toString @builder @constructor
| class A2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L)) {
| private val a: Int = 1
| var b: Int = 1
| protected var c: Int = _
|
| def helloWorld: String = "hello world"
| }""".stripMargin should compile
""" @apply @toString @builder @constructor(excludeFields=Seq("c"))
| class A2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L)) {
| private val a: Int = 1
| var b: Int = 1
| protected var c: Int = _
|
| def helloWorld: String = "hello world"
| }""".stripMargin should compile
}
"constructor3" should "failed at object" in {
@apply
@toString
@builder
@constructor(excludeFields = Seq("c"))
class A2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L)) {
private val a: Int = 1
var b: Int = 1
protected var c: Int = _
def helloWorld: String = "hello world"
}
println(A2(1, 2, None, Some(12L)))
println(A2.builder().int(1).j(2).build())
println(new A2(1, 2, None, None, 100))
}
}
...@@ -13,8 +13,8 @@ class LogTest extends AnyFlatSpec with Matchers { ...@@ -13,8 +13,8 @@ class LogTest extends AnyFlatSpec with Matchers {
"log1" should "ok on class" in { "log1" should "ok on class" in {
"""@log(verbose=true) class TestClass1(val i: Int = 0, var j: Int) { """@log(verbose=true) class TestClass1(val i: Int = 0, var j: Int) {
log.info("hello") log.info("hello")
}""" should compile }""" should compile
"""@log class TestClass2(val i: Int = 0, var j: Int)""" should compile """@log class TestClass2(val i: Int = 0, var j: Int)""" should compile
"""@log() class TestClass3(val i: Int = 0, var j: Int)""" should compile """@log() class TestClass3(val i: Int = 0, var j: Int)""" should compile
...@@ -25,8 +25,8 @@ class LogTest extends AnyFlatSpec with Matchers { ...@@ -25,8 +25,8 @@ class LogTest extends AnyFlatSpec with Matchers {
"log2" should "ok on case class" in { "log2" should "ok on case class" in {
"""@log(verbose=true) case class TestClass1(val i: Int = 0, var j: Int) { """@log(verbose=true) case class TestClass1(val i: Int = 0, var j: Int) {
log.info("hello") log.info("hello")
}""" should compile }""" should compile
"""@log case class TestClass2(val i: Int = 0, var j: Int)""" 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() case class TestClass3(val i: Int = 0, var j: Int)""" should compile
...@@ -37,8 +37,8 @@ class LogTest extends AnyFlatSpec with Matchers { ...@@ -37,8 +37,8 @@ class LogTest extends AnyFlatSpec with Matchers {
"log3" should "ok on object" in { "log3" should "ok on object" in {
"""@log(verbose=true) object TestClass1 { """@log(verbose=true) object TestClass1 {
log.info("hello") log.info("hello")
}""" should compile }""" should compile
"""@log object TestClass2""" should compile """@log object TestClass2""" should compile
"""@log() object TestClass3""" should compile """@log() object TestClass3""" should compile
...@@ -49,8 +49,8 @@ class LogTest extends AnyFlatSpec with Matchers { ...@@ -49,8 +49,8 @@ class LogTest extends AnyFlatSpec with Matchers {
"log4 log4j2" should "ok on object" in { "log4 log4j2" should "ok on object" in {
"""@log(verbose=true) object TestClass1 { """@log(verbose=true) object TestClass1 {
log.info("hello") log.info("hello")
}""" should compile }""" should compile
"""@log object TestClass2""" should compile """@log object TestClass2""" should compile
"""@log() object TestClass3""" should compile """@log() object TestClass3""" should compile
...@@ -61,8 +61,8 @@ class LogTest extends AnyFlatSpec with Matchers { ...@@ -61,8 +61,8 @@ class LogTest extends AnyFlatSpec with Matchers {
"log5 slf4j" should "ok on object" in { "log5 slf4j" should "ok on object" in {
"""@log(verbose=true) object TestClass1 { """@log(verbose=true) object TestClass1 {
log.info("hello") log.info("hello")
}""" should compile }""" should compile
"""@log object TestClass2""" should compile """@log object TestClass2""" should compile
"""@log() object TestClass3""" should compile """@log() object TestClass3""" should compile
...@@ -73,8 +73,8 @@ class LogTest extends AnyFlatSpec with Matchers { ...@@ -73,8 +73,8 @@ class LogTest extends AnyFlatSpec with Matchers {
"log6 log4j2" should "ok on class" in { "log6 log4j2" should "ok on class" in {
"""@log(verbose=true) class TestClass1(val i: Int = 0, var j: Int) { """@log(verbose=true) class TestClass1(val i: Int = 0, var j: Int) {
log.info("hello") log.info("hello")
}""" should compile }""" should compile
"""@log class TestClass2(val i: Int = 0, var j: Int)""" should compile """@log class TestClass2(val i: Int = 0, var j: Int)""" should compile
"""@log() class TestClass3(val i: Int = 0, var j: Int)""" should compile """@log() class TestClass3(val i: Int = 0, var j: Int)""" should compile
...@@ -85,8 +85,8 @@ class LogTest extends AnyFlatSpec with Matchers { ...@@ -85,8 +85,8 @@ class LogTest extends AnyFlatSpec with Matchers {
"log7 slf4j" should "ok on class" in { "log7 slf4j" should "ok on class" in {
"""@log(verbose=true) class TestClass1(val i: Int = 0, var j: Int) { """@log(verbose=true) class TestClass1(val i: Int = 0, var j: Int) {
log.info("hello") log.info("hello")
}""" should compile }""" should compile
"""@toString @builder @log class TestClass2(val i: Int = 0, var j: Int)""" should compile //Use with multiple annotations """@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 """@log() class TestClass3(val i: Int = 0, var j: Int)""" should compile
...@@ -96,4 +96,62 @@ class LogTest extends AnyFlatSpec with Matchers { ...@@ -96,4 +96,62 @@ class LogTest extends AnyFlatSpec with Matchers {
"""@log(verbose=true, logType=io.github.dreamylost.LogType.Slf4j) class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }""" should compile """@log(verbose=true, logType=io.github.dreamylost.LogType.Slf4j) class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }""" should compile
"""@log(io.github.dreamylost.LogType.Slf4j) class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }""" should compile //default verbose is false """@log(io.github.dreamylost.LogType.Slf4j) class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }""" should compile //default verbose is false
} }
"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
"""@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
"""@log(verbose=true) class TestClass4(val i: Int = 0, var j: Int)""" should compile
"""@log(logType=io.github.dreamylost.LogType.Slf4j) class TestClass5(val i: Int = 0, var j: Int)""" should compile
"""@log(verbose=true, logType=io.github.dreamylost.LogType.Slf4j) class TestClass6(val i: Int = 0, var j: Int)""" should compile
"""@log(verbose=true, logType=io.github.dreamylost.LogType.Slf4j) class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }""" should compile
"""@log(io.github.dreamylost.LogType.Slf4j) @builder class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }
| @log(io.github.dreamylost.LogType.Slf4j) object TestClass6 { log.info("hello world");builder() }""".stripMargin should compile //default verbose is false
}
"log9 slf4j" should "ok on class and it object" in {
"""
|@log(io.github.dreamylost.LogType.Slf4j) @builder class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }
|@log(io.github.dreamylost.LogType.Slf4j) object TestClass6 { log.info("hello world"); builder()}
|""".stripMargin should compile
}
"log10 slf4j" should "failed on case class and it object" in {
"""
| @log(io.github.dreamylost.LogType.Slf4j)
| @builder case class TestClass6(val i: Int = 0, var j: Int) {
| log.info("hello world")
| }
| @log(logType = io.github.dreamylost.LogType.Slf4j) object TestClass6 {
| log.info("hello world"); builder()
| }
|""".stripMargin shouldNot compile //The context of class was not passed in object macro
}
"log11 slf4j" should "ok on class and it object" in {
"""
| @log(io.github.dreamylost.LogType.Slf4j)
| @builder class TestClass6(val i: Int = 0, var j: Int) {
| log.info("hello world")
| }
| @log(logType = io.github.dreamylost.LogType.Slf4j) object TestClass6 {
| log.info("hello world"); builder()
| }
|""".stripMargin should compile
"""
| @builder
| @log(io.github.dreamylost.LogType.Slf4j)
| class TestClass6(val i: Int = 0, var j: Int) {
| log.info("hello world")
| }
| @log(logType = io.github.dreamylost.LogType.Slf4j) object TestClass6 {
| log.info("hello world"); builder()
| }
|""".stripMargin should compile
}
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册