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

refactor (#82)

上级 277059da
coverage:
precision: 2
round: down
range: "70...100"
status:
project: yes
patch: yes
# changes: no
comment:
layout: "header, diff, changes, tree"
behavior: default
ignore:
- "package.scala"
- "macros.scala"
\ No newline at end of file
......@@ -30,8 +30,27 @@ import scala.reflect.macros.whitebox
* @version 1.0
*/
abstract class AbstractMacroProcessor(val c: whitebox.Context) {
import c.universe._
/**
* Subclasses should override the method and return the final result abstract syntax tree, or an abstract syntax tree close to the final result.
* When the macro implementation is very simple, we don't need to use this method, so we don't need to implement it.
* When there are many macro input parameters, we will not use this method temporarily because we need to pass parameters.
*
* @param classDecl
* @param compDeclOpt
* @return c.Expr[Any], Why use Any? The dependent type need aux-pattern in scala2. Now let's get around this.
*
*/
def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = ???
/**
* Subclasses must override the method.
*
* @param annottees
* @return Macro expanded final syntax tree.
*/
def impl(annottees: Expr[Any]*): Expr[Any]
/**
......
......@@ -35,40 +35,37 @@ object applyMacro {
import c.universe._
override def impl(annottees: Expr[Any]*): Expr[Any] = {
val args: Tuple1[Boolean] = extractArgumentsTuple1 {
private val extractArgumentsDetail: Tuple1[Boolean] = {
extractArgumentsTuple1 {
case q"new apply(verbose=$verbose)" => Tuple1(evalTree(verbose.asInstanceOf[Tree]))
case q"new apply()" => Tuple1(false)
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
}
val annotateeClass: ClassDef = checkAndGetClassDef(annottees: _*)
val isCase: Boolean = isCaseClass(annotateeClass)
c.info(c.enclosingPosition, s"impl argument: $args, isCase: $isCase", force = args._1)
if (isCase) c.abort(c.enclosingPosition, s"Annotation is only supported on 'case class'")
}
def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val (className, classParams, classTypeParams) = classDecl match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$bases { ..$body }" =>
c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = args._1)
(tpname, paramss.asInstanceOf[List[List[Tree]]], tparams.asInstanceOf[List[Tree]])
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
}
c.info(c.enclosingPosition, s"modifiedDeclaration compDeclOpt: $compDeclOpt, annotteeClassParams: $classParams", force = args._1)
val tpName = className.asInstanceOf[TypeName]
val apply = getApplyMethodWithCurrying(tpName, classParams, classTypeParams)
val compDecl = modifiedCompanion(compDeclOpt, apply, tpName)
c.Expr(
q"""
override def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val (className, classParamss, classTypeParams) = classDecl match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$bases { ..$body }" =>
c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = extractArgumentsDetail._1)
(tpname.asInstanceOf[TypeName], paramss.asInstanceOf[List[List[Tree]]], tparams.asInstanceOf[List[Tree]])
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
}
val apply = getApplyMethodWithCurrying(className, classParamss, classTypeParams)
val compDecl = modifiedCompanion(compDeclOpt, apply, className)
c.Expr(
q"""
$classDecl
$compDecl
""")
}
}
override def impl(annottees: Expr[Any]*): Expr[Any] = {
val annotateeClass: ClassDef = checkAndGetClassDef(annottees: _*)
if (isCaseClass(annotateeClass)) c.abort(c.enclosingPosition, s"Annotation is only supported on 'case class'")
val resTree = handleWithImplType(annottees: _*)(modifiedDeclaration)
printTree(force = args._1, resTree.tree)
printTree(force = extractArgumentsDetail._1, resTree.tree)
resTree
}
}
}
......@@ -37,46 +37,45 @@ object builderMacro {
import c.universe._
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
def getBuilderClassName(classTree: TypeName): TypeName = {
TypeName(classTree.toTermName.decodedName.toString + BUFFER_CLASS_NAME_SUFFIX)
private def getBuilderClassName(classTree: TypeName): TypeName = {
TypeName(classTree.toTermName.decodedName.toString + BUFFER_CLASS_NAME_SUFFIX)
}
private def fieldDefinition(field: Tree): Tree = {
field match {
case q"$mods val $tname: $tpt = $expr" => q"""private var $tname: $tpt = $expr"""
case q"$mods var $tname: $tpt = $expr" => q"""private var $tname: $tpt = $expr"""
}
}
def fieldSetMethod(typeName: TypeName, field: Tree, classTypeParams: List[Tree]): Tree = {
val builderClassName = getBuilderClassName(typeName)
val returnTypeParams = extractClassTypeParamsTypeName(classTypeParams)
field match {
case q"$mods var $tname: $tpt = $expr" =>
q"""
private def fieldSetMethod(typeName: TypeName, field: Tree, classTypeParams: List[Tree]): Tree = {
val builderClassName = getBuilderClassName(typeName)
val returnTypeParams = extractClassTypeParamsTypeName(classTypeParams)
field match {
case q"$mods var $tname: $tpt = $expr" =>
q"""
def $tname($tname: $tpt): $builderClassName[..$returnTypeParams] = {
this.$tname = $tname
this
}
"""
case q"$mods val $tname: $tpt = $expr" =>
q"""
case q"$mods val $tname: $tpt = $expr" =>
q"""
def $tname($tname: $tpt): $builderClassName[..$returnTypeParams] = {
this.$tname = $tname
this
}
"""
}
}
def fieldDefinition(field: Tree): Tree = {
field match {
case q"$mods val $tname: $tpt = $expr" => q"""private var $tname: $tpt = $expr"""
case q"$mods var $tname: $tpt = $expr" => q"""private var $tname: $tpt = $expr"""
}
}
}
def builderTemplate(typeName: TypeName, fieldss: List[List[Tree]], classTypeParams: List[Tree], isCase: Boolean): Tree = {
val fields = fieldss.flatten
val builderClassName = getBuilderClassName(typeName)
val builderFieldMethods = fields.map(f => fieldSetMethod(typeName, f, classTypeParams))
val builderFieldDefinitions = fields.map(f => fieldDefinition(f))
val returnTypeParams = extractClassTypeParamsTypeName(classTypeParams)
q"""
private def getBuilderClassAndMethod(typeName: TypeName, fieldss: List[List[Tree]], classTypeParams: List[Tree], isCase: Boolean): Tree = {
val fields = fieldss.flatten
val builderClassName = getBuilderClassName(typeName)
val builderFieldMethods = fields.map(f => fieldSetMethod(typeName, f, classTypeParams))
val builderFieldDefinitions = fields.map(f => fieldDefinition(f))
val returnTypeParams = extractClassTypeParamsTypeName(classTypeParams)
q"""
def builder[..$classTypeParams](): $builderClassName[..$returnTypeParams] = new $builderClassName()
class $builderClassName[..$classTypeParams] {
......@@ -88,34 +87,30 @@ object builderMacro {
def build(): $typeName[..$returnTypeParams] = ${getConstructorWithCurrying(typeName, fieldss, isCase)}
}
"""
}
override def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val (className, fieldss, classTypeParams) = classDecl match {
// @see https://scala-lang.org/files/archive/spec/2.13/05-classes-and-objects.html
case q"$mods class $tpname[..$tparams](...$paramss) extends ..$bases { ..$body }" =>
c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = true)
(tpname.asInstanceOf[TypeName], paramss.asInstanceOf[List[List[Tree]]], tparams.asInstanceOf[List[Tree]])
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
}
// Why use Any? The dependent type need aux-pattern in scala2. Now let's get around this.
def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val (className, fieldss, classTypeParams) = classDecl match {
// @see https://scala-lang.org/files/archive/spec/2.13/05-classes-and-objects.html
case q"$mods class $tpname[..$tparams](...$paramss) extends ..$bases { ..$body }" =>
c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = true)
(tpname, paramss.asInstanceOf[List[List[Tree]]], tparams.asInstanceOf[List[Tree]])
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
}
val cName = className.asInstanceOf[TypeName]
val isCase = isCaseClass(classDecl)
val builder = builderTemplate(cName, fieldss, classTypeParams, isCase)
val compDecl = modifiedCompanion(compDeclOpt, builder, cName)
c.info(c.enclosingPosition, s"builderTree: $builder, compDecl: $compDecl", force = true)
// Return both the class and companion object declarations
c.Expr(
q"""
val builder = getBuilderClassAndMethod(className, fieldss, classTypeParams, isCaseClass(classDecl))
val compDecl = modifiedCompanion(compDeclOpt, builder, className)
// Return both the class and companion object declarations
c.Expr(
q"""
$classDecl
$compDecl
""")
}
}
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
val resTree = handleWithImplType(annottees: _*)(modifiedDeclaration)
printTree(force = true, resTree.tree)
resTree
}
}
......
......@@ -35,105 +35,98 @@ object constructorMacro {
import c.universe._
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
val args: (Boolean, Seq[String]) = extractArgumentsTuple2 {
private val extractArgumentsDetail: Tuple2[Boolean, Seq[String]] = {
extractArgumentsTuple2 {
case q"new constructor(verbose=$verbose)" => (evalTree(verbose.asInstanceOf[Tree]), Nil)
case q"new constructor(excludeFields=$excludeFields)" => (false, evalTree(excludeFields.asInstanceOf[Tree]))
case q"new constructor(verbose=$verbose, excludeFields=$excludeFields)" => (evalTree(verbose.asInstanceOf[Tree]), evalTree(excludeFields.asInstanceOf[Tree]))
case q"new constructor()" => (false, Nil)
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
}
}
val annotateeClass: ClassDef = checkAndGetClassDef(annottees: _*)
val isCase: Boolean = isCaseClass(annotateeClass)
if (isCase) {
c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $annotateeClass")
/**
* Extract the internal fields of members belonging to the class, but not in primary constructor and only `var`.
*/
private def getClassMemberVarDefOnlyAssignExpr(annotteeClassDefinitions: Seq[Tree]): Seq[Tree] = {
getClassMemberValDefs(annotteeClassDefinitions).filter(_ match {
case q"$mods var $tname: $tpt = $expr" if !extractArgumentsDetail._2.contains(tname.asInstanceOf[TermName].decodedName.toString) => true
case _ => false
}).map {
case q"$mods var $pat = $expr" =>
// TODO getClass RETURN a java type, maybe we can try use class reflect to get the fields type name.
q"$pat: ${TypeName(toScalaType(evalTree(expr.asInstanceOf[Tree]).getClass.getTypeName))}"
case q"$mods var $tname: $tpt = $expr" => q"$tname: $tpt"
}
}
def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val (annotteeClassParams, annotteeClassDefinitions) = classDecl match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = args._1)
(paramss.asInstanceOf[List[List[Tree]]], stats.asInstanceOf[Seq[Tree]])
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
}
// Extract the internal fields of members belonging to the class, but not in primary constructor.
val classFieldDefinitions = getClassMemberValDefs(annotteeClassDefinitions)
val excludeFields = args._2
/**
* Extract the internal fields of members belonging to the class, but not in primary constructor and only `var`.
*/
def getClassMemberVarDefOnlyAssignExpr: Seq[Tree] = {
import c.universe._
getClassMemberValDefs(annotteeClassDefinitions).filter(_ match {
case q"$mods var $tname: $tpt = $expr" if !excludeFields.contains(tname.asInstanceOf[TermName].decodedName.toString) => true
case _ => false
}).map {
case q"$mods var $pat = $expr" =>
// TODO getClass RETURN a java type, maybe we can try use class reflect to get the fields type name.
q"$pat: ${TypeName(toScalaType(evalTree(expr.asInstanceOf[Tree]).getClass.getTypeName))}"
case q"$mods var $tname: $tpt = $expr" => q"$tname: $tpt"
}
}
val classFieldDefinitionsOnlyAssignExpr = getClassMemberVarDefOnlyAssignExpr
if (classFieldDefinitionsOnlyAssignExpr.isEmpty) {
c.abort(c.enclosingPosition, s"Annotation is only supported on class when the internal field (declare as 'var') is nonEmpty. classDef: $classDecl")
}
val annotteeClassFieldNames = classFieldDefinitions.filter(_ match {
case q"$mods var $tname: $tpt = $expr" if !excludeFields.contains(tname.asInstanceOf[TermName].decodedName.toString) => true
case _ => false
}).map {
case q"$mods var $tname: $tpt = $expr" => tname.asInstanceOf[TermName]
}
c.info(c.enclosingPosition, s"modifiedDeclaration compDeclOpt: $compDeclOpt, annotteeClassParams: $annotteeClassParams", force = args._1)
// Extract the field of the primary constructor.
val allFieldsTermName = annotteeClassParams.map(f => f.map(ff => getFieldTermName(ff)))
/**
* We generate this method with currying, and we have to deal with the first layer of currying alone.
*/
def getThisMethodWithCurrying: Tree = {
// not currying
// Extract the field of the primary constructor.
val classParamsAssignExpr = getFieldAssignExprs(annotteeClassParams.flatten)
val applyMethod = if (annotteeClassParams.isEmpty || annotteeClassParams.size == 1) {
q"""
/**
* We generate this method with currying, and we have to deal with the first layer of currying alone.
*/
private def getThisMethodWithCurrying(annotteeClassParams: List[List[Tree]], annotteeClassDefinitions: Seq[Tree]): Tree = {
val classFieldDefinitionsOnlyAssignExpr = getClassMemberVarDefOnlyAssignExpr(annotteeClassDefinitions)
if (classFieldDefinitionsOnlyAssignExpr.isEmpty) {
c.abort(c.enclosingPosition, s"Annotation is only supported on class when the internal field (declare as 'var') is nonEmpty.")
}
// Extract the internal fields of members belonging to the class, but not in primary constructor.
val classFieldDefinitions = getClassMemberValDefs(annotteeClassDefinitions)
val annotteeClassFieldNames = classFieldDefinitions.filter(_ match {
case q"$mods var $tname: $tpt = $expr" if !extractArgumentsDetail._2.contains(tname.asInstanceOf[TermName].decodedName.toString) => true
case _ => false
}).map {
case q"$mods var $tname: $tpt = $expr" => tname.asInstanceOf[TermName]
}
// Extract the field of the primary constructor.
val allFieldsTermName = annotteeClassParams.map(f => f.map(ff => getFieldTermName(ff)))
// Extract the field of the primary constructor.
val classParamsAssignExpr = getFieldAssignExprs(annotteeClassParams.flatten)
val applyMethod = if (annotteeClassParams.isEmpty || annotteeClassParams.size == 1) {
q"""
def this(..${classParamsAssignExpr ++ classFieldDefinitionsOnlyAssignExpr}) = {
this(..${allFieldsTermName.flatten})
..${annotteeClassFieldNames.map(f => q"this.$f = $f")}
}
"""
} else {
// NOTE: currying constructor overload must be placed in the first bracket block.
val allClassParamsAssignExpr = annotteeClassParams.map(cc => getFieldAssignExprs(cc))
q"""
} else {
// NOTE: currying constructor overload must be placed in the first bracket block.
val allClassParamsAssignExpr = annotteeClassParams.map(cc => getFieldAssignExprs(cc))
q"""
def this(..${allClassParamsAssignExpr.head ++ classFieldDefinitionsOnlyAssignExpr})(...${allClassParamsAssignExpr.tail}) = {
this(..${allFieldsTermName.head})(...${allFieldsTermName.tail})
..${annotteeClassFieldNames.map(f => q"this.$f = $f")}
}
"""
}
applyMethod
}
val resTree = annotateeClass match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${stats.toList.:+(getThisMethodWithCurrying)} }"
}
c.Expr[Any](treeResultWithCompanionObject(resTree, annottees: _*))
}
applyMethod
}
val resTree = handleWithImplType(annottees: _*)(modifiedDeclaration)
printTree(force = args._1, resTree.tree)
override def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val (annotteeClassParams, annotteeClassDefinitions) = classDecl match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = extractArgumentsDetail._1)
(paramss.asInstanceOf[List[List[Tree]]], stats.asInstanceOf[Seq[Tree]])
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
}
c.Expr(getThisMethodWithCurrying(annotteeClassParams, annotteeClassDefinitions))
}
resTree
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
val annotateeClass: ClassDef = checkAndGetClassDef(annottees: _*)
if (isCaseClass(annotateeClass)) c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $annotateeClass")
val tmpTree = handleWithImplType(annottees: _*)(modifiedDeclaration)
val resTree = annotateeClass match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${stats.toList.:+(tmpTree.tree)} }"
}
val res = c.Expr[Any](treeResultWithCompanionObject(resTree, annottees: _*))
printTree(force = extractArgumentsDetail._1, res.tree)
res
}
}
}
......@@ -35,70 +35,68 @@ object equalsAndHashCodeMacro {
import c.universe._
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
val args: (Boolean, Seq[String]) = extractArgumentsTuple2 {
case q"new equalsAndHashCode(verbose=$verbose)" => (evalTree(verbose.asInstanceOf[Tree]), Nil)
case q"new equalsAndHashCode(excludeFields=$excludeFields)" => (false, evalTree(excludeFields.asInstanceOf[Tree]))
case q"new equalsAndHashCode(verbose=$verbose, excludeFields=$excludeFields)" => (evalTree(verbose.asInstanceOf[Tree]), evalTree(excludeFields.asInstanceOf[Tree]))
case q"new equalsAndHashCode()" => (false, Nil)
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
}
private val extractArgumentsDetail: (Boolean, Nil.type) = extractArgumentsTuple2 {
case q"new equalsAndHashCode(verbose=$verbose)" => (evalTree(verbose.asInstanceOf[Tree]), Nil)
case q"new equalsAndHashCode(excludeFields=$excludeFields)" => (false, evalTree(excludeFields.asInstanceOf[Tree]))
case q"new equalsAndHashCode(verbose=$verbose, excludeFields=$excludeFields)" => (evalTree(verbose.asInstanceOf[Tree]), evalTree(excludeFields.asInstanceOf[Tree]))
case q"new equalsAndHashCode()" => (false, Nil)
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
}
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
val annotateeClass: ClassDef = checkAndGetClassDef(annottees: _*)
val isCase: Boolean = isCaseClass(annotateeClass)
if (isCase) {
c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $annotateeClass")
if (isCaseClass(annotateeClass)) c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $annotateeClass")
val tmpTree = handleWithImplType(annottees: _*)(modifiedDeclaration)
// return with object if it exists
val resTree = annotateeClass match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
val originalStatus = q"{ ..$stats }"
val append =
q"""
..$originalStatus
..$tmpTree
"""
q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${append} }"
}
val excludeFields = args._2
def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val (className, annotteeClassParams, annotteeClassDefinitions, superClasses) = classDecl match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = args._1)
(tpname, paramss.asInstanceOf[List[List[Tree]]], stats.asInstanceOf[Seq[Tree]], parents)
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
}
val ctorFieldNames = annotteeClassParams.flatten.filter(cf => classParamsIsPrivate(cf))
val allFieldsTermName = ctorFieldNames.map(f => getFieldTermName(f))
c.info(c.enclosingPosition, s"modifiedDeclaration compDeclOpt: $compDeclOpt, ctorFieldNames: $ctorFieldNames, " +
s"annotteeClassParams: $superClasses", force = args._1)
/**
* Extract the internal fields of members belonging to the class.
*/
def getClassMemberAllTermName: Seq[TermName] = {
getClassMemberValDefs(annotteeClassDefinitions).filter(_ match {
case q"$mods var $tname: $tpt = $expr" if !excludeFields.contains(tname.asInstanceOf[TermName].decodedName.toString) => true
case q"$mods val $tname: $tpt = $expr" if !excludeFields.contains(tname.asInstanceOf[TermName].decodedName.toString) => true
case q"$mods val $pat = $expr" if !excludeFields.contains(pat.asInstanceOf[TermName].decodedName.toString) => true
case q"$mods var $pat = $expr" if !excludeFields.contains(pat.asInstanceOf[TermName].decodedName.toString) => true
case _ => false
}).map(f => getFieldTermName(f))
}
val res = c.Expr[Any](treeResultWithCompanionObject(resTree, annottees: _*))
printTree(force = extractArgumentsDetail._1, res.tree)
res
}
val existsCanEqual = getClassMemberDefDefs(annotteeClassDefinitions) exists {
case q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr" if tname.toString() == "canEqual" && paramss.nonEmpty =>
val params = paramss.asInstanceOf[List[List[Tree]]].flatten.map(pp => getMethodParamName(pp))
params.exists(p => p.decodedName.toString == "Any")
case _ => false
}
/**
* Extract the internal fields of members belonging to the class.
*/
private def getClassMemberAllTermName(annotteeClassDefinitions: Seq[Tree]): Seq[TermName] = {
getClassMemberValDefs(annotteeClassDefinitions).filter(_ match {
case q"$mods var $tname: $tpt = $expr" if !extractArgumentsDetail._2.contains(tname.asInstanceOf[TermName].decodedName.toString) => true
case q"$mods val $tname: $tpt = $expr" if !extractArgumentsDetail._2.contains(tname.asInstanceOf[TermName].decodedName.toString) => true
case q"$mods val $pat = $expr" if !extractArgumentsDetail._2.contains(pat.asInstanceOf[TermName].decodedName.toString) => true
case q"$mods var $pat = $expr" if !extractArgumentsDetail._2.contains(pat.asInstanceOf[TermName].decodedName.toString) => true
case _ => false
}).map(f => getFieldTermName(f))
}
// + super.hashCode
val SDKClasses = Set("java.lang.Object", "scala.AnyRef")
val canEqualsExistsInSuper = if (superClasses.nonEmpty && !superClasses.forall(sc => SDKClasses.contains(sc.toString()))) { // TODO better way
true
} else false
// equals method
private def getEqualsMethod(className: TypeName, termNames: Seq[TermName], superClasses: Seq[Tree], annotteeClassDefinitions: Seq[Tree]): Tree = {
val existsCanEqual = getClassMemberDefDefs(annotteeClassDefinitions) exists {
case q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr" if tname.toString() == "canEqual" && paramss.nonEmpty =>
val params = paramss.asInstanceOf[List[List[Tree]]].flatten.map(pp => getMethodParamName(pp))
params.exists(p => p.decodedName.toString == "Any")
case _ => false
}
val SDKClasses = Set("java.lang.Object", "scala.AnyRef")
val canEqualsExistsInSuper = if (superClasses.nonEmpty && !superClasses.forall(sc => SDKClasses.contains(sc.toString()))) { // TODO better way
true
} else false
// equals template
def ==(termNames: Seq[TermName]): Tree = {
val getEqualsExpr = (termName: TermName) => {
q"this.$termName.equals(t.$termName)"
}
val equalsExprs = termNames.map(getEqualsExpr)
val modifiers = if (canEqualsExistsInSuper) Modifiers(Flag.OVERRIDE, typeNames.EMPTY, List()) else Modifiers(NoFlags, typeNames.EMPTY, List())
val canEqual = if (existsCanEqual) q"" else q"$modifiers def canEqual(that: Any) = that.isInstanceOf[$className]"
q"""
val getEqualsExpr = (termName: TermName) => {
q"this.$termName.equals(t.$termName)"
}
val equalsExprs = termNames.map(getEqualsExpr)
val modifiers = if (canEqualsExistsInSuper) Modifiers(Flag.OVERRIDE, typeNames.EMPTY, List()) else Modifiers(NoFlags, typeNames.EMPTY, List())
val canEqual = if (existsCanEqual) q"" else q"$modifiers def canEqual(that: Any) = that.isInstanceOf[$className]"
q"""
$canEqual
override def equals(that: Any): Boolean =
......@@ -107,55 +105,51 @@ object equalsAndHashCodeMacro {
case _ => false
}
"""
}
}
// hashcode template
def ##(termNames: Seq[TermName]): Tree = {
// the algorithm see https://alvinalexander.com/scala/how-to-define-equals-hashcode-methods-in-scala-object-equality/
// We use default 1.
if (!canEqualsExistsInSuper) {
q"""
private def getHashcodeMethod(termNames: Seq[TermName], superClasses: Seq[Tree]): Tree = {
// we append super.hashCode by `+`
val SDKClasses = Set("java.lang.Object", "scala.AnyRef")
val canEqualsExistsInSuper = if (superClasses.nonEmpty && !superClasses.forall(sc => SDKClasses.contains(sc.toString()))) { // TODO better way
true
} else false
// the algorithm see https://alvinalexander.com/scala/how-to-define-equals-hashcode-methods-in-scala-object-equality/
// We use default 1.
if (!canEqualsExistsInSuper) {
q"""
override def hashCode(): Int = {
val state = Seq(..$termNames)
state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b)
}
"""
} else {
q"""
} else {
q"""
override def hashCode(): Int = {
val state = Seq(..$termNames)
state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b) + super.hashCode
}
"""
}
}
val allTernNames = allFieldsTermName ++ getClassMemberAllTermName
val hashcode = ##(allTernNames)
val equals = ==(allTernNames)
val equalsAndHashcode =
q"""
..$equals
$hashcode
"""
// return with object if it exists
val resTree = annotateeClass match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
val originalStatus = q"{ ..$stats }"
val append =
q"""
..$originalStatus
..$equalsAndHashcode
"""
q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${append} }"
}
c.Expr[Any](treeResultWithCompanionObject(resTree, annottees: _*))
}
}
val resTree = handleWithImplType(annottees: _*)(modifiedDeclaration)
printTree(force = args._1, resTree.tree)
resTree
override def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef]): Any = {
val (className, annotteeClassParams, annotteeClassDefinitions, superClasses) = classDecl match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = extractArgumentsDetail._1)
(tpname.asInstanceOf[TypeName], paramss.asInstanceOf[List[List[Tree]]], stats.asInstanceOf[Seq[Tree]], parents.asInstanceOf[Seq[Tree]])
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
}
val ctorFieldNames = annotteeClassParams.flatten.filter(cf => classParamsIsPrivate(cf))
val allFieldsTermName = ctorFieldNames.map(f => getFieldTermName(f))
val allTernNames = allFieldsTermName ++ getClassMemberAllTermName(annotteeClassDefinitions)
val hash = getHashcodeMethod(allTernNames, superClasses)
val equals = getEqualsMethod(className, allTernNames, superClasses, annotteeClassDefinitions)
c.Expr(
q"""
..$equals
$hash
""")
}
}
}
......@@ -35,46 +35,42 @@ object jsonMacro {
import c.universe._
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
def jsonFormatter(className: TypeName, fields: List[Tree]): Tree = {
fields.length match {
case 0 => c.abort(c.enclosingPosition, "Cannot create json formatter for case class with no fields")
case _ =>
c.info(c.enclosingPosition, s"jsonFormatter className: $className, field length: ${fields.length}", force = true)
q"implicit val jsonAnnotationFormat = play.api.libs.json.Json.format[$className]"
}
}
// The dependent type need aux-pattern in scala2. Now let's get around this.
def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val (className, fields) = classDecl match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$bases { ..$body }" =>
if (!mods.asInstanceOf[Modifiers].hasFlag(Flag.CASE)) {
c.abort(c.enclosingPosition, s"Annotation is only supported on case class. classDef: $classDecl, mods: $mods")
} else {
c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = true)
(tpname, paramss.asInstanceOf[List[List[Tree]]])
}
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
}
c.info(c.enclosingPosition, s"modifiedDeclaration className: $className, fields: $fields", force = true)
val cName = className.asInstanceOf[TypeName]
val format = jsonFormatter(cName, fields.flatten)
val compDecl = modifiedCompanion(compDeclOpt, format, cName)
c.info(c.enclosingPosition, s"format: $format, compDecl: $compDecl", force = true)
// Return both the class and companion object declarations
c.Expr(
q"""
$classDecl
$compDecl
""")
private def jsonFormatter(className: TypeName, fields: List[Tree]): Tree = {
fields.length match {
case 0 => c.abort(c.enclosingPosition, "Cannot create json formatter for case class with no fields")
case _ =>
c.info(c.enclosingPosition, s"jsonFormatter className: $className, field length: ${fields.length}", force = true)
q"implicit val jsonAnnotationFormat = play.api.libs.json.Json.format[$className]"
}
}
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
val resTree = handleWithImplType(annottees: _*)(modifiedDeclaration)
printTree(force = true, resTree.tree)
resTree
}
override def modifiedDeclaration(classDecl: c.universe.ClassDef, compDeclOpt: Option[c.universe.ModuleDef]): Any = {
val (className, fields) = classDecl match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$bases { ..$body }" =>
if (!mods.asInstanceOf[Modifiers].hasFlag(Flag.CASE)) {
c.abort(c.enclosingPosition, s"Annotation is only supported on case class. classDef: $classDecl, mods: $mods")
} else {
c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = true)
(tpname.asInstanceOf[TypeName], paramss.asInstanceOf[List[List[Tree]]])
}
case _ => c.abort(c.enclosingPosition, s"${ErrorMessage.ONLY_CLASS} classDef: $classDecl")
}
val format = jsonFormatter(className, fields.flatten)
val compDecl = modifiedCompanion(compDeclOpt, format, className)
c.info(c.enclosingPosition, s"format: $format, compDecl: $compDecl", force = true)
// Return both the class and companion object declarations
c.Expr(
q"""
$classDecl
$compDecl
""")
}
}
}
......@@ -26,6 +26,7 @@ import io.github.dreamylost.logs.LogType
import io.github.dreamylost.logs.LogType._
import scala.reflect.macros.whitebox
import io.github.dreamylost.logs
/**
*
......@@ -39,37 +40,33 @@ object logMacro {
import c.universe._
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
def getLogType(logType: Tree): LogType = {
if (logType.children.exists(t => t.toString().contains(PACKAGE))) {
evalTree(logType.asInstanceOf[Tree]) // TODO remove asInstanceOf
} else {
LogType.getLogType(logType.toString())
}
}
val args: (Boolean, LogType) = extractArgumentsTuple2 {
case q"new log(logType=$logType)" =>
val tpe = getLogType(logType.asInstanceOf[Tree])
(false, tpe)
case q"new log(verbose=$verbose)" => (evalTree(verbose.asInstanceOf[Tree]), LogType.JLog)
case q"new log($logType)" =>
val tpe = getLogType(logType.asInstanceOf[Tree])
(false, tpe)
case q"new log(verbose=$verbose, logType=$logType)" =>
val tpe = getLogType(logType.asInstanceOf[Tree])
(evalTree(verbose.asInstanceOf[Tree]), tpe)
case q"new log()" => (false, LogType.JLog)
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
}
private val extractArgumentsDetail: (Boolean, logs.LogType.Value) = extractArgumentsTuple2 {
case q"new log(logType=$logType)" =>
val tpe = getLogType(logType.asInstanceOf[Tree])
(false, tpe)
case q"new log(verbose=$verbose)" => (evalTree(verbose.asInstanceOf[Tree]), LogType.JLog)
case q"new log(verbose=$verbose, logType=$logType)" =>
val tpe = getLogType(logType.asInstanceOf[Tree])
(evalTree(verbose.asInstanceOf[Tree]), tpe)
case q"new log()" => (false, LogType.JLog)
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
}
c.info(c.enclosingPosition, s"annottees: $annottees, args: $args", force = args._1)
private def getLogType(logType: Tree): LogType = {
if (logType.children.exists(t => t.toString().contains(PACKAGE))) {
evalTree(logType.asInstanceOf[Tree]) // TODO remove asInstanceOf
} else {
LogType.getLogType(logType.toString())
}
}
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
val logTree = annottees.map(_.tree) match {
// Match a class, and expand, get class/object name.
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: _ =>
LogType.getLogImpl(args._2).getTemplate(c)(tpname.asInstanceOf[TypeName].toTermName.decodedName.toString, isClass = true)
LogType.getLogImpl(extractArgumentsDetail._2).getTemplate(c)(tpname.asInstanceOf[TypeName].toTermName.decodedName.toString, isClass = true)
case q"$mods object $tpname extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: _ =>
LogType.getLogImpl(args._2).getTemplate(c)(tpname.asInstanceOf[TermName].decodedName.toString, isClass = false)
LogType.getLogImpl(extractArgumentsDetail._2).getTemplate(c)(tpname.asInstanceOf[TermName].decodedName.toString, isClass = false)
case _ => c.abort(c.enclosingPosition, s"Annotation is only supported on class or object.")
}
......@@ -85,8 +82,9 @@ object logMacro {
}
val res = treeResultWithCompanionObject(resTree, annottees: _*)
printTree(force = args._1, res)
printTree(force = extractArgumentsDetail._1, res)
c.Expr[Any](resTree)
}
}
}
......@@ -35,29 +35,29 @@ object synchronizedMacro {
import c.universe._
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
val args: (Boolean, String) = extractArgumentsTuple2 {
case q"new synchronized(verbose=$verbose, lockedName=$lock)" => (evalTree(verbose.asInstanceOf[Tree]), evalTree(lock.asInstanceOf[Tree]))
case q"new synchronized(lockedName=$lock)" => (false, evalTree(lock.asInstanceOf[Tree]))
case q"new synchronized()" => (false, "this")
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
}
private val extractArgumentsDetail: (Boolean, String) = extractArgumentsTuple2 {
case q"new synchronized(verbose=$verbose, lockedName=$lock)" => (evalTree(verbose.asInstanceOf[Tree]), evalTree(lock.asInstanceOf[Tree]))
case q"new synchronized(lockedName=$lock)" => (false, evalTree(lock.asInstanceOf[Tree]))
case q"new synchronized()" => (false, "this")
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
}
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
val resTree = annottees map (_.tree) match {
// Match a method, and expand.
case _@ q"$modrs def $tname[..$tparams](...$paramss): $tpt = $expr" :: _ =>
if (args._2 != null) {
if (args._2 == "this") {
if (extractArgumentsDetail._2 != null) {
if (extractArgumentsDetail._2 == "this") {
q"""def $tname[..$tparams](...$paramss): $tpt = ${This(TypeName(""))}.synchronized { $expr }"""
} else {
q"""def $tname[..$tparams](...$paramss): $tpt = ${TermName(args._2)}.synchronized { $expr }"""
q"""def $tname[..$tparams](...$paramss): $tpt = ${TermName(extractArgumentsDetail._2)}.synchronized { $expr }"""
}
} else {
c.abort(c.enclosingPosition, "Invalid args, lockName cannot be a null!")
}
case _ => c.abort(c.enclosingPosition, "Invalid annotation target: not a method")
}
printTree(args._1, resTree)
printTree(extractArgumentsDetail._1, resTree)
c.Expr[Any](resTree)
}
}
......
......@@ -37,37 +37,33 @@ object toStringMacro {
import c.universe._
private val extractArgumentsDetail: (Boolean, Boolean, Boolean, Boolean) = extractArgumentsTuple4 {
case q"new toString(includeInternalFields=$bb, includeFieldNames=$cc, callSuper=$dd)" =>
(false, evalTree(bb.asInstanceOf[Tree]), evalTree(cc.asInstanceOf[Tree]), evalTree(dd.asInstanceOf[Tree]))
case q"new toString(verbose=$aa, includeInternalFields=$bb, includeFieldNames=$cc)" =>
(evalTree(aa.asInstanceOf[Tree]), evalTree(bb.asInstanceOf[Tree]), evalTree(cc.asInstanceOf[Tree]), false)
case q"new toString($aa, $bb, $cc)" =>
(evalTree(aa.asInstanceOf[Tree]), evalTree(bb.asInstanceOf[Tree]), evalTree(cc.asInstanceOf[Tree]), false)
case q"new toString(verbose=$aa, includeInternalFields=$bb, includeFieldNames=$cc, callSuper=$dd)" =>
(evalTree(aa.asInstanceOf[Tree]), evalTree(bb.asInstanceOf[Tree]), evalTree(cc.asInstanceOf[Tree]), evalTree(dd.asInstanceOf[Tree]))
case q"new toString($aa, $bb, $cc, $dd)" =>
(evalTree(aa.asInstanceOf[Tree]), evalTree(bb.asInstanceOf[Tree]), evalTree(cc.asInstanceOf[Tree]), evalTree(dd.asInstanceOf[Tree]))
case q"new toString(includeInternalFields=$bb, includeFieldNames=$cc)" =>
(false, evalTree(bb.asInstanceOf[Tree]), evalTree(cc.asInstanceOf[Tree]), false)
case q"new toString(includeInternalFields=$bb)" =>
(false, evalTree(bb.asInstanceOf[Tree]), true, false)
case q"new toString(includeFieldNames=$cc)" =>
(false, true, evalTree(cc.asInstanceOf[Tree]), false)
case q"new toString()" => (false, true, true, false)
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
}
override def impl(annottees: c.universe.Expr[Any]*): c.universe.Expr[Any] = {
// extract parameters of annotation, must in order
val arg: (Boolean, Boolean, Boolean, Boolean) = extractArgumentsTuple4 {
case q"new toString(includeInternalFields=$bb, includeFieldNames=$cc, callSuper=$dd)" =>
(false, evalTree(bb.asInstanceOf[Tree]), evalTree(cc.asInstanceOf[Tree]), evalTree(dd.asInstanceOf[Tree]))
case q"new toString($aa, $bb, $cc)" =>
(evalTree(aa.asInstanceOf[Tree]), evalTree(bb.asInstanceOf[Tree]), evalTree(cc.asInstanceOf[Tree]), false)
case q"new toString(verbose=$aa, includeInternalFields=$bb, includeFieldNames=$cc, callSuper=$dd)" =>
(evalTree(aa.asInstanceOf[Tree]), evalTree(bb.asInstanceOf[Tree]), evalTree(cc.asInstanceOf[Tree]), evalTree(dd.asInstanceOf[Tree]))
case q"new toString(verbose=$aa, includeInternalFields=$bb, includeFieldNames=$cc)" =>
(evalTree(aa.asInstanceOf[Tree]), evalTree(bb.asInstanceOf[Tree]), evalTree(cc.asInstanceOf[Tree]), false)
case q"new toString($aa, $bb, $cc, $dd)" =>
(evalTree(aa.asInstanceOf[Tree]), evalTree(bb.asInstanceOf[Tree]), evalTree(cc.asInstanceOf[Tree]), evalTree(dd.asInstanceOf[Tree]))
case q"new toString(includeInternalFields=$bb, includeFieldNames=$cc)" =>
(false, evalTree(bb.asInstanceOf[Tree]), evalTree(cc.asInstanceOf[Tree]), false)
case q"new toString(includeInternalFields=$bb)" =>
(false, evalTree(bb.asInstanceOf[Tree]), true, false)
case q"new toString(includeFieldNames=$cc)" =>
(false, true, evalTree(cc.asInstanceOf[Tree]), false)
case q"new toString()" => (false, true, true, false)
case _ => c.abort(c.enclosingPosition, ErrorMessage.UNEXPECTED_PATTERN)
}
val argument = Argument(arg._1, arg._2, arg._3, arg._4)
c.info(c.enclosingPosition, s"toString annottees: $annottees", force = argument.verbose)
val argument = Argument(extractArgumentsDetail._1, extractArgumentsDetail._2, extractArgumentsDetail._3, extractArgumentsDetail._4)
// Check the type of the class, which can only be defined on the ordinary class
val annotateeClass: ClassDef = checkAndGetClassDef(annottees: _*)
val isCase: Boolean = isCaseClass(annotateeClass)
c.info(c.enclosingPosition, s"impl argument: $argument, isCase: $isCase", force = argument.verbose)
val resMethod = toStringTemplateImpl(argument, annotateeClass)
val resTree = annotateeClass match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
......@@ -79,7 +75,7 @@ object toStringMacro {
c.Expr[Any](res)
}
def printField(argument: Argument, lastParam: Option[String], field: Tree): Tree = {
private def printField(argument: Argument, lastParam: Option[String], field: Tree): Tree = {
// Print one field as <name of the field>+"="+fieldName
if (argument.includeFieldNames) {
lastParam.fold(q"$field") { lp =>
......@@ -99,7 +95,6 @@ object toStringMacro {
case _ => if (field.toString() != lp) q"""$field+${", "}""" else q"""$field"""
}
}
}
}
......@@ -108,8 +103,7 @@ object toStringMacro {
val (className, annotteeClassParams, superClasses, annotteeClassDefinitions) = {
annotateeClass match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
c.info(c.enclosingPosition, s"parents: $parents", force = argument.verbose)
(tpname, paramss.asInstanceOf[List[List[Tree]]], parents, stats.asInstanceOf[List[Tree]])
(tpname.asInstanceOf[TypeName], paramss.asInstanceOf[List[List[Tree]]], parents, stats.asInstanceOf[List[Tree]])
}
}
// Check the type of the class, whether it already contains its own toString
......@@ -129,8 +123,6 @@ object toStringMacro {
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()}, 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 {
......@@ -139,7 +131,7 @@ object toStringMacro {
}
val paramsWithName = member.foldLeft(q"${""}")((res, acc) => q"$res + ${printField(argument, lastParam, acc)}")
//scala/bug https://github.com/scala/bug/issues/3967 not be 'Foo(i=1,j=2)' in standard library
val toString = q"""override def toString: String = ${className.toString()} + ${"("} + $paramsWithName + ${")"}"""
val toString = q"""override def toString: String = ${className.toTermName.decodedName.toString} + ${"("} + $paramsWithName + ${")"}"""
// Have super class ?
if (argument.callSuper && superClasses.nonEmpty) {
......@@ -150,7 +142,7 @@ object toStringMacro {
superClassDef.fold(toString)(_ => {
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)"
q"override def toString: String = StringContext(${className.toTermName.decodedName.toString} + ${"("} + $superClass, ${if (member.nonEmpty) ", " else ""}+$paramsWithName + ${")"}).s(super.toString)"
}
)
} else {
......
......@@ -36,7 +36,7 @@ class ApplyTest extends AnyFlatSpec with Matchers {
// int: Int => private[this] val int: Int = _;
// val j: Int => val j: Int = _;
// apply => def apply(int: Int, j: Int, k: Option[String] = None, t: Option[Long] = Some(1L)): A = new A(int, j, k, t)
"""@apply(verbose = true) class C2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))(o: Int = 1)""" should compile
"""@toString @apply class A(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))""" should compile
@toString
@apply class A2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))
......@@ -76,6 +76,8 @@ class ApplyTest extends AnyFlatSpec with Matchers {
@toString
@apply class B4[T, U](int: T, val j: U)
println(B4("helloworld", 2))
}
"apply5" should "failed when input not invalid" in {
"""@apply(true) class C2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))(o: Int = 1)""" shouldNot compile
}
}
......@@ -32,7 +32,7 @@ import org.scalatest.matchers.should.Matchers
*/
class ConstructorTest extends AnyFlatSpec with Matchers {
"constructor1" should "failed at object" in {
"constructor1" should "failed" in {
""" @constructor
| object A2 {
| private val a: Int = 1
......@@ -50,6 +50,16 @@ class ConstructorTest extends AnyFlatSpec with Matchers {
| }
| A2(1, 2, None, None).c
| """.stripMargin shouldNot compile
""" @apply @toString @builder @constructor(excludeFields=Seq("c"), verbose = true) //verbose should in front
| class A2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L)) {
| private val a: Int = 1
| var b: Int = 1
| protected var c: Int = _
|
| def helloWorld: String = "hello world"
| }
| """.stripMargin shouldNot compile
}
"constructor2" should "ok at class" in {
......
......@@ -135,4 +135,23 @@ class EqualsAndHashCodeTest extends AnyFlatSpec with Matchers {
| }
|""".stripMargin should compile
}
"equals5" should "failed when input not in order" in {
"""
| @equalsAndHashCode(excludeFields = Nil, verbose = true)
| class Employee4(name: String, age: Int, var role: String) extends Person(name, age) {
| val i = 0
| def hello: String = ???
| }
|""".stripMargin shouldNot compile
"""
| @equalsAndHashCode(verbose = true, excludeFields = Seq("i"))
| class Employee4(name: String, age: Int, var role: String) extends Person(name, age) {
| val i = 0
| def hello: String = ???
| }
|""".stripMargin should compile
}
}
......@@ -116,7 +116,7 @@ class LogTest extends AnyFlatSpec with Matchers {
"""@log(logType=io.github.dreamylost.logs.LogType.Slf4j) class TestClass5(val i: Int = 0, var j: Int)""" should compile
"""@log(verbose=true, logType=io.github.dreamylost.logs.LogType.Slf4j) class TestClass6(val i: Int = 0, var j: Int)""" should compile
"""@log(verbose=true, logType=io.github.dreamylost.logs.LogType.Slf4j) class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }""" should compile
"""@log(io.github.dreamylost.logs.LogType.Slf4j) class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }""" should compile //default verbose is false
"""@log(logType = io.github.dreamylost.logs.LogType.Slf4j) class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }""" should compile //default verbose is false
}
"log8 slf4j" should "ok on class and has object" in {
......@@ -130,14 +130,14 @@ class LogTest extends AnyFlatSpec with Matchers {
"""@log(logType=io.github.dreamylost.logs.LogType.Slf4j) class TestClass5(val i: Int = 0, var j: Int)""" should compile
"""@log(verbose=true, logType=io.github.dreamylost.logs.LogType.Slf4j) class TestClass6(val i: Int = 0, var j: Int)""" should compile
"""@log(verbose=true, logType=io.github.dreamylost.logs.LogType.Slf4j) class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }""" should compile
"""@log(io.github.dreamylost.logs.LogType.Slf4j) @builder class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }
| @log(io.github.dreamylost.logs.LogType.Slf4j) object TestClass6 { log.info("hello world");builder() }""".stripMargin should compile //default verbose is false
"""@log(logType = io.github.dreamylost.logs.LogType.Slf4j) @builder class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }
| @log(logType = io.github.dreamylost.logs.LogType.Slf4j) object TestClass6 { log.info("hello world");builder() }""".stripMargin should compile //default verbose is false
}
"log9 slf4j" should "ok on class and it object" in {
"""
|@log(io.github.dreamylost.logs.LogType.Slf4j) @builder class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }
|@log(io.github.dreamylost.logs.LogType.Slf4j) object TestClass6 { log.info("hello world"); builder()}
|@log(logType = io.github.dreamylost.logs.LogType.Slf4j) @builder class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }
|@log(logType = io.github.dreamylost.logs.LogType.Slf4j) object TestClass6 { log.info("hello world"); builder()}
|""".stripMargin should compile
}
......@@ -162,22 +162,22 @@ class LogTest extends AnyFlatSpec with Matchers {
"log11 slf4j" should "ok on class and it object" in {
"""
| @log(io.github.dreamylost.logs.LogType.Slf4j)
| @log(logType = io.github.dreamylost.logs.LogType.Slf4j)
| @builder class TestClass6(val i: Int = 0, var j: Int) {
| log.info("hello world")
| }
| @log(logType = io.github.dreamylost.logs.LogType.Slf4j) object TestClass6 {
|@log(logType = io.github.dreamylost.logs.LogType.Slf4j) object TestClass6 {
| log.info("hello world"); builder()
| }
|""".stripMargin should compile
"""
| @builder
| @log(io.github.dreamylost.logs.LogType.Slf4j)
| @log(logType = io.github.dreamylost.logs.LogType.Slf4j)
| class TestClass6(val i: Int = 0, var j: Int) {
| log.info("hello world")
| }
| @log(logType = io.github.dreamylost.logs.LogType.Slf4j) object TestClass6 {
|@log(logType = io.github.dreamylost.logs.LogType.Slf4j) object TestClass6 {
| log.info("hello world"); builder()
| }
|""".stripMargin should compile
......@@ -202,4 +202,13 @@ class LogTest extends AnyFlatSpec with Matchers {
}
}
"log12 slf4j" should "failed when input not in order" in {
"""
| import io.github.dreamylost.logs.LogType
| @log(logType = LogType.Slf4j, verbose = true)
| @builder class TestClass6(val i: Int = 0, var j: Int) {
| log.info("hello world")
| }
|""".stripMargin shouldNot compile
}
}
......@@ -58,7 +58,7 @@ class SynchronizedTest extends AnyFlatSpec with Matchers {
val obj = new Object
@synchronized(lockedName = "obj")
@synchronized(verbose = true, lockedName = "obj")
def getStr3(k: Int): String = {
k + ""
}
......@@ -77,6 +77,9 @@ class SynchronizedTest extends AnyFlatSpec with Matchers {
this.synchronized(k + "")
}
}
}
"synchronized3" should "fail when obj not exists or it was used on fields" in {
"""
@synchronized(lockedName = "obj")
......@@ -89,4 +92,14 @@ class SynchronizedTest extends AnyFlatSpec with Matchers {
""" shouldNot compile
}
"synchronized4" should "failed when input not in order" in {
"""
| val obj = new Object
| @synchronized(lockedName = "obj", verbose = true)
| def getStr3(k: Int): String = {
| k + ""
| }
|""".stripMargin shouldNot compile
}
}
......@@ -44,7 +44,7 @@ class ToStringTest extends AnyFlatSpec with Matchers {
}
"toString2" should "contains internal field and with name" in {
@toString(true, true, true)
@toString(includeInternalFields = true)
class TestClass(val i: Int = 0, var j: Int) {
val y: Int = 0
var z: String = "hello"
......@@ -109,7 +109,7 @@ class ToStringTest extends AnyFlatSpec with Matchers {
}
"toString7" should "case class contains internal field and with name" in {
@toString(true, true, true)
@toString(includeFieldNames = true)
case class TestClass(i: Int = 0, var j: Int) {
val y: Int = 0
var z: String = "hello"
......@@ -234,6 +234,12 @@ class ToStringTest extends AnyFlatSpec with Matchers {
println(s1)
assert(s1 == "TestClass2(j=1)")
@toString(verbose = true, includeInternalFields = true, includeFieldNames = true)
case class TestClass2_1(j: Int = 1) extends TestClass1(1)
@toString(true, true, true, false)
case class TestClass2_2(j: Int = 1) extends TestClass1(1)
@toString(includeInternalFields = true, includeFieldNames = true)
case class TestClass3(j: Int) extends TestClass1(j)
val s2 = TestClass3(0).toString
......@@ -282,4 +288,12 @@ class ToStringTest extends AnyFlatSpec with Matchers {
assert(s5.startsWith("TestClass5(super=io.github.dreamylost.ToStringTes") && s5.endsWith("1)"))
}
"toString17" should "failed when input not in order" in {
"""
| import io.github.dreamylost.logs.LogType
| @toString(includeFieldNames = false, callSuper = true, verbose = true)
| class TestClass6(val i: Int = 0, var j: Int)
|""".stripMargin shouldNot compile
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册