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

add `@JavaCompatible` (#119)

* add JavaCompatible
* ready to 0.3.2
上级 02c349ce
......@@ -18,8 +18,8 @@ Learn Scala macro and abstract syntax tree.
# Environment
- It is compiled in Java 8, 11
- It is compiled in Scala 2.11.x ~ 2.13.x
- Compile passed in Java 8、11
- Compile passed in Scala 2.11.12、2.12.14、2.13.6
# Features
......@@ -33,6 +33,7 @@ Learn Scala macro and abstract syntax tree.
- `@equalsAndHashCode`
- `@jacksonEnum`
- `@elapsed`
- `@JavaCompatible`
> The intellij plugin named `Scala-Macro-Tools` in marketplace.
......
......@@ -18,8 +18,8 @@
# 环境
- 使用 Java 8, 11 编译通过
- 使用 Scala 2.11.x ~ 2.13.x 编译通过
- Java 8、11 编译通过
- Scala 2.11.12、2.12.14、2.13.6 编译通过
# 功能
......@@ -33,6 +33,7 @@
- `@equalsAndHashCode`
- `@jacksonEnum`
- `@elapsed`
- `@JavaCompatible`
> Intellij插件 `Scala-Macro-Tools`。
......
/*
* Copyright (c) 2021 jxnu-liguobin && contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.dreamylost
import scala.annotation.{ compileTimeOnly, StaticAnnotation }
import io.github.dreamylost.macros.javaCompatibleMacro
/**
* annotation to generate non-parameter constructor and get/set method for case classes.
* Fields marked `private[this]` in curry are not supported !
*
* @author 梦境迷离
* @param verbose Whether to enable detailed log.
* @since 2021/11/23
* @version 1.0
*/
@compileTimeOnly("enable macro to expand macro annotations")
final class JavaCompatible(
verbose: Boolean = false
) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro javaCompatibleMacro.JavaCompatibleProcessor.impl
}
/*
* Copyright (c) 2021 jxnu-liguobin && contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.dreamylost.macros
import scala.reflect.macros.whitebox
/**
*
* @author 梦境迷离
* @since 2021/11/23
* @version 1.0
*/
object javaCompatibleMacro {
class JavaCompatibleProcessor(override val c: whitebox.Context) extends AbstractMacroProcessor(c) {
import c.universe._
/**
* We generate this method with currying, and we have to deal with the first layer of currying alone.
*/
private def getNoArgsContrWithCurrying(annotteeClassParams: List[List[Tree]], annotteeClassDefinitions: Seq[Tree]): Tree = {
if (annotteeClassDefinitions.exists(f => !isNotLocalClassMember(f))) {
c.info(c.enclosingPosition, s"The params of 'private[this]' exists in class constructor", verbose)
}
annotteeClassDefinitions.foreach {
case defDef: DefDef if defDef.name.decodedName.toString == "this" && defDef.vparamss.isEmpty =>
c.abort(defDef.pos, "Non-parameter constructor method has already defined, please remove it or not use'@JavaCompatible'")
case _ =>
}
val defaultParameters = annotteeClassParams.map(valDefAccessors).map(params => params.map(param => {
param.paramType match {
case t if t <:< typeOf[Int] => q"0"
case t if t <:< typeOf[Byte] => q"0"
case t if t <:< typeOf[Double] => q"0D"
case t if t <:< typeOf[Float] => q"0F"
case t if t <:< typeOf[Short] => q"0"
case t if t <:< typeOf[Long] => q"0L"
case t if t <:< typeOf[Char] => q"63.toChar" // default char is ?
case t if t <:< typeOf[Boolean] => q"false"
case _ => q"null"
}
}))
if (annotteeClassParams.isEmpty || annotteeClassParams.size == 1) {
q"""
def this() = {
this(..${defaultParameters.flatten})
}
"""
} else {
q"""
def this() = {
this(..${defaultParameters.head})(...${defaultParameters.tail})
}
"""
}
}
private def replaceAnnotation(valDefTree: Tree): Tree = {
val safeValDef = valDefAccessors(Seq(valDefTree)).head
val mods = safeValDef.mods.mapAnnotations(f => {
if (!f.toString().contains("BeanProperty")) f ++ List(q"new _root_.scala.beans.BeanProperty") else f
})
ValDef(mods, safeValDef.name, safeValDef.tpt, safeValDef.rhs)
}
private def getClassWithBeanProperty(classDecl: ClassDef): Tree = {
val q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$bases { ..$body }" = classDecl
val newFieldss = paramss.asInstanceOf[List[List[Tree]]].map(_.map(replaceAnnotation))
q"$mods class $tpname[..$tparams] $ctorMods(...$newFieldss) extends ..$bases { ..$body }"
}
override def createCustomExpr(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val tmpClassDefTree = appendClassBody(classDecl, classInfo => List(getNoArgsContrWithCurrying(classInfo.classParamss, classInfo.body)))
val rest = getClassWithBeanProperty(tmpClassDefTree)
c.Expr(
q"""
${compDeclOpt.fold(EmptyTree)(x => x)}
$rest
""")
}
override def checkAnnottees(annottees: Seq[c.universe.Expr[Any]]): Unit = {
super.checkAnnottees(annottees)
val annotateeClass: ClassDef = checkGetClassDef(annottees)
if (!isCaseClass(annotateeClass)) {
c.abort(c.enclosingPosition, ErrorMessage.ONLY_CASE_CLASS)
}
}
}
}
/*
* Copyright (c) 2021 jxnu-liguobin && contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.dreamylost
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
/**
*
* @author 梦境迷离
* @since 2021/11/23
* @version 1.0
*/
class JavaCompatibleTest extends AnyFlatSpec with Matchers {
"JavaCompatible1" should "ok" in {
"""
| @JavaCompatible
| case class A(a: Int, b: Short, c: Byte, d: Double, e: Float, f: Long, g: Char, h: Boolean)
| val t = new A()
| assert(t.a == 0 && t.g == '?')
|""".stripMargin should compile
}
"JavaCompatible2" should "ok" in {
"""
| @JavaCompatible
| case class A(a: Int, b: Short, c: Byte, d: Double)(val e: Float, val f: Long)(val g: Char, val h: Boolean)
| val t = new A()
| assert(t.a == 0 && t.g == '?')
|""".stripMargin should compile
}
"JavaCompatible3" should "failed" in {
"""
| @JavaCompatible
| case class A(a: Int, b: Short, c: Byte, d: Double)(val e: Float, val f: Long)(g: Char, h: Boolean)
| val t = new A()
| assert(t.a == 0 && t.g == '?')
|""".stripMargin shouldNot compile
"""
| @JavaCompatible
| class A(val a: Int, val b: Short)
| val t = new A()
| assert(t.a == 0)
|""".stripMargin shouldNot compile
}
"JavaCompatible4" should "ok" in {
@JavaCompatible
case class A(a: Int, b: Short, c: Byte, d: Double, e: Float, f: Long, g: Char, h: Boolean, i: String)
val t = new A()
assert(t.a == 0 && t.g == '?')
}
"JavaCompatible5" should "ok" in {
import scala.beans.BeanProperty
@JavaCompatible
case class A(@BeanProperty a: Int, b: Short, c: Byte, d: Double, e: Float, f: Long, g: Char, h: Boolean, i: String)
val t = new A()
assert(t.a == 0 && t.g == '?')
}
"JavaCompatible6" should "ok when exists @BeanProperty" in {
import scala.beans.BeanProperty
@JavaCompatible
case class A(@BeanProperty a: Int, b: Short, c: Byte, d: Double, e: Float, f: Long, g: Char, h: Boolean, i: String)
val t = new A()
assert(t.getA == 0)
assert(t.getB == 0)
}
"JavaCompatible7" should "ok when exists super" in {
import scala.beans.BeanProperty
class B(@BeanProperty val name: String, @BeanProperty val id: Int)
@JavaCompatible
case class A(a: Int, b: Short, override val name: String, override val id: Int) extends B(name, id)
val t = new A()
assert(t.getA == 0)
assert(t.getB == 0)
}
// Why this code compile failed but test in """ """.stripMargin will pass?
"JavaCompatible8" should "fail when exists super but not use @BeanProperty" in {
"""
| class B(val name: String, val id: Int)
| @JavaCompatible
| case class A(a: Int, b: Short, override val name: String, override val id: Int) extends B(name, id)
| val t = new A()
|""".stripMargin should compile
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册