提交 6d2eb149 编写于 作者: 梦境迷离's avatar 梦境迷离

pre

上级 0bd530ec
......@@ -7,9 +7,10 @@ scala macro and abstract syntax tree learning code.
- Argument
- `verbose` Whether to enable detailed log.
- `withFieldName` Whether to include the name of the field in the toString.
- `containsCtorParams` Whether to include the fields of the primary constructor.
- `withInternalField` Whether to include the fields defined within a class.
- Support `case class` and `class`.
- source code1
- Example
```scala
class TestClass(val i: Int = 0, var j: Int) {
......@@ -18,33 +19,10 @@ class TestClass(val i: Int = 0, var j: Int) {
var x: String = "world"
}
case class TestClass2(i: Int = 0, var j: Int) // No method body, only have primary constructor.
println(new TestClass(1, 2));
```
- when withFieldName=false containsCtorParams=false
```
println(new TestClass(1, 2))
TestClass(0, hello, world)
```
- when withFieldName=false containsCtorParams=true
```
println(new TestClass(1, 2))
TestClass(1, 2, 0, hello, world)
```
- when withFieldName=true containsCtorParams=false
```
println(new TestClass(1, 2))
TestClass(y=0, z=hello, x=world)
```
- when withFieldName=true containsCtorParams=true
```
println(new TestClass(1, 2))
TestClass(i=1, j=2, y=0, z=hello, x=world)
```
\ No newline at end of file
| withInternalField / withFieldName | 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)```|
\ No newline at end of file
version in Scope.ThisScope := "0.1-SNAPSHOT"
version in Scope.ThisScope := "0.0.1-SNAPSHOT"
package io.github.liguobin
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.annotation.{ StaticAnnotation, compileTimeOnly }
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
......@@ -9,23 +9,23 @@ import scala.reflect.macros.whitebox
*
* @author 梦境迷离
* @param verbose Whether to enable detailed log.
* @param containsCtorParams Whether to include the fields of the primary constructor.
* @param withInternalField Whether to include the fields defined within a class.
* @param withFieldName Whether to include the name of the field in the toString.
* @since 2021/6/13
* @version 1.0
*/
@compileTimeOnly("enable macro to expand macro annotations")
class toString(
verbose: Boolean = false,
containsCtorParams: Boolean = true,
withFieldName: Boolean = true
) extends StaticAnnotation {
final class toString(
verbose: Boolean = false,
withInternalField: Boolean = true,
withFieldName: Boolean = true
) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro stringMacro.impl
}
case class Argument(verbose: Boolean, containsCtorParams: Boolean, withFieldName: Boolean)
case class Argument(verbose: Boolean, withInternalField: Boolean, withFieldName: Boolean)
object stringMacro {
......@@ -35,9 +35,9 @@ object stringMacro {
if (argument.withFieldName) {
lastParam.fold(q"$field") { lp =>
field match {
case tree@q"$mods var $tname: $tpt = $expr" =>
case tree @ q"$mods var $tname: $tpt = $expr" =>
if (tname.toString() != lp) q"""${tname.toString()}+${"="}+this.$tname+${", "}""" else q"""${tname.toString()}+${"="}+this.$tname"""
case tree@q"$mods val $tname: $tpt = $expr" =>
case tree @ q"$mods val $tname: $tpt = $expr" =>
if (tname.toString() != lp) q"""${tname.toString()}+${"="}+this.$tname+${", "}""" else q"""${tname.toString()}+${"="}+this.$tname"""
case _ => q"$field"
}
......@@ -45,9 +45,9 @@ object stringMacro {
} else {
lastParam.fold(q"$field") { lp =>
field match {
case tree@q"$mods var $tname: $tpt = $expr" => if (tname.toString() != lp) q"""$tname+${", "}""" else q"""$tname"""
case tree@q"$mods val $tname: $tpt = $expr" => if (tname.toString() != lp) q"""$tname+${", "}""" else q"""$tname"""
case _ => if (field.toString() != lp) q"""$field+${", "}""" else q"""$field"""
case tree @ q"$mods var $tname: $tpt = $expr" => if (tname.toString() != lp) q"""$tname+${", "}""" else q"""$tname"""
case tree @ q"$mods val $tname: $tpt = $expr" => if (tname.toString() != lp) q"""$tname+${", "}""" else q"""$tname"""
case _ => if (field.toString() != lp) q"""$field+${", "}""" else q"""$field"""
}
}
......@@ -72,23 +72,21 @@ object stringMacro {
c.abort(mem.pos, "'toString' method has already defined, please remove it or not use'@toString'")
}
false
case m: DefDef =>
false
case _ => false
})
// For the parameters of a given constructor, separate the parameter components and extract the constructor parameters containing val and var
val ctorParams = annotteeClassParams.asInstanceOf[List[List[Tree]]].flatten.map {
case tree@q"$mods val $tname: $tpt = $expr" => tree
case tree@q"$mods var $tname: $tpt = $expr" => tree
case tree @ q"$mods val $tname: $tpt = $expr" => tree
case tree @ q"$mods var $tname: $tpt = $expr" => tree
}
c.info(c.enclosingPosition, s"className: $className, ctorParams: ${ctorParams.toString()}", force = true)
c.info(c.enclosingPosition, s"className: $className, fields: ${annotteeClassFieldDefinitions.toString()}", force = true)
val member = if (argument.containsCtorParams) ctorParams ++ annotteeClassFieldDefinitions else annotteeClassFieldDefinitions
val member = if (argument.withInternalField) ctorParams ++ annotteeClassFieldDefinitions else ctorParams
val lastParam = member.lastOption.map {
case v: ValDef => v.name.toTermName.decodedName.toString
case c => c.toString
case c => c.toString
}
val paramsWithName = member.foldLeft(q"${""}")((res, acc) => q"$res + ${printField(c)(argument, lastParam, acc)}")
//scala/bug https://github.com/scala/bug/issues/3967 not be 'Foo(i=1,j=2)' in standard library
......@@ -98,16 +96,17 @@ object stringMacro {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
// extract parameters of annotation
// extract 'isVerbose' parameters of annotation
val arg = c.prefix.tree match {
case q"new toString($aa, $bb, $cc)" => (c.eval[Boolean](c.Expr(aa)), c.eval[Boolean](c.Expr(bb)), c.eval[Boolean](c.Expr(cc)))
case q"new toString(withInternalField=$bb, withFieldName=$cc)" => (false, c.eval[Boolean](c.Expr(bb)), c.eval[Boolean](c.Expr(cc)))
case q"new toString()" => (false, true, true)
case _ => c.abort(c.enclosingPosition, "unexpected annotation pattern!")
}
val argument = Argument(arg._1, arg._2, arg._3)
// Check the type of the class, which can only be defined on the ordinary class
val annotateeClass: ClassDef = annottees.map(_.tree).toList match {
case (claz: ClassDef) :: Nil => claz
case _ => c.abort(c.enclosingPosition, "Unexpected annottee. Only applicable to class definitions.")
case _ => c.abort(c.enclosingPosition, "Unexpected annottee. Only applicable to class definitions.")
}
val isCase: Boolean = {
annotateeClass match {
......@@ -119,7 +118,7 @@ object stringMacro {
}
}
c.info(c.enclosingPosition, s"impl argument: $argument, isCase: $isCase", true)
c.info(c.enclosingPosition, s"impl argument: $argument, isCase: $isCase", force = true)
val resMethod = toStringTemplateImpl(c)(argument, annotateeClass)
val resTree = annotateeClass match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
......
package io.github.liguobin
import org.scalatest.{FlatSpec, Matchers}
import org.scalatest.{ FlatSpec, Matchers }
/**
*
......@@ -10,7 +10,7 @@ import org.scalatest.{FlatSpec, Matchers}
*/
class ToStringTest extends FlatSpec with Matchers {
"toString1" should "not contains constructors parameters" in {
"toString1" should "not contains internal field" in {
@toString(false, false, false)
class TestClass(val i: Int = 0, var j: Int) {
val y: Int = 0
......@@ -19,10 +19,10 @@ class ToStringTest extends FlatSpec with Matchers {
}
val s = new TestClass(1, 2).toString
println(s)
assert(s == "TestClass(0, hello, world)")
assert(s == "TestClass(1, 2)")
}
"toString2" should "contains constructors parameters" in {
"toString2" should "contains internal field and with name" in {
@toString(true, true, true)
class TestClass(val i: Int = 0, var j: Int) {
val y: Int = 0
......@@ -34,7 +34,7 @@ class ToStringTest extends FlatSpec with Matchers {
assert(s == "TestClass(i=1, j=2, y=0, z=hello, x=world)")
}
"toString3" should "not contains constructors parameters but with name" in {
"toString3" should "not contains internal field but with name" in {
@toString(true, false, true)
class TestClass(val i: Int = 0, var j: Int) {
val y: Int = 0
......@@ -43,10 +43,10 @@ class ToStringTest extends FlatSpec with Matchers {
}
val s = new TestClass(1, 2).toString
println(s)
assert(s == "TestClass(y=0, z=hello, x=world)")
assert(s == "TestClass(i=1, j=2)")
}
"toString4" should "contains constructors parameters but without name" in {
"toString4" should "contains internal field but without name" in {
@toString(true, true, false)
class TestClass(val i: Int = 0, var j: Int) {
val y: Int = 0
......@@ -67,10 +67,10 @@ class ToStringTest extends FlatSpec with Matchers {
}
val s = TestClass(1, 2).toString
println(s)
assert(s == "TestClass(0, hello, world)")
assert(s == "TestClass(1, 2)")
}
"toString6" should "case class with name" in {
"toString6" should "case class not contains internal field and with name" in {
@toString(true, false, true)
case class TestClass(i: Int = 0, var j: Int) {
val y: Int = 0
......@@ -83,11 +83,11 @@ class ToStringTest extends FlatSpec with Matchers {
println(s)
println(s2)
assert(s == "TestClass(y=0, z=hello, x=world)")
assert(s == "TestClass(i=1, j=2)")
assert(s2 == "TestClass2(1,2)")
}
"toString7" should "case class with name" in {
"toString7" should "case class contains internal field and with name" in {
@toString(true, true, true)
case class TestClass(i: Int = 0, var j: Int) {
val y: Int = 0
......@@ -99,7 +99,7 @@ class ToStringTest extends FlatSpec with Matchers {
assert(s == "TestClass(i=1, j=2, y=0, z=hello, x=world)")
}
"toString8" should "case class with name and itself" in {
"toString8" should "case class contains internal field and with name, itself" in {
@toString(true, true, true)
case class TestClass(i: Int = 0, var j: Int, k: TestClass) {
val y: Int = 0
......@@ -111,7 +111,7 @@ class ToStringTest extends FlatSpec with Matchers {
assert(s == "TestClass(i=1, j=2, k=TestClass(i=1, j=2, k=null, y=0, z=hello, x=world), y=0, z=hello, x=world)")
}
"toString9" should "case class with name and itself2" in {
"toString9" should "case class contains internal field and with name, itself2" in {
@toString(true, true, true)
case class TestClass(i: Int = 0, var j: Int) {
val y: Int = 0
......@@ -124,7 +124,7 @@ class ToStringTest extends FlatSpec with Matchers {
assert(s == "TestClass(i=1, j=2, y=0, z=hello, x=world, t=null)")
}
"toString10" should "case class with name and itself3" in {
"toString10" should "case class contains internal field with name, itself3" in {
@toString(true, true, true)
case class TestClass(i: Int = 0, var j: Int, k: TestClass) {
val y: Int = 0
......@@ -155,7 +155,7 @@ class ToStringTest extends FlatSpec with Matchers {
class TestClass(i: Int = 0, var j: Int)
val s = new TestClass(1, 2).toString
println(s)
assert(s == "TestClass()")
assert(s == "TestClass(i=1, j=2)")
@toString(true, true, true)
class TestClass2(i: Int = 1, var j: Int = 2)
......@@ -181,7 +181,7 @@ class ToStringTest extends FlatSpec with Matchers {
case class TestClass2(i: Int = 1, var j: Int = 3)
val s2 = TestClass2(1, 2).toString
println(s2)
assert(s2 == "TestClass2()")
assert(s2 == "TestClass2(1, 2)")
@toString(true, true, true)
case class TestClass3(i: Int = 1, var j: Int = 3)
......@@ -189,4 +189,40 @@ class ToStringTest extends FlatSpec with Matchers {
println(s3)
assert(s3 == "TestClass3(i=1, j=2)")
}
"toString14" should "empty class and with default params" in {
@toString()
case class TestClass1()
val s1 = TestClass1().toString
println(s1)
assert(s1 == "TestClass1()")
@toString(true, false, false)
case class TestClass2()
val s2 = TestClass2().toString
println(s2)
assert(s2 == "TestClass2()")
}
"toString15" should "super param not find" in {
@toString()
class TestClass1(val i: Int)
@toString(withInternalField = true, withFieldName = true)
case class TestClass2() extends TestClass1(1)
val s1 = TestClass2().toString
println(s1)
assert(s1 == "TestClass2()") //TODO not support println super fields
@toString(withInternalField = true, withFieldName = true)
case class TestClass3(j: Int) extends TestClass1(j)
val s2 = TestClass3(0).toString
println(s2)
assert(s2 == "TestClass3(j=0)")
@toString(withInternalField = true, withFieldName = true)
class TestClass4(j: Int) extends TestClass1(j)
val s3 = new TestClass4(0).toString
println(s3)
assert(s3 == "TestClass4(j=0)")
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册