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

Merge pull request #2 from jxnu-liguobin/dev

release
...@@ -7,9 +7,10 @@ scala macro and abstract syntax tree learning code. ...@@ -7,9 +7,10 @@ scala macro and abstract syntax tree learning code.
- Argument - Argument
- `verbose` Whether to enable detailed log. - `verbose` Whether to enable detailed log.
- `withFieldName` Whether to include the name of the field in the toString. - `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 ```scala
class TestClass(val i: Int = 0, var j: Int) { class TestClass(val i: Int = 0, var j: Int) {
...@@ -18,33 +19,10 @@ 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" 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 | withInternalField / withFieldName | false |true
| ---- | ---- |----|
``` |false|```TestClass(1, 2)``` |```TestClass(i=0, j=2)```|
println(new TestClass(1, 2)) |true|```TestClass(1, 2, 0, hello, world)```|```TestClass(i=1, j=2, y=0, z=hello, x=world)```|
TestClass(0, hello, world) \ No newline at end of file
```
- 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
version in Scope.ThisScope := "0.1-SNAPSHOT" version in ThisBuild := "0.0.2-SNAPSHOT"
import sbtrelease.ReleaseStateTransformations._
name := "scala-macro-tools" name := "scala-macro-tools"
scalaVersion := "2.13.6" scalaVersion := "2.13.6"
organization := "io.github.jxnu-liguobin" organization := "io.github.jxnu-liguobin"
...@@ -7,7 +9,7 @@ lazy val scala211 = "2.11.12" ...@@ -7,7 +9,7 @@ lazy val scala211 = "2.11.12"
lazy val scala213 = "2.13.6" lazy val scala213 = "2.13.6"
lazy val supportedScalaVersions = List(scala213, scala212, scala211) lazy val supportedScalaVersions = List(scala213, scala212, scala211)
lazy val core = (project in file(".")) lazy val root = (project in file("."))
.settings( .settings(
crossScalaVersions := supportedScalaVersions, crossScalaVersions := supportedScalaVersions,
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
...@@ -17,7 +19,22 @@ lazy val core = (project in file(".")) ...@@ -17,7 +19,22 @@ lazy val core = (project in file("."))
), Compile / scalacOptions ++= { ), Compile / scalacOptions ++= {
CrossVersion.partialVersion(scalaVersion.value) match { CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, n)) if n <= 12 => Nil case Some((2, n)) if n <= 12 => Nil
case _ => List("-Ymacro-annotations", "-Ymacro-debug-verbose") case _ => List("-Ymacro-annotations" /*, "-Ymacro-debug-verbose"*/)
} }
} },
) releaseIgnoreUntrackedFiles := true,
releaseCrossBuild := true,
releaseProcess := Seq[ReleaseStep](
checkSnapshotDependencies,
inquireVersions,
runClean,
releaseStepCommandAndRemaining("^ compile"),
setReleaseVersion,
commitReleaseVersion,
tagRelease,
releaseStepCommandAndRemaining("^ publishSigned"),
setNextVersion,
commitNextVersion,
pushChanges
)
).settings(Publishing.publishSettings)
import sbt.Keys._
import sbt._
import xerial.sbt.Sonatype.autoImport.sonatypeProfileName
/**
* sbt publish setting
*
* @author 梦境迷离 dreamylost
* @since 2020-07-19
* @version v1.0
*/
object Publishing {
//publish by sbt publishSigned
lazy val publishSettings = Seq(
credentials += Credentials(Path.userHome / ".ivy2" / ".sonatype_credentials"),
publishTo := {
val nexus = "https://oss.sonatype.org/"
if (isSnapshot.value)
Some("snapshots" at nexus + "content/repositories/snapshots")
else
Some("releases" at nexus + "service/local/staging/deploy/maven2")
},
licenses := Seq("MIT" -> url("https://opensource.org/licenses/MIT")),
publishMavenStyle := true,
publishArtifact in Test := false,
pomIncludeRepository := { _ => false },
developers := List(
Developer(
id = "dreamylost",
name = "梦境迷离",
email = "dreamylost@outlook.com",
url = url("https://dreamylost.cn")
)),
sonatypeProfileName := organization.value,
isSnapshot := version.value endsWith "SNAPSHOT",
homepage := Some(url("https://github.com/jxnu-liguobin")),
scmInfo := Some(
ScmInfo(
url("https://github.com/bitlap/scala-macro-tools"),
"scm:git@github.com:bitlap/scala-macro-tools.git"
))
)
}
package io.github.liguobin package io.github.dreamylost
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
...@@ -9,23 +9,23 @@ import scala.reflect.macros.whitebox ...@@ -9,23 +9,23 @@ import scala.reflect.macros.whitebox
* *
* @author 梦境迷离 * @author 梦境迷离
* @param verbose Whether to enable detailed log. * @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. * @param withFieldName Whether to include the name of the field in the toString.
* @since 2021/6/13 * @since 2021/6/13
* @version 1.0 * @version 1.0
*/ */
@compileTimeOnly("enable macro to expand macro annotations") @compileTimeOnly("enable macro to expand macro annotations")
class toString( final class toString(
verbose: Boolean = false, verbose: Boolean = false,
containsCtorParams: Boolean = true, withInternalField: Boolean = true,
withFieldName: Boolean = true withFieldName: Boolean = true
) extends StaticAnnotation { ) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro stringMacro.impl 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 { object stringMacro {
...@@ -35,9 +35,9 @@ object stringMacro { ...@@ -35,9 +35,9 @@ object stringMacro {
if (argument.withFieldName) { if (argument.withFieldName) {
lastParam.fold(q"$field") { lp => lastParam.fold(q"$field") { lp =>
field match { 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""" 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""" if (tname.toString() != lp) q"""${tname.toString()}+${"="}+this.$tname+${", "}""" else q"""${tname.toString()}+${"="}+this.$tname"""
case _ => q"$field" case _ => q"$field"
} }
...@@ -45,9 +45,9 @@ object stringMacro { ...@@ -45,9 +45,9 @@ object stringMacro {
} else { } else {
lastParam.fold(q"$field") { lp => lastParam.fold(q"$field") { lp =>
field match { field match {
case tree@q"$mods var $tname: $tpt = $expr" => if (tname.toString() != lp) q"""$tname+${", "}""" else q"""$tname""" 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 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 _ => if (field.toString() != lp) q"""$field+${", "}""" else q"""$field"""
} }
} }
...@@ -72,23 +72,21 @@ object stringMacro { ...@@ -72,23 +72,21 @@ object stringMacro {
c.abort(mem.pos, "'toString' method has already defined, please remove it or not use'@toString'") c.abort(mem.pos, "'toString' method has already defined, please remove it or not use'@toString'")
} }
false false
case m: DefDef =>
false
case _ => false case _ => false
}) })
// For the parameters of a given constructor, separate the parameter components and extract the constructor parameters containing val and var // 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 { val ctorParams = annotteeClassParams.asInstanceOf[List[List[Tree]]].flatten.map {
case tree@q"$mods val $tname: $tpt = $expr" => tree case tree @ q"$mods val $tname: $tpt = $expr" => tree
case tree@q"$mods var $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, ctorParams: ${ctorParams.toString()}", force = true)
c.info(c.enclosingPosition, s"className: $className, fields: ${annotteeClassFieldDefinitions.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 { val lastParam = member.lastOption.map {
case v: ValDef => v.name.toTermName.decodedName.toString 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)}") 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 //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 { ...@@ -98,16 +96,17 @@ object stringMacro {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._ import c.universe._
// extract parameters of annotation // extract parameters of annotation
// extract 'isVerbose' parameters of annotation
val arg = c.prefix.tree match { 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($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!") case _ => c.abort(c.enclosingPosition, "unexpected annotation pattern!")
} }
val argument = Argument(arg._1, arg._2, arg._3) val argument = Argument(arg._1, arg._2, arg._3)
// 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 = annottees.map(_.tree).toList match { val annotateeClass: ClassDef = annottees.map(_.tree).toList match {
case (claz: ClassDef) :: Nil => claz 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 = { val isCase: Boolean = {
annotateeClass match { annotateeClass match {
...@@ -119,7 +118,7 @@ object stringMacro { ...@@ -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 resMethod = toStringTemplateImpl(c)(argument, annotateeClass)
val resTree = annotateeClass match { val resTree = annotateeClass 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 }" =>
......
package io.github.liguobin package io.github.dreamylost
import org.scalatest.{FlatSpec, Matchers} import org.scalatest.{ FlatSpec, Matchers }
/** /**
* *
...@@ -10,7 +10,7 @@ import org.scalatest.{FlatSpec, Matchers} ...@@ -10,7 +10,7 @@ import org.scalatest.{FlatSpec, Matchers}
*/ */
class ToStringTest extends FlatSpec with 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) @toString(false, false, false)
class TestClass(val i: Int = 0, var j: Int) { class TestClass(val i: Int = 0, var j: Int) {
val y: Int = 0 val y: Int = 0
...@@ -19,10 +19,10 @@ class ToStringTest extends FlatSpec with Matchers { ...@@ -19,10 +19,10 @@ class ToStringTest extends FlatSpec with Matchers {
} }
val s = new TestClass(1, 2).toString val s = new TestClass(1, 2).toString
println(s) 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) @toString(true, true, true)
class TestClass(val i: Int = 0, var j: Int) { class TestClass(val i: Int = 0, var j: Int) {
val y: Int = 0 val y: Int = 0
...@@ -34,7 +34,7 @@ class ToStringTest extends FlatSpec with Matchers { ...@@ -34,7 +34,7 @@ class ToStringTest extends FlatSpec with Matchers {
assert(s == "TestClass(i=1, j=2, y=0, z=hello, x=world)") 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) @toString(true, false, true)
class TestClass(val i: Int = 0, var j: Int) { class TestClass(val i: Int = 0, var j: Int) {
val y: Int = 0 val y: Int = 0
...@@ -43,10 +43,10 @@ class ToStringTest extends FlatSpec with Matchers { ...@@ -43,10 +43,10 @@ class ToStringTest extends FlatSpec with Matchers {
} }
val s = new TestClass(1, 2).toString val s = new TestClass(1, 2).toString
println(s) 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) @toString(true, true, false)
class TestClass(val i: Int = 0, var j: Int) { class TestClass(val i: Int = 0, var j: Int) {
val y: Int = 0 val y: Int = 0
...@@ -67,10 +67,10 @@ class ToStringTest extends FlatSpec with Matchers { ...@@ -67,10 +67,10 @@ class ToStringTest extends FlatSpec with Matchers {
} }
val s = TestClass(1, 2).toString val s = TestClass(1, 2).toString
println(s) 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) @toString(true, false, true)
case class TestClass(i: Int = 0, var j: Int) { case class TestClass(i: Int = 0, var j: Int) {
val y: Int = 0 val y: Int = 0
...@@ -83,11 +83,11 @@ class ToStringTest extends FlatSpec with Matchers { ...@@ -83,11 +83,11 @@ class ToStringTest extends FlatSpec with Matchers {
println(s) println(s)
println(s2) println(s2)
assert(s == "TestClass(y=0, z=hello, x=world)") assert(s == "TestClass(i=1, j=2)")
assert(s2 == "TestClass2(1,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) @toString(true, true, true)
case class TestClass(i: Int = 0, var j: Int) { case class TestClass(i: Int = 0, var j: Int) {
val y: Int = 0 val y: Int = 0
...@@ -99,7 +99,7 @@ class ToStringTest extends FlatSpec with Matchers { ...@@ -99,7 +99,7 @@ class ToStringTest extends FlatSpec with Matchers {
assert(s == "TestClass(i=1, j=2, y=0, z=hello, x=world)") 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) @toString(true, true, true)
case class TestClass(i: Int = 0, var j: Int, k: TestClass) { case class TestClass(i: Int = 0, var j: Int, k: TestClass) {
val y: Int = 0 val y: Int = 0
...@@ -111,7 +111,7 @@ class ToStringTest extends FlatSpec with Matchers { ...@@ -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)") 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) @toString(true, true, true)
case class TestClass(i: Int = 0, var j: Int) { case class TestClass(i: Int = 0, var j: Int) {
val y: Int = 0 val y: Int = 0
...@@ -124,7 +124,7 @@ class ToStringTest extends FlatSpec with Matchers { ...@@ -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)") 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) @toString(true, true, true)
case class TestClass(i: Int = 0, var j: Int, k: TestClass) { case class TestClass(i: Int = 0, var j: Int, k: TestClass) {
val y: Int = 0 val y: Int = 0
...@@ -155,7 +155,7 @@ class ToStringTest extends FlatSpec with Matchers { ...@@ -155,7 +155,7 @@ class ToStringTest extends FlatSpec with Matchers {
class TestClass(i: Int = 0, var j: Int) class TestClass(i: Int = 0, var j: Int)
val s = new TestClass(1, 2).toString val s = new TestClass(1, 2).toString
println(s) println(s)
assert(s == "TestClass()") assert(s == "TestClass(i=1, j=2)")
@toString(true, true, true) @toString(true, true, true)
class TestClass2(i: Int = 1, var j: Int = 2) class TestClass2(i: Int = 1, var j: Int = 2)
...@@ -181,7 +181,7 @@ class ToStringTest extends FlatSpec with Matchers { ...@@ -181,7 +181,7 @@ class ToStringTest extends FlatSpec with Matchers {
case class TestClass2(i: Int = 1, var j: Int = 3) case class TestClass2(i: Int = 1, var j: Int = 3)
val s2 = TestClass2(1, 2).toString val s2 = TestClass2(1, 2).toString
println(s2) println(s2)
assert(s2 == "TestClass2()") assert(s2 == "TestClass2(1, 2)")
@toString(true, true, true) @toString(true, true, true)
case class TestClass3(i: Int = 1, var j: Int = 3) case class TestClass3(i: Int = 1, var j: Int = 3)
...@@ -189,4 +189,40 @@ class ToStringTest extends FlatSpec with Matchers { ...@@ -189,4 +189,40 @@ class ToStringTest extends FlatSpec with Matchers {
println(s3) println(s3)
assert(s3 == "TestClass3(i=1, j=2)") 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.
先完成此消息的编辑!
想要评论请 注册