未验证 提交 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.
- 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 ThisBuild := "0.0.2-SNAPSHOT"
import sbtrelease.ReleaseStateTransformations._
name := "scala-macro-tools"
scalaVersion := "2.13.6"
organization := "io.github.jxnu-liguobin"
......@@ -7,7 +9,7 @@ lazy val scala211 = "2.11.12"
lazy val scala213 = "2.13.6"
lazy val supportedScalaVersions = List(scala213, scala212, scala211)
lazy val core = (project in file("."))
lazy val root = (project in file("."))
.settings(
crossScalaVersions := supportedScalaVersions,
libraryDependencies ++= Seq(
......@@ -17,7 +19,22 @@ lazy val core = (project in file("."))
), Compile / scalacOptions ++= {
CrossVersion.partialVersion(scalaVersion.value) match {
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.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
package io.github.dreamylost
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.
先完成此消息的编辑!
想要评论请 注册