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

let primitive types and string can be omitted, support generic (#61)

* primitive types and string can be omitted
* support generic
`@apply`
`@builder`
`@constructor`
上级 c421a353
......@@ -5,15 +5,34 @@
[![Version](https://img.shields.io/jetbrains/plugin/v/17202-scala-macro-tools)](https://plugins.jetbrains.com/plugin/17202-scala-macro-tools)
我写该库的动机
该库的目的
--
学习Scala宏编程(macro)和抽象语法树(ast)。
> 本项目目前处于实验阶段,有建议、意见或者问题欢迎提issue。如果本项目对你有帮助,欢迎点个star。
[中文说明](./README.md) | [English](./README_EN.md)
**[中文说明](./README.md) | [English](./README_EN.md)**
# 我能学到什么
- Scala2 宏编程
- 了解Scala反射
- 熟悉插值语法的应用,了解Scala Spec
- 了解Scala AST相关的基本使用
- 知道如何编写宏注解(宏拓展)
- 知道如何使用宏生成对象,类,方法,字段
- 其他
- 知道劝退人的SBT如何优雅配置,诸如发布到云仓库,多版本构建,多模块项目
- 了解如何利用Intellij-Scala插件编写自己的Scala插件
- 了解Intellij是怎样支持语法提示的,插件工作流程
- 知道如何写好scalatest单元测试
- 类似的其他思考
# 环境
- 使用 Java 8, 11 编译通过
- 使用 Scala 2.11.x ~ 2.13.x 编译通过
# 功能
......@@ -27,226 +46,7 @@
> 涉及到交互操作的注解在IDEA插件中都得到了支持。在插件市场中搜索`Scala-Macro-Tools`可下载。
## 已知问题
- 不支持泛型。
- `@constructor``@toString`同时使用,必须放最后。
## @toString
`@toString`注解用于为Scala类生成`toString`方法。
- 说明
- `verbose` 指定是否开启详细编译日志。可选,默认`false`
- `includeFieldNames` 指定是否在`toString`中包含字段的名称。可选,默认`true`
- `includeInternalFields` 指定是否包含类内部定义的字段。它们不是在主构造函数中。可选,默认`true`
- `callSuper` 指定是否包含`super``toString`方法值。如果超级类是一种特质,则不支持。可选,默认`false`
- 支持普通类和样例类。
- 示例
```scala
@toString class TestClass(val i: Int = 0, var j: Int) {
val y: Int = 0
var z: String = "hello"
var x: String = "world"
}
println(new TestClass(1, 2));
```
| includeInternalFields / includeFieldNames | false | true |
| ----------------------------------------- | -------------------------------------- | ------------------------------------------------ |
| false | ```TestClass(1, 2)``` | ```TestClass(i=0, j=2)``` |
| true | ```TestClass(1, 2, 0, hello, world)``` | ```TestClass(i=1, j=2, y=0, z=hello, x=world)``` |
## @json
`@json`注解是向Play项目的样例类添加json format对象的最快方法。
- 说明
- 此注释启发来自[json-annotation](https://github.com/kifi/json-annotation),并做了优化,现在它可以与其他注解同时使用。
- 只有一个隐式的`val`值会被自动生成(如果伴生对象不存在的话,还会生成一个伴生对象用于存放该隐式值),此外没有其他的操作。
- 示例
```scala
@json case class Person(name: String, age: Int)
```
现在,您可以使用Play的转化方法序列化或反序列化对象:
```scala
import play.api.libs.json._
val person = Person("Victor Hugo", 46)
val json = Json.toJson(person)
Json.fromJson[Person](json)
```
## @builder
`@builder`注解用于为Scala类生成构造器模式。
- 说明
- 支持普通类和样例类。
- 仅支持对主构造函数使用。
- 如果该类没有伴生对象,将生成一个伴生对象来存储`builder`方法和类。
- 示例
```scala
@builder
case class TestClass1(val i: Int = 0, var j: Int, x: String, o: Option[String] = Some(""))
val ret = TestClass1.builder().i(1).j(0).x("x").build()
assert(ret.toString == "TestClass1(1,0,x,Some())")
```
宏生成的中间代码:
```scala
object TestClass1 extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
def builder(): TestClass1Builder = new TestClass1Builder();
class TestClass1Builder extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
private var i: Int = 0;
private var j: Int = _;
private var x: String = _;
private var o: Option[String] = Some("");
def i(i: Int): TestClass1Builder = {
this.i = i;
this
};
def j(j: Int): TestClass1Builder = {
this.j = j;
this
};
def x(x: String): TestClass1Builder = {
this.x = x;
this
};
def o(o: Option[String]): TestClass1Builder = {
this.o = o;
this
};
def build(): TestClass1 = TestClass1(i, j, x, o)
}
}
```
## @synchronized
`@synchronized`注解是一个更方便、更灵活的用于同步方法的注解。
- 说明
- `lockedName` 指定自定义的锁对象的名称。可选,默认`this`
- 支持静态方法(`object`中的函数)和实例方法(`class`中的函数)。
- 示例
```scala
private final val obj = new Object
@synchronized(lockedName = "obj") // 如果您填写一个不存在的字段名,编译将失败。
def getStr3(k: Int): String = {
k + ""
}
// 或者
@synchronized //使用 this 作为锁对象
def getStr(k: Int): String = {
k + ""
}
```
宏生成的中间代码:
```scala
// 注意,它不会判断synchronized是否已经存在,因此如果synchronized已经存在,它将被使用两次。如下
// `def getStr(k: Int): String = this.synchronized(this.synchronized(k.$plus("")))
// 目前还不确定是否在字节码级别会被优化。
def getStr(k: Int): String = this.synchronized(k.$plus(""))
```
## @log
`@log`注解不使用混入和包装,而是直接使用宏生成默认的log对象来操作log。
- 说明
- `verbose` 指定是否开启详细编译日志。可选,默认`false`
- `logType` 指定需要生成的`log`的类型。可选,默认`JLog`
- `io.github.dreamylost.logs.LogType.JLog` 使用 `java.util.logging.Logger`
- `io.github.dreamylost.logs.LogType.Log4j2` 使用 `org.apache.logging.log4j.Logger`
- `io.github.dreamylost.logs.LogType.Slf4j` 使用 `org.slf4j.Logger`
- 支持普通类,样例类,单例对象。
- 示例
```scala
@log(verbose = true) class TestClass1(val i: Int = 0, var j: Int) {
log.info("hello")
}
@log(verbose=true, logType=io.github.dreamylost.LogType.Slf4j) class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }
```
## @apply
`@apply`注解用于为普通类的主构造函数生成`apply`方法。
- 说明
- `verbose` 指定是否开启详细编译日志。可选,默认`false`
- 仅支持在`class`上使用且仅支持主构造函数。
- 示例
```scala
@apply @toString class B2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))
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
}
```
[各个注解的说明](./docs/howToUse.md)
# 如何使用
......@@ -258,19 +58,7 @@ def <init>(int: Int, j: Int, k: Option[String], t: Option[Long], b: Int) = {
"io.github.jxnu-liguobin" %% "scala-macro-tools" % "<VERSION>"
```
该库已发布到maven中央仓库,请使用最新版本。
| Library Version | Scala 2.11 | Scala 2.12 | Scala 2.13 |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0.1.0 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.1.0)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.1.0/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.1.0)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.1.0/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.1.0)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.1.0/jar) |
| 0.0.6 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.6)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.6/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.6)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.6/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.6)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.6/jar) |
| 0.0.5 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.5)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.5/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.5)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.5/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.5)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.5/jar) |
| 0.0.4 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.4)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.4/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.4)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.4/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.4)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.4/jar) |
| 0.0.3 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.3)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.3/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.3)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.3/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.3)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.3/jar) |
| 0.0.2 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.2)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.2/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.2)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.2/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.2)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.2/jar) |
| 0.0.1 | - | - | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.1)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.1/jar) |
仅将本库导入构建系统(例如gradle、sbt)是不够的。你需要多走一步。
该库已发布到maven中央仓库,请使用最新版本。仅将本库导入构建系统(例如gradle、sbt)是不够的。你需要多走一步。
| Scala 2.11 | Scala 2.12 | Scala 2.13 |
| ------------------------ | ------------------------ | ------------------------------------- |
......
......@@ -13,6 +13,11 @@ Learn Scala macro and abstract syntax tree.
[中文说明](./README.md) | [English](./README_EN.md)
# Environment
- It is compiled in Java 8, 11
- It is compiled in Scala 2.11.x ~ 2.13.x
# Features
- `@toString`
......@@ -25,229 +30,7 @@ Learn Scala macro and abstract syntax tree.
> Annotations involving interaction are supported in the idea plug-in (named `Scala-Macro-Tools` in Marketplace).
## Known Issues
- Generic is not supported.
- When `@constructor` and `@toString` are used together, the former must be put last.
## @toString
The `@toString` used to generate `toString` for Scala classes or a `toString` with parameter names for the case classes.
- Note
- `verbose` Whether to enable detailed log.
- `includeFieldNames` Whether to include the names of the field in the `toString`, 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.
- Support `case class` and `class`.
- Example
```scala
@toString class TestClass(val i: Int = 0, var j: Int) {
val y: Int = 0
var z: String = "hello"
var x: String = "world"
}
println(new TestClass(1, 2));
```
| includeInternalFields / includeFieldNames | false | true |
| ----------------------------------------- | -------------------------------------- | ------------------------------------------------ |
| false | ```TestClass(1, 2)``` | ```TestClass(i=0, j=2)``` |
| true | ```TestClass(1, 2, 0, hello, world)``` | ```TestClass(i=1, j=2, y=0, z=hello, x=world)``` |
## @json
The `@json` scala macro annotation is the quickest way to add a JSON format to your Play project's case classes.
- Note
- This annotation is drawn from [json-annotation](https://github.com/kifi/json-annotation) and have some
optimization.
- 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
operations.
- Example
```scala
@json case class Person(name: String, age: Int)
```
You can now serialize/deserialize your objects using Play's convenience methods:
```scala
import play.api.libs.json._
val person = Person("Victor Hugo", 46)
val json = Json.toJson(person)
Json.fromJson[Person](json)
```
## @builder
The `@builder` used to generate builder pattern for Scala classes.
- Note
- Support `case class` / `class`.
- Only support for **primary constructor**.
- If there is no companion object, one will be generated to store the `builder` class and method.
- Example
```scala
@builder
case class TestClass1(val i: Int = 0, var j: Int, x: String, o: Option[String] = Some(""))
val ret = TestClass1.builder().i(1).j(0).x("x").build()
assert(ret.toString == "TestClass1(1,0,x,Some())")
```
Compiler macro code:
```scala
object TestClass1 extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
def builder(): TestClass1Builder = new TestClass1Builder();
class TestClass1Builder extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
private var i: Int = 0;
private var j: Int = _;
private var x: String = _;
private var o: Option[String] = Some("");
def i(i: Int): TestClass1Builder = {
this.i = i;
this
};
def j(j: Int): TestClass1Builder = {
this.j = j;
this
};
def x(x: String): TestClass1Builder = {
this.x = x;
this
};
def o(o: Option[String]): TestClass1Builder = {
this.o = o;
this
};
def build(): TestClass1 = TestClass1(i, j, x, o)
}
}
```
## @synchronized
The `@synchronized` is a more convenient and flexible synchronous annotation.
- Note
- `lockedName` The name of the custom lock obj, default is `this`.
- Support static and instance methods.
- Example
```scala
private final val obj = new Object
@synchronized(lockedName = "obj") // The default is this. If you fill in a non existent field name, the compilation will fail.
def getStr3(k: Int): String = {
k + ""
}
// or
@synchronized //use this
def getStr(k: Int): String = {
k + ""
}
```
Compiler macro code:
```scala
// Note that it will not judge whether synchronized already exists, so if synchronized already exists, it will be used twice.
// For example `def getStr(k: Int): String = this.synchronized(this.synchronized(k.$plus("")))
// It is not sure whether it will be optimized at the bytecode level.
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.
- Note
- `verbose` Whether to enable detailed log.
- `logType` Specifies the type of `log` that needs to be generated, default is `io.github.dreamylost.LogType.JLog`.
- `io.github.dreamylost.logs.LogType.JLog` use `java.util.logging.Logger`
- `io.github.dreamylost.logs.LogType.Log4j2` use `org.apache.logging.log4j.Logger`
- `io.github.dreamylost.logs.LogType.Slf4j` use `org.slf4j.Logger`
- Support `class`, `case class` and `object`.
- Example
```scala
@log(verbose = true) class TestClass1(val i: Int = 0, var j: Int) {
log.info("hello")
}
@log(verbose=true, logType=io.github.dreamylost.LogType.Slf4j) class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }
```
## @apply
The `@apply` used to generate `apply` method for primary construction of ordinary classes.
- Note
- `verbose` Whether to enable detailed log.
- Only support `class`.
- Only support **primary construction**.
- Example
```scala
@apply @toString class B2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))
println(B2(1, 2))
```
## @constructor
The `@constructor` used to generate secondary constructor method for classes, only when it has inner fields.
- Note
- `verbose` Whether to enable detailed log.
- `excludeFields` Whether to exclude the specified `var` fields, default is `Nil`.
- Only support `class`.
- The inner fields are placed in the first bracket block if constructor is currying.
- 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
}
```
**[Description of each annotation](./docs/howToUse_en.md)**
# How to use
......@@ -257,19 +40,7 @@ Add library dependency
"io.github.jxnu-liguobin" %% "scala-macro-tools" % "<VERSION>"
```
The artefacts have been uploaded to Maven Central.
| Library Version | Scala 2.11 | Scala 2.12 | Scala 2.13 |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0.1.0 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.1.0)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.1.0/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.1.0)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.1.0/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.1.0)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.1.0/jar) |
| 0.0.6 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.6)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.6/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.6)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.6/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.6)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.6/jar) |
| 0.0.5 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.5)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.5/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.5)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.5/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.5)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.5/jar) |
| 0.0.4 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.4)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.4/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.4)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.4/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.4)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.4/jar) |
| 0.0.3 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.3)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.3/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.3)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.3/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.3)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.3/jar) |
| 0.0.2 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.2)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.2/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.2)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.2/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.2)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.2/jar) |
| 0.0.1 | - | - | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.1)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.1/jar) |
Importing the library into your build system (e.g gradle, sbt), is not enough. You need to follow an extra step.
The artefacts have been uploaded to Maven Central. Importing the library into your build system (e.g gradle, sbt), is not enough. You need to follow an extra step.
| Scala 2.11 | Scala 2.12 | Scala 2.13 |
| ---------------------------- | ---------------------------- | --------------------------------------------------- |
......
## @toString
`@toString`注解用于为Scala类生成`toString`方法。
- 说明
- `verbose` 指定是否开启详细编译日志。可选,默认`false`
- `includeFieldNames` 指定是否在`toString`中包含字段的名称。可选,默认`true`
- `includeInternalFields` 指定是否包含类内部定义的字段。它们不是在主构造函数中。可选,默认`true`
- `callSuper` 指定是否包含`super``toString`方法值。如果超级类是一种特质,则不支持。可选,默认`false`
- 支持普通类和样例类。
- 示例
```scala
@toString class TestClass(val i: Int = 0, var j: Int) {
val y: Int = 0
var z: String = "hello"
var x: String = "world"
}
println(new TestClass(1, 2));
```
| includeInternalFields / includeFieldNames | false | true |
| ----------------------------------------- | -------------------------------------- | ------------------------------------------------ |
| false | ```TestClass(1, 2)``` | ```TestClass(i=0, j=2)``` |
| true | ```TestClass(1, 2, 0, hello, world)``` | ```TestClass(i=1, j=2, y=0, z=hello, x=world)``` |
## @json
`@json`注解是向Play项目的样例类添加json format对象的最快方法。
- 说明
- 此注释启发来自[json-annotation](https://github.com/kifi/json-annotation),并做了优化,现在它可以与其他注解同时使用。
- 只有一个隐式的`val`值会被自动生成(如果伴生对象不存在的话,还会生成一个伴生对象用于存放该隐式值),此外没有其他的操作。
- 示例
```scala
@json case class Person(name: String, age: Int)
```
现在,您可以使用Play的转化方法序列化或反序列化对象:
```scala
import play.api.libs.json._
val person = Person("Victor Hugo", 46)
val json = Json.toJson(person)
Json.fromJson[Person](json)
```
## @builder
`@builder`注解用于为Scala类生成构造器模式。
- 说明
- 支持普通类和样例类。
- 仅支持对主构造函数使用。
- 如果该类没有伴生对象,将生成一个伴生对象来存储`builder`方法和类。
- 示例
```scala
@builder
case class TestClass1(val i: Int = 0, var j: Int, x: String, o: Option[String] = Some(""))
val ret = TestClass1.builder().i(1).j(0).x("x").build()
assert(ret.toString == "TestClass1(1,0,x,Some())")
```
宏生成的中间代码:
```scala
object TestClass1 extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
def builder(): TestClass1Builder = new TestClass1Builder();
class TestClass1Builder extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
private var i: Int = 0;
private var j: Int = _;
private var x: String = _;
private var o: Option[String] = Some("");
def i(i: Int): TestClass1Builder = {
this.i = i;
this
};
def j(j: Int): TestClass1Builder = {
this.j = j;
this
};
def x(x: String): TestClass1Builder = {
this.x = x;
this
};
def o(o: Option[String]): TestClass1Builder = {
this.o = o;
this
};
def build(): TestClass1 = TestClass1(i, j, x, o)
}
}
```
## @synchronized
`@synchronized`注解是一个更方便、更灵活的用于同步方法的注解。
- 说明
- `lockedName` 指定自定义的锁对象的名称。可选,默认`this`
- 支持静态方法(`object`中的函数)和实例方法(`class`中的函数)。
- 示例
```scala
private final val obj = new Object
@synchronized(lockedName = "obj") // 如果您填写一个不存在的字段名,编译将失败。
def getStr3(k: Int): String = {
k + ""
}
// 或者
@synchronized //使用 this 作为锁对象
def getStr(k: Int): String = {
k + ""
}
```
宏生成的中间代码:
```scala
// 注意,它不会判断synchronized是否已经存在,因此如果synchronized已经存在,它将被使用两次。如下
// `def getStr(k: Int): String = this.synchronized(this.synchronized(k.$plus("")))
// 目前还不确定是否在字节码级别会被优化。
def getStr(k: Int): String = this.synchronized(k.$plus(""))
```
## @log
`@log`注解不使用混入和包装,而是直接使用宏生成默认的log对象来操作log。
- 说明
- `verbose` 指定是否开启详细编译日志。可选,默认`false`
- `logType` 指定需要生成的`log`的类型。可选,默认`JLog`
- `io.github.dreamylost.logs.LogType.JLog` 使用 `java.util.logging.Logger`
- `io.github.dreamylost.logs.LogType.Log4j2` 使用 `org.apache.logging.log4j.Logger`
- `io.github.dreamylost.logs.LogType.Slf4j` 使用 `org.slf4j.Logger`
- 支持普通类,样例类,单例对象。
- 示例
```scala
@log(verbose = true) class TestClass1(val i: Int = 0, var j: Int) {
log.info("hello")
}
@log(verbose=true, logType=io.github.dreamylost.LogType.Slf4j) class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }
```
## @apply
`@apply`注解用于为普通类的主构造函数生成`apply`方法。
- 说明
- `verbose` 指定是否开启详细编译日志。可选,默认`false`
- 仅支持在`class`上使用且仅支持主构造函数。
- 示例
```scala
@apply @toString class B2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))
println(B2(1, 2, None, None)) //0.1.0,不携带字段的默认值到apply参数中,所以参数都是必传
```
## @constructor
`@constructor`注解用于为普通类生成辅助构造函数。仅当类有内部字段时可用。
- 说明
- `verbose` 指定是否开启详细编译日志。可选,默认`false`
- `excludeFields` 指定是否需要排除不需要用于构造函数的`var`字段。可选,默认空(所有class内部的`var`字段都将作为构造函数的入参)。
- 仅支持在`class`上使用。
- 主构造函数存在柯里化时,内部字段被放置在柯里化的第一个括号块中。(生成的仍然是柯里化的辅助构造)
- 内部字段的类型需要显示指定,否则宏拓展无法获取到该类型。目前支持为基本类型和字符串实现省略。如`var i = 1; var j: Int = 1; var k: Object = new Object()`都是可以的,而`var k = new Object()`是不可以的。
- 示例
```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
}
```
\ No newline at end of file
## @toString
The `@toString` used to generate `toString` for Scala classes or a `toString` with parameter names for the case classes.
- Note
- `verbose` Whether to enable detailed log.
- `includeFieldNames` Whether to include the names of the field in the `toString`, 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.
- Support `case class` and `class`.
- Example
```scala
@toString class TestClass(val i: Int = 0, var j: Int) {
val y: Int = 0
var z: String = "hello"
var x: String = "world"
}
println(new TestClass(1, 2));
```
| includeInternalFields / includeFieldNames | false | true |
| ----------------------------------------- | -------------------------------------- | ------------------------------------------------ |
| false | ```TestClass(1, 2)``` | ```TestClass(i=0, j=2)``` |
| true | ```TestClass(1, 2, 0, hello, world)``` | ```TestClass(i=1, j=2, y=0, z=hello, x=world)``` |
## @json
The `@json` scala macro annotation is the quickest way to add a JSON format to your Play project's case classes.
- Note
- This annotation is drawn from [json-annotation](https://github.com/kifi/json-annotation) and have some
optimization.
- 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
operations.
- Example
```scala
@json case class Person(name: String, age: Int)
```
You can now serialize/deserialize your objects using Play's convenience methods:
```scala
import play.api.libs.json._
val person = Person("Victor Hugo", 46)
val json = Json.toJson(person)
Json.fromJson[Person](json)
```
## @builder
The `@builder` used to generate builder pattern for Scala classes.
- Note
- Support `case class` / `class`.
- Only support for **primary constructor**.
- If there is no companion object, one will be generated to store the `builder` class and method.
- Example
```scala
@builder
case class TestClass1(val i: Int = 0, var j: Int, x: String, o: Option[String] = Some(""))
val ret = TestClass1.builder().i(1).j(0).x("x").build()
assert(ret.toString == "TestClass1(1,0,x,Some())")
```
Compiler macro code:
```scala
object TestClass1 extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
def builder(): TestClass1Builder = new TestClass1Builder();
class TestClass1Builder extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
private var i: Int = 0;
private var j: Int = _;
private var x: String = _;
private var o: Option[String] = Some("");
def i(i: Int): TestClass1Builder = {
this.i = i;
this
};
def j(j: Int): TestClass1Builder = {
this.j = j;
this
};
def x(x: String): TestClass1Builder = {
this.x = x;
this
};
def o(o: Option[String]): TestClass1Builder = {
this.o = o;
this
};
def build(): TestClass1 = TestClass1(i, j, x, o)
}
}
```
## @synchronized
The `@synchronized` is a more convenient and flexible synchronous annotation.
- Note
- `lockedName` The name of the custom lock obj, default is `this`.
- Support static and instance methods.
- Example
```scala
private final val obj = new Object
@synchronized(lockedName = "obj") // The default is this. If you fill in a non existent field name, the compilation will fail.
def getStr3(k: Int): String = {
k + ""
}
// or
@synchronized //use this
def getStr(k: Int): String = {
k + ""
}
```
Compiler macro code:
```scala
// Note that it will not judge whether synchronized already exists, so if synchronized already exists, it will be used twice.
// For example `def getStr(k: Int): String = this.synchronized(this.synchronized(k.$plus("")))
// It is not sure whether it will be optimized at the bytecode level.
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.
- Note
- `verbose` Whether to enable detailed log.
- `logType` Specifies the type of `log` that needs to be generated, default is `io.github.dreamylost.LogType.JLog`.
- `io.github.dreamylost.logs.LogType.JLog` use `java.util.logging.Logger`
- `io.github.dreamylost.logs.LogType.Log4j2` use `org.apache.logging.log4j.Logger`
- `io.github.dreamylost.logs.LogType.Slf4j` use `org.slf4j.Logger`
- Support `class`, `case class` and `object`.
- Example
```scala
@log(verbose = true) class TestClass1(val i: Int = 0, var j: Int) {
log.info("hello")
}
@log(verbose=true, logType=io.github.dreamylost.LogType.Slf4j) class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }
```
## @apply
The `@apply` used to generate `apply` method for primary construction of ordinary classes.
- Note
- `verbose` Whether to enable detailed log.
- Only support `class`.
- Only support **primary construction**.
- Example
```scala
@apply @toString class B2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))
println(B2(1, 2))
```
## @constructor
The `@constructor` used to generate secondary constructor method for classes, only when it has internal fields.
- Note
- `verbose` Whether to enable detailed log.
- `excludeFields` Whether to exclude the specified `var` fields, default is `Nil`.
- Only support `class`.
- The internal fields are placed in the first bracket block if constructor is currying.
- The type of the internal field must be specified, otherwise the macro extension cannot get the type.
At present, only primitive types and string can be omitted. For example, `var i = 1; var j: int = 1; var k: Object = new Object()` is OK, but `var k = new object()` is not.
- 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
}
```
\ No newline at end of file
......@@ -133,7 +133,7 @@ trait MacroCommon {
* @return Return the result of modifyAction
*/
def handleWithImplType(c: whitebox.Context)(annottees: c.Expr[Any]*)
(modifyAction: (c.universe.ClassDef, Option[c.universe.ModuleDef]) => Any): c.Expr[Nothing] = {
(modifyAction: (c.universe.ClassDef, Option[c.universe.ModuleDef]) => Any): c.Expr[Nothing] = {
import c.universe._
annottees.map(_.tree) match {
case (classDecl: ClassDef) :: Nil => modifyAction(classDecl, None).asInstanceOf[c.Expr[Nothing]]
......@@ -202,7 +202,7 @@ trait MacroCommon {
*/
def modifiedCompanion(c: whitebox.Context)(
compDeclOpt: Option[c.universe.ModuleDef],
codeBlock: c.Tree, className: c.TypeName): c.universe.Tree = {
codeBlock: c.Tree, className: c.TypeName): c.universe.Tree = {
import c.universe._
compDeclOpt map { compDecl =>
val q"$mods object $obj extends ..$bases { ..$body }" = compDecl
......@@ -233,7 +233,7 @@ trait MacroCommon {
import c.universe._
annotteeClassDefinitions.filter(p => p match {
case _: ValDef => true
case _ => false
case _ => false
})
}
......@@ -270,18 +270,55 @@ trait MacroCommon {
* @return A apply method with currying.
* @example [[def apply(int: Int)(j: Int)(k: Option[String])(t: Option[Long]): B3 = new B3(int)(j)(k)(t)]]
*/
def getApplyMethodWithCurrying(c: whitebox.Context)(typeName: c.TypeName, fieldss: List[List[c.Tree]]): c.Tree = {
def getApplyMethodWithCurrying(c: whitebox.Context)(typeName: c.TypeName, fieldss: List[List[c.Tree]], classTypeParams: List[c.Tree]): c.Tree = {
import c.universe._
val allFieldsTermName = fieldss.map(f => fieldAssignExpr(c)(f))
val returnTypeParams = extractClassTypeParamsTypeName(c)(classTypeParams)
// not currying
val applyMethod = if (fieldss.isEmpty || fieldss.size == 1) {
q"def apply(..${allFieldsTermName.flatten}): $typeName = ${getConstructorWithCurrying(c)(typeName, fieldss, isCase = false)}"
q"def apply[..$classTypeParams](..${allFieldsTermName.flatten}): $typeName[..$returnTypeParams] = ${getConstructorWithCurrying(c)(typeName, fieldss, isCase = false)}"
} else {
// currying
val first = allFieldsTermName.head
q"def apply(..$first)(...${allFieldsTermName.tail}): $typeName = ${getConstructorWithCurrying(c)(typeName, fieldss, isCase = false)}"
q"def apply[..$classTypeParams](..$first)(...${allFieldsTermName.tail}): $typeName[..$returnTypeParams] = ${getConstructorWithCurrying(c)(typeName, fieldss, isCase = false)}"
}
c.info(c.enclosingPosition, s"getApplyWithCurrying constructor: $applyMethod, paramss: $fieldss", force = true)
applyMethod
}
/**
* Only for primitive types, we can get type and map to scala type.
*
* @param jType java type name
* @return Scala type name
*/
def toScalaType(jType: String): String = {
val types = Map(
"java.lang.Integer" -> "Int",
"java.lang.Long" -> "Long",
"java.lang.Double" -> "Double",
"java.lang.Float" -> "Float",
"java.lang.Short" -> "Short",
"java.lang.Byte" -> "Byte",
"java.lang.Boolean" -> "Boolean",
"java.lang.Character" -> "Char",
"java.lang.String" -> "String"
)
types.getOrElse(jType, jType)
}
/**
* Gets a list of generic parameters.
* This is because the generic parameters of a class cannot be used directly in the return type, and need to be converted.
*
* @param c
* @param tpParams
* @return
*/
def extractClassTypeParamsTypeName(c: whitebox.Context)(tpParams: List[c.Tree]): List[c.TypeName] = {
import c.universe._
tpParams.map {
case t: TypeDef => t.name
}
}
}
......@@ -26,17 +26,17 @@ object applyMacro extends MacroCommon {
if (isCase) c.abort(c.enclosingPosition, s"Annotation is only supported on 'case class'")
def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val (className, annotteeClassParams) = classDecl match {
val (className, classParams, classTypeParams) = classDecl match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$bases { ..$body }" =>
c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = args._1)
(tpname, paramss.asInstanceOf[List[List[Tree]]])
(tpname, paramss.asInstanceOf[List[List[Tree]]], tparams)
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
}
c.info(c.enclosingPosition, s"modifiedDeclaration compDeclOpt: $compDeclOpt, annotteeClassParams: $annotteeClassParams", force = args._1)
c.info(c.enclosingPosition, s"modifiedDeclaration compDeclOpt: $compDeclOpt, annotteeClassParams: $classParams", force = args._1)
val tpName = className match {
case t: TypeName => t
}
val apply = getApplyMethodWithCurrying(c)(tpName, annotteeClassParams)
val apply = getApplyMethodWithCurrying(c)(tpName, classParams, classTypeParams)
val compDecl = modifiedCompanion(c)(compDeclOpt, apply, tpName)
c.Expr(
q"""
......
......@@ -18,19 +18,20 @@ object builderMacro extends MacroCommon {
TypeName(classTree.toTermName.decodedName.toString + BUFFER_CLASS_NAME_SUFFIX)
}
def fieldSetMethod(typeName: TypeName, field: Tree): c.Tree = {
def fieldSetMethod(typeName: TypeName, field: Tree, classTypeParams: List[Tree]): c.Tree = {
val builderClassName = getBuilderClassName(typeName)
val returnTypeParams = extractClassTypeParamsTypeName(c)(classTypeParams)
field match {
case q"$mods var $tname: $tpt = $expr" =>
q"""
def $tname($tname: $tpt): ${builderClassName} = {
def $tname($tname: $tpt): $builderClassName[..$returnTypeParams] = {
this.$tname = $tname
this
}
"""
case q"$mods val $tname: $tpt = $expr" =>
q"""
def $tname($tname: $tpt): ${builderClassName} = {
def $tname($tname: $tpt): $builderClassName[..$returnTypeParams] = {
this.$tname = $tname
this
}
......@@ -45,32 +46,33 @@ object builderMacro extends MacroCommon {
}
}
def builderTemplate(typeName: TypeName, fieldss: List[List[Tree]], isCase: Boolean): Tree = {
def builderTemplate(typeName: TypeName, fieldss: List[List[Tree]], classTypeParams: List[Tree], isCase: Boolean): Tree = {
val fields = fieldss.flatten
val builderClassName = getBuilderClassName(typeName)
val builderFieldMethods = fields.map(f => fieldSetMethod(typeName, f))
val builderFieldMethods = fields.map(f => fieldSetMethod(typeName, f, classTypeParams))
val builderFieldDefinitions = fields.map(f => fieldDefinition(f))
val returnTypeParams = extractClassTypeParamsTypeName(c)(classTypeParams)
q"""
def builder(): $builderClassName = new $builderClassName()
def builder[..$classTypeParams](): $builderClassName[..$returnTypeParams] = new $builderClassName()
class $builderClassName {
class $builderClassName[..$classTypeParams] {
..$builderFieldDefinitions
..$builderFieldMethods
def build(): $typeName = ${getConstructorWithCurrying(c)(typeName, fieldss, isCase)}
def build(): $typeName[..$returnTypeParams] = ${getConstructorWithCurrying(c)(typeName, fieldss, isCase)}
}
"""
}
// Why use Any? The dependent type need aux-pattern in scala2. Now let's get around this.
def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val (className, fieldss) = classDecl match {
val (className, fieldss, classTypeParams) = classDecl match {
// @see https://scala-lang.org/files/archive/spec/2.13/05-classes-and-objects.html
case q"$mods class $tpname[..$tparams](...$paramss) extends ..$bases { ..$body }" =>
c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = true)
(tpname, paramss)
(tpname, paramss, tparams)
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
}
c.info(c.enclosingPosition, s"modifiedDeclaration compDeclOpt: $compDeclOpt, fieldss: $fieldss", force = true)
......@@ -79,7 +81,7 @@ object builderMacro extends MacroCommon {
case t: TypeName => t
}
val isCase = isCaseClass(c)(classDecl)
val builder = builderTemplate(cName, fieldss, isCase)
val builder = builderTemplate(cName, fieldss, classTypeParams, isCase)
val compDecl = modifiedCompanion(c)(compDeclOpt, builder, cName)
c.info(c.enclosingPosition, s"builderTree: $builder, compDecl: $compDecl", force = true)
// Return both the class and companion object declarations
......
......@@ -48,6 +48,9 @@ object constructorMacro extends MacroCommon {
case q"$mods var $tname: $tpt = $expr" if !excludeFields.contains(tname.asInstanceOf[TermName].decodedName.toString) => true
case _ => false
}).map {
case q"$mods var $pat = $expr" =>
// TODO getClass RETURN a java type, maybe we can try use class reflect to get the fields type name.
q"$pat: ${TypeName(toScalaType(evalTree(c)(expr.asInstanceOf[Tree]).getClass.getTypeName))}"
case q"$mods var $tname: $tpt = $expr" => q"$tname: $tpt"
}
}
......
......@@ -38,8 +38,19 @@ class ApplyTest extends AnyFlatSpec with Matchers {
"""@apply @toString class C(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))(o: Int = 1)""" shouldNot compile
}
"apply2" should "ok with currying" in {
"apply3" should "ok with currying" in {
@apply
@toString class B3(int: Int)(val j: Int)(var k: Option[String] = None)(t: Option[Long] = Some(1L))
}
"apply4" should "ok with generic" in {
@apply
@toString class B3[T, U](int: T)(val j: U)
println(B3(1)(2))
@toString
@apply class B4[T, U](int: T, val j: U)
println(B4("helloworld", 2))
}
}
......@@ -86,4 +86,19 @@ class BuilderTest extends AnyFlatSpec with Matchers {
class TestClass12(val i: Int = 0)(var j: Int)(val k: Int)
(val t: Option[String])
}
"builder9" should "ok with generic" in {
@builder
case class TestClass11[T](i: T)(var j: Int)(val k: Int)
(val t: Option[String])
val a = TestClass11.builder().i("hello generic").j(1).k(22).t(None).build()
println(a)
@builder
class TestClass12[T, U](val i: T, var j: U)
val b = TestClass12.builder().i(new AnyRef).j(List("helloworld", "generic is ok")).build()
println(b)
}
}
......@@ -105,7 +105,7 @@ class ConstructorTest extends AnyFlatSpec with Matchers {
| }""".stripMargin should compile
}
"constructor3" should "failed at object" in {
"constructor3" should "ok when construction is currying" in {
@apply
@toString
@builder
......@@ -141,4 +141,29 @@ class ConstructorTest extends AnyFlatSpec with Matchers {
*/
}
"constructor4" should "ok when type is omitted" in {
@toString
class B
@toString
@constructor
class TestClass12(val i: Int = 0)(var j: Int)(val k: Int) {
var b = "hello" //primitive type, support no type declared
var c: B = new B() //not support no type declared, `var c = new B ()` cannot be compiled.
}
val t = new TestClass12(1, "helloo", new B())(1)(1)
println(t)
}
"constructor5" should "ok when type is generic" in {
@toString
@constructor
class TestClass12[T, U](val i: U)(var j: Int)(val k: T) {
var b: List[T] = _
}
val t = new TestClass12(1, List("helloo"))(1)("1")
println(t)
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册