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

simple tostring impl

上级
{"name":"sbt","version":"1.4.7","bspVersion":"2.0.0-M5","languages":["scala"],"argv":["/Library/Java/JavaVirtualMachines/jdk1.8.0_74.jdk/Contents/Home/jre/bin/java","-Xms100m","-Xmx100m","-classpath","/Users/liguobin/Library/Application Support/JetBrains/IntelliJIdea2020.3/plugins/Scala/launcher/sbt-launch.jar","xsbt.boot.Boot","-bsp"]}
\ No newline at end of file
name: Kotlin CI
on:
push:
branches:
- master
- dev
pull_request:
branches:
- master
- dev
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build
run: sbt +compile
- name: Publish Local
run: sbt publishLocal
\ No newline at end of file
# Project exclude paths
/project/project/target/
/project/target/
/target/
/target/scala-2.13/classes/
/target/scala-2.13/test-classes/
.idea/
\ No newline at end of file
autoformat = true
withBaseDirectory = true
// Preferences
alignParameters = true
newlineAtEndOfFile = true
rewriteArrowSymbols = false
allowParamGroupsOnNewlines = true
danglingCloseParenthesis = Preserve
alignSingleLineCaseStatements = true
doubleIndentConstructorArguments = true
# scala-macro-tools
scala macro and abstract syntax tree learning code.
# @toString
- features
- Automatically ignore when use on case class.
- Constructor parameters which have val/var modifier and class internal fields.
- The existing custom `toString` method will fail to compile.
- source code
```scala
@toString
class TestClass(val i: Int = 0, var j: Int) {
val y: Int = 0
var z: String = "hello"
var x: String = "world"
}
```
- result of scalac
```scala
class TestClass extends scala.AnyRef {
<paramaccessor> val i: Int = _;
<paramaccessor> var j: Int = _;
def <init>(i: Int = 0, j: Int) = {
super.<init>();
()
};
val y: Int = 0;
var z: String = "hello";
var x: String = "world";
override def toString(): String = scala.collection.immutable.List(i, j, y, z, x).toString.replace("List", "TestClass") // Use crude methods.
}
```
version in Scope.ThisScope := "0.1-SNAPSHOT"
name := "scala-macro-tools"
scalaVersion := "2.13.6"
organization := "io.github.jxnu-liguobin"
lazy val scala212 = "2.12.13"
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("."))
.settings(
crossScalaVersions := supportedScalaVersions,
libraryDependencies ++= Seq(
"org.scala-lang" % "scala-compiler" % scalaVersion.value,
"org.scala-lang" % "scala-reflect" % scalaVersion.value,
"org.scalatest" %% "scalatest" % "3.0.9" % Test
), Compile / scalacOptions ++= {
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, n)) if n <= 12 => Nil
case _ => List("-Ymacro-annotations", "-Ymacro-debug-verbose")
}
}
)
sbt.version = 1.4.7
\ No newline at end of file
addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.3")
addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.3")
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.1")
addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.13")
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.10.0")
\ No newline at end of file
package io.github.liguobin
import scala.annotation.{ StaticAnnotation, compileTimeOnly }
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
/**
* toString for class
*
* @author 梦境迷离
* @param verbose Whether to enable detailed log
* @param isContainsCtorParams Whether to include the fields in the constructor
* @since 2021/6/13
* @version 1.0
*/
@compileTimeOnly("enable macro to expand macro annotations")
class toString(verbose: Boolean = false, isContainsCtorParams: Boolean = false) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro stringMacro.impl
}
object stringMacro {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
// extract 'isVerbose' parameters of annotation
val isVerbose = c.prefix.tree match {
case Apply(_, q"verbose = $foo" :: Nil) =>
foo match {
case Literal(Constant(verbose: Boolean)) => verbose
case _ =>
c.warning(
c.enclosingPosition,
"The value provided for 'verbose' must be a constant (true or false) and not an expression (e.g. 2 == 1 + 1). Verbose set to false."
)
false
}
case _ => false
}
// extract 'containsCtorParams' parameters of annotation
val containsCtorParams = c.prefix.tree match {
case Apply(_, q"isContainsCtorParams = $foo" :: Nil) =>
foo match {
case Literal(Constant(isContainsCtorParams: Boolean)) => isContainsCtorParams
case _ =>
c.warning(
c.enclosingPosition,
"The value provided for 'isContainsCtorParams' must be a constant (true or false) and not an expression (e.g. 2 == 1 + 1). isContainsCtorParams set to false."
)
false
}
case _ => false
}
// 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.")
}
// if (annotateeClass.tpe.typeSymbol.asClass.isCaseClass) {
// c.warning(
// c.enclosingPosition,
// "'toString' annotation is used on 'case class'. Ignore"
// )
// }
// For a given class definition, separate the components of the class
val (isCase, className, annotteeClassParams, annotteeClassDefinitions) = {
annotateeClass match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
val isCase = if (mods.asInstanceOf[Modifiers].hasFlag(Flag.CASE)) {
c.warning(
c.enclosingPosition,
"'toString' annotation is used on 'case class'. Ignore")
true
} else false
(isCase, tpname, paramss, stats)
}
}
// Check the type of the class, whether it already contains its own toString
annotteeClassDefinitions.asInstanceOf[List[Tree]].map {
case v: ValDef => true
case m: MemberDef => c.abort(m.pos, "'toString' method has already defined, please remove it or not use'@toString'")
}
// Extract the fields in the class definition
val fields = annotteeClassDefinitions.asInstanceOf[List[Tree]].map {
case v: ValDef => v.name
}
// 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" => TermName.apply(tname.toString())
case tree @ q"$mods var $tname: $tpt = $expr" => TermName.apply(tname.toString())
}
val member = if (containsCtorParams) ctorParams ++ fields else fields
// Generate toString method TODO refactor code
val method =
q"""
override def toString(): String = ($member).toString.replace("List", ${className.toString()})
"""
val resTree = annotateeClass match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
if (!isCase) {
q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${stats.toList.:+(method)} }"
} else {
q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }"
}
}
// Print the ast
c.info(
c.enclosingPosition,
"\n###### Expanded macro ######\n" + resTree.toString() + "\n###### Expanded macro ######\n",
force = isVerbose
)
c.Expr[Any](resTree)
}
}
package io.github.liguobin
import org.scalatest.{ FlatSpec, Matchers }
/**
*
* @author 梦境迷离
* @since 2021/6/13
* @version 1.0
*/
class ToStringTest extends FlatSpec with Matchers {
"toString1" should "contains ContainsCtorParams" in {
@toString
class TestClass(val i: Int = 0, var j: Int) {
val y: Int = 0
var z: String = "hello"
var x: String = "world"
}
val s = new TestClass(1, 2).toString
println(s)
assert(s == "TestClass(0, hello, world)")
}
"toString2" should "contains ContainsCtorParams" in {
@toString(isContainsCtorParams = true)
class TestClass(val i: Int = 0, var j: Int) {
val y: Int = 0
var z: String = "hello"
var x: String = "world"
}
val s = new TestClass(1, 2).toString
println(s)
assert(s == "TestClass(1, 2, 0, hello, world)")
}
// "toString3" should "failed when toString already defined" in {
// @toString(isContainsCtorParams = true)
// class TestClass(val i: Int = 0, var j: Int) {
// val y: Int = 0
// var z: String = "hello"
// var x: String = "world"
//
// override def toString = s"TestClass($y, $z, $x, $i, $j)"
// }
// val s = new TestClass(1, 2).toString
// println(s)
// assert(s == "TestClass(1, 2, 0, hello world macro, hello world)")
// }
"toString4" should "case class" in {
@toString(isContainsCtorParams = true)
case class TestClass(val i: Int = 0, var j: Int)
val s = TestClass(1, 2).toString
println(s)
assert(s == "TestClass(1, 2)")
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册