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

`@tostring` support callSuper (#13)

* support callSuper
* rename option
上级 18151b7e
......@@ -12,10 +12,11 @@ scala macro and abstract syntax tree learning code.
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.
- `withFieldName` Whether to include the names of the field in the `toString`.
- `withInternalField` Whether to include the fields defined within a class.
- Support `case class` and `class`.
- `verbose` Whether to enable detailed log.
- `includeFieldNames` Whether to include the names of the field in the `toString`.
- `includeInternalFields` Whether to include the fields defined within a class. Not in a primary constructor.
- `callSuper` Whether to include the super's `toString`. Not support if super class is a trait.
- Support `case class` and `class`.
- Example
......@@ -29,7 +30,7 @@ class TestClass(val i: Int = 0, var j: Int) {
println(new TestClass(1, 2));
```
|withInternalField / withFieldName| false |true
|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)```|
......@@ -67,7 +68,7 @@ The `@builder` used to generate builder pattern for Scala classes.
- Note
- Support `case class` / `class`.
- It can be used with `@toString`. But it needs to be put in the back.
- If there is no companion object, one will be generated to store the builder method and class.
- If there is no companion object, one will be generated to store the `builder` method and `Builder` class.
- IDE support is not very good, a red prompt will appear, but the compilation is OK.
- Example
......
......@@ -8,7 +8,7 @@ package io.github.dreamylost
*/
object Main extends App {
@toString(withInternalField = true, withFieldName = true)
@toString(includeInternalFields = true, includeFieldNames = true)
class TestClass(val i: Int = 0, var j: Int) {
val y: Int = 0
var z: String = "hello"
......
......@@ -8,7 +8,7 @@ package io.github.dreamylost
*/
object Main extends App {
@toString(withInternalField = true, withFieldName = true)
@toString(includeInternalFields = true, includeFieldNames = true)
class TestClass(val i: Int = 0, var j: Int) {
val y: Int = 0
var z: String = "hello"
......
......@@ -8,31 +8,33 @@ import scala.reflect.macros.whitebox
* toString for classes
*
* @author 梦境迷离
* @param verbose Whether to enable detailed log.
* @param withInternalField Whether to include the fields defined within a class.
* @param withFieldName Whether to include the name of the field in the toString.
* @param verbose Whether to enable detailed log.
* @param includeInternalFields Whether to include the fields defined within a class.
* @param includeFieldNames Whether to include the name of the field in the toString.
* @param callSuper Whether to include the super's toString.
* @since 2021/6/13
* @version 1.0
*/
@compileTimeOnly("enable macro to expand macro annotations")
final class toString(
verbose: Boolean = false,
withInternalField: Boolean = true,
withFieldName: Boolean = true
verbose: Boolean = false,
includeInternalFields: Boolean = true,
includeFieldNames: Boolean = true,
callSuper: Boolean = false
) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro stringMacro.impl
}
case class Argument(verbose: Boolean, withInternalField: Boolean, withFieldName: Boolean)
final case class Argument(verbose: Boolean, includeInternalFields: Boolean, includeFieldNames: Boolean, callSuper: Boolean)
object stringMacro {
def printField(c: whitebox.Context)(argument: Argument, lastParam: Option[String], field: c.universe.Tree): c.universe.Tree = {
import c.universe._
// Print one field as <name of the field>+"="+fieldName
if (argument.withFieldName) {
if (argument.includeFieldNames) {
lastParam.fold(q"$field") { lp =>
field match {
case tree @ q"$mods var $tname: $tpt = $expr" =>
......@@ -57,17 +59,18 @@ object stringMacro {
private def toStringTemplateImpl(c: whitebox.Context)(argument: Argument, annotateeClass: c.universe.ClassDef): c.universe.Tree = {
import c.universe._
// For a given class definition, separate the components of the class
val (className, annotteeClassParams, annotteeClassDefinitions) = {
val (className, annotteeClassParams, superClasses, annotteeClassDefinitions) = {
annotateeClass match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
(tpname, paramss, stats)
c.info(c.enclosingPosition, s"parents: $parents", force = argument.verbose)
(tpname, paramss, parents, stats)
}
}
// Check the type of the class, whether it already contains its own toString
val annotteeClassFieldDefinitions = annotteeClassDefinitions.asInstanceOf[List[Tree]].filter(p => p match {
case _: ValDef => true
case mem: MemberDef =>
c.info(c.enclosingPosition, s"MemberDef: ${mem.toString}", true)
c.info(c.enclosingPosition, s"MemberDef: ${mem.toString}", force = argument.verbose)
if (mem.toString().startsWith("override def toString")) {
c.abort(mem.pos, "'toString' method has already defined, please remove it or not use'@toString'")
}
......@@ -80,9 +83,9 @@ object stringMacro {
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.withInternalField) ctorParams ++ annotteeClassFieldDefinitions else ctorParams
c.info(c.enclosingPosition, s"className: $className, ctorParams: ${ctorParams.toString()}, superClasses: $superClasses", force = argument.verbose)
c.info(c.enclosingPosition, s"className: $className, fields: ${annotteeClassFieldDefinitions.toString()}", force = argument.verbose)
val member = if (argument.includeInternalFields) ctorParams ++ annotteeClassFieldDefinitions else ctorParams
val lastParam = member.lastOption.map {
case v: ValDef => v.name.toTermName.decodedName.toString
......@@ -90,20 +93,43 @@ object stringMacro {
}
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
q"""override def toString: String = ${className.toString()} + ${"("} + $paramsWithName + ${")"}"""
val toString = q"""override def toString: String = ${className.toString()} + ${"("} + $paramsWithName + ${")"}"""
// Have super class ?
if (argument.callSuper && superClasses.nonEmpty) {
val superClassDef = superClasses.head match {
case tree: Tree => Some(tree) // TODO type check better
case _ => None
}
superClassDef.fold(toString)(sc => {
val superClass = q"${"super="}"
c.info(c.enclosingPosition, s"member: $member, superClass: $superClass, superClassDef: $superClassDef, paramsWithName: $paramsWithName", force = argument.verbose)
q"override def toString: String = StringContext(${className.toString()} + ${"("} + $superClass, ${if (member.nonEmpty) ", " else ""}+$paramsWithName + ${")"}).s(super.toString)"
}
)
} else {
toString
}
}
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
// extract parameters of annotation
// extract parameters of annotation, must in order
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 q"new toString(includeInternalFields=$bb, includeFieldNames=$cc, callSuper=$dd)" => (false, 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)" => (c.eval[Boolean](c.Expr(aa)), c.eval[Boolean](c.Expr(bb)), c.eval[Boolean](c.Expr(cc)), false)
case q"new toString(verbose=$aa, includeInternalFields=$bb, includeFieldNames=$cc, callSuper=$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(verbose=$aa, includeInternalFields=$bb, includeFieldNames=$cc)" => (c.eval[Boolean](c.Expr(aa)), c.eval[Boolean](c.Expr(bb)), c.eval[Boolean](c.Expr(cc)), false)
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()" => (false, true, true, false)
case _ => c.abort(c.enclosingPosition, "unexpected annotation pattern!")
}
c.info(c.enclosingPosition, s"toString annottees: $annottees", true)
val argument = Argument(arg._1, arg._2, arg._3)
val argument = Argument(arg._1, arg._2, arg._3, arg._4)
c.info(c.enclosingPosition, s"toString annottees: $annottees", force = argument.verbose)
// Check the type of the class, which can only be defined on the ordinary class
val annotateeClass: ClassDef = annottees.map(_.tree).toList match {
case (classDecl: ClassDef) :: Nil => classDecl
......@@ -120,7 +146,7 @@ object stringMacro {
}
}
c.info(c.enclosingPosition, s"impl argument: $argument, isCase: $isCase", force = true)
c.info(c.enclosingPosition, s"impl argument: $argument, isCase: $isCase", force = argument.verbose)
val resMethod = toStringTemplateImpl(c)(argument, annotateeClass)
val resTree = annotateeClass match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
......
......@@ -204,25 +204,60 @@ class ToStringTest extends FlatSpec with Matchers {
assert(s2 == "TestClass2()")
}
"toString15" should "super param not find" in {
"toString15" should "non-contains super toString" in {
@toString()
class TestClass1(val i: Int)
@toString(withInternalField = true, withFieldName = true)
case class TestClass2() extends TestClass1(1)
@toString(verbose = true, includeInternalFields = true, includeFieldNames = true, callSuper = false)
case class TestClass2(j: Int = 1) extends TestClass1(1)
val s1 = TestClass2().toString
println(s1)
assert(s1 == "TestClass2()") //TODO not support println super fields
assert(s1 == "TestClass2(j=1)")
@toString(withInternalField = true, withFieldName = true)
@toString(includeInternalFields = true, includeFieldNames = 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)
@toString(includeInternalFields = true, includeFieldNames = true)
class TestClass4(j: Int) extends TestClass1(j)
val s3 = new TestClass4(0).toString
println(s3)
assert(s3 == "TestClass4(j=0)")
}
"toString16" should "contains super toString" in {
@toString()
class TestClass1(val i: Int)
@toString(verbose = true, includeInternalFields = true, includeFieldNames = true, callSuper = true)
case class TestClass2(j: Int = 1) extends TestClass1(1)
val s1 = TestClass2().toString
println(s1)
assert(s1 == "TestClass2(super=TestClass1(i=1), j=1)")
@toString(includeInternalFields = true, includeFieldNames = true, callSuper = true)
class TestClass4() extends TestClass1(1)
// StringContext("TestClass5(super=", ")").s(super.toString);
val s4 = new TestClass4().toString
println(s4)
assert(s4 == "TestClass4(super=TestClass1(i=1))")
@toString(includeInternalFields = false, includeFieldNames = true, callSuper = true)
class TestClass4_2() extends TestClass1(1)
val s4_2 = new TestClass4_2().toString
println(s4_2)
assert(s4_2 == "TestClass4_2(super=TestClass1(i=1))")
trait A {
val i: Int
}
@toString(includeInternalFields = true, includeFieldNames = false, callSuper = true)
class TestClass5 extends A {
override val i = 1
}
val s5 = new TestClass5().toString
println(s5)
// Because not support if super class is a trait
assert(s5.startsWith("TestClass5(super=io.github.dreamylost.ToStringTes") && s5.endsWith("1)"))
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册