Skip to content
This repository was archived by the owner on Mar 24, 2025. It is now read-only.

support default value #208

Merged
merged 2 commits into from
Jun 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions smt-common/src/main/scala/org/bitlap/common/MacroCache.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,6 @@ object MacroCache {
lazy val classFieldNameMapping: mutable.Map[Int, mutable.Map[String, String]] = mutable.Map.empty

lazy val classFieldTypeMapping: mutable.Map[Int, mutable.Map[String, Any]] = mutable.Map.empty

lazy val classFieldDefaultValueMapping: mutable.Map[Int, mutable.Map[String, Any]] = mutable.Map.empty
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ class Transformable[From, To] {
): Transformable[From, To] =
macro TransformerMacro.mapNameImpl[From, To, FromField, ToField]

/** Defines default value for missing field to successfully create `To` object. This method has the lowest priority.
*
* Only the `selectToField` field does not have the same name found in the `From` and is not in the name mapping.
*/
@unchecked
def setDefaultValue[ToField](selectToField: To => ToField, defaultValue: ToField): Transformable[From, To] =
macro TransformerMacro.setDefaultValueImpl[From, To, ToField]

def instance: Transformer[From, To] = macro TransformerMacro.instanceImpl[From, To]

}
Expand Down
116 changes: 84 additions & 32 deletions smt-common/src/main/scala/org/bitlap/common/TransformerMacro.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,13 @@ class TransformerMacro(override val c: whitebox.Context) extends AbstractMacroPr

import c.universe._

protected val packageName = q"_root_.org.bitlap.common"
private val builderFunctionPrefix = "_TransformableFunction$"
private val annoBuilderPrefix = "_AnonObjectTransformable$"
private val fromTermName = TermName("from")
import scala.collection.immutable

protected val packageName = q"_root_.org.bitlap.common"
private val builderFunctionPrefix = "_TransformableFunction$"
private val builderDefaultValuePrefix$ = "_TransformableDefaultValue$"
private val annoBuilderPrefix = "_AnonObjectTransformable$"
private val fromTermName = TermName("from")

def mapTypeImpl[From, To, FromField, ToField](
selectFromField: Expr[From => FromField],
Expand All @@ -51,6 +54,19 @@ class TransformerMacro(override val c: whitebox.Context) extends AbstractMacroPr
exprPrintTree[Transformable[From, To]](force = false, tree)
}

def setDefaultValueImpl[From, To, ToField](
selectToField: Expr[To => ToField],
defaultValue: Expr[ToField]
): Expr[Transformable[From, To]] = {
val Function(_, Select(_, toName)) = selectToField.tree
val builderId = getBuilderId(annoBuilderPrefix)
MacroCache.classFieldDefaultValueMapping
.getOrElseUpdate(builderId, mutable.Map.empty)
.update(toName.decodedName.toString, defaultValue)
val tree = q"new ${c.prefix.actualType}"
exprPrintTree[Transformable[From, To]](force = false, tree)
}

def mapNameImpl[From, To, FromField, ToField](
selectFromField: Expr[From => FromField],
selectToField: Expr[To => ToField]
Expand Down Expand Up @@ -98,14 +114,27 @@ class TransformerMacro(override val c: whitebox.Context) extends AbstractMacroPr
}

private def getPreTree: Iterable[Tree] = {
val customTrees = MacroCache.classFieldTypeMapping.getOrElse(getBuilderId(annoBuilderPrefix), mutable.Map.empty)
val (_, preTrees) = customTrees.collect { case (key, expr: Expr[Tree] @unchecked) =>
val customFunctionTrees = buildPreTrees(
MacroCache.classFieldTypeMapping.getOrElse(getBuilderId(annoBuilderPrefix), mutable.Map.empty)
)
val customDefaultValueTrees = buildPreTrees(
MacroCache.classFieldDefaultValueMapping.getOrElse(getBuilderId(annoBuilderPrefix), mutable.Map.empty)
)

customFunctionTrees ++ customDefaultValueTrees
}

private def buildPreTrees(mapping: mutable.Map[String, Any]): Iterable[Tree] = {
val (_, preTrees) = mapping.collect { case (key, expr: Expr[Tree] @unchecked) =>
val wrapName = (prefix: String) => TermName(prefix + key)
expr.tree match {
case buildFunction: Function =>
val functionName = TermName(builderFunctionPrefix + key)
key -> q"lazy val $functionName: ${buildFunction.tpe} = $buildFunction"
case function: Function =>
key -> q"lazy val ${wrapName(builderFunctionPrefix)}: ${function.tpe} = $function"
case tree: Tree =>
key -> q"lazy val ${wrapName(builderDefaultValuePrefix$)} = $tree"
}
}.unzip

preTrees
}

Expand All @@ -114,32 +143,47 @@ class TransformerMacro(override val c: whitebox.Context) extends AbstractMacroPr
val fromClassName = resolveClassTypeName[From]
val toClassInfo = getCaseClassFieldInfo[To]()
val fromClassInfo = getCaseClassFieldInfo[From]()
if (fromClassInfo.size < toClassInfo.size) {
c.abort(
c.enclosingPosition,
s"From type: `$fromClassName` has fewer fields than To type: `$toClassName` and cannot be transformed"
)
}

val customDefaultValueMapping =
MacroCache.classFieldDefaultValueMapping.getOrElse(getBuilderId(annoBuilderPrefix), mutable.Map.empty)
val customFieldNameMapping =
MacroCache.classFieldNameMapping.getOrElse(getBuilderId(annoBuilderPrefix), mutable.Map.empty)
val customFieldTypeMapping =
MacroCache.classFieldTypeMapping.getOrElse(getBuilderId(annoBuilderPrefix), mutable.Map.empty)
c.info(c.enclosingPosition, s"Field Name Mapping:$customFieldNameMapping", force = true)
c.info(c.enclosingPosition, s"Field Type Mapping:$customFieldTypeMapping", force = true)

c.info(c.enclosingPosition, s"Field default value mapping: $customDefaultValueMapping", force = true)
c.info(c.enclosingPosition, s"Field name mapping: $customFieldNameMapping", force = true)
c.info(c.enclosingPosition, s"Field type mapping: $customFieldTypeMapping", force = true)

val missingFields = toClassInfo.map(_.fieldName).filterNot(fromClassInfo.map(_.fieldName).contains)
val missingExcludeMappingName = missingFields.filterNot(customFieldNameMapping.contains)
if (missingExcludeMappingName.nonEmpty) {
val noDefaultValueFields = missingExcludeMappingName.filterNot(customDefaultValueMapping.keySet.contains)
if (noDefaultValueFields.nonEmpty) {
c.abort(
c.enclosingPosition,
s"From type: `$fromClassName` has fewer fields than To type: `$toClassName` and cannot be transformed!" +
s"\nMissing field mapping: `$fromClassName`.? => `$toClassName`.`${missingExcludeMappingName.mkString(",")}`." +
s"\nPlease consider using `setName` or `setDefaultValue` method for `$toClassName`.${missingExcludeMappingName
.mkString(",")}!"
)
}
}

val fields = toClassInfo.map { field =>
val fromFieldName = customFieldNameMapping.get(field.fieldName)
val realToFieldName = fromFieldName.fold(field.fieldName)(x => x)
// scalafmt: { maxColumn = 400 }
fromFieldName match {
case Some(fromName) if customFieldTypeMapping.contains(fromName) =>
q"""${TermName(builderFunctionPrefix + fromName)}.apply(${q"$fromTermName.${TermName(realToFieldName)}"})"""
q"""${TermName(field.fieldName)} = ${TermName(builderFunctionPrefix + fromName)}.apply(${q"$fromTermName.${TermName(realToFieldName)}"})"""
case None if customFieldTypeMapping.contains(field.fieldName) =>
q"""${TermName(builderFunctionPrefix + field.fieldName)}.apply(${q"$fromTermName.${TermName(realToFieldName)}"})"""
q"""${TermName(field.fieldName)} = ${TermName(builderFunctionPrefix + field.fieldName)}.apply(${q"$fromTermName.${TermName(realToFieldName)}"})"""
case _ =>
checkFieldGetFieldTerm[From](
realToFieldName,
fromClassInfo.find(_.fieldName == realToFieldName),
field
field,
customDefaultValueMapping
)
}
}
Expand All @@ -153,24 +197,30 @@ class TransformerMacro(override val c: whitebox.Context) extends AbstractMacroPr
private def checkFieldGetFieldTerm[From: WeakTypeTag](
realFromFieldName: String,
fromFieldOpt: Option[FieldInformation],
toField: FieldInformation
toField: FieldInformation,
customDefaultValueMapping: mutable.Map[String, Any]
): Tree = {
val fromFieldTerm = q"$fromTermName.${TermName(realFromFieldName)}"
val fromClassName = resolveClassTypeName[From]

if (fromFieldOpt.isEmpty) {
if (fromFieldOpt.isEmpty && !customDefaultValueMapping.keySet.contains(toField.fieldName)) {
c.abort(
c.enclosingPosition,
s"value `$realFromFieldName` is not a member of `$fromClassName`, Please consider using `setName` method!"
s"The value `$realFromFieldName` is not a member of `$fromClassName`!" +
s"\nPlease consider using `setDefaultValue` method!"
)
return fromFieldTerm
}

val fromField = fromFieldOpt.get
if (!(fromField.fieldType weak_<:< toField.fieldType)) {
tryForWrapType(fromFieldTerm, fromField, toField)
} else {
fromFieldTerm
fromFieldOpt match {
case Some(fromField) if !(fromField.fieldType weak_<:< toField.fieldType) =>
tryForWrapType(fromFieldTerm, fromField, toField)
case Some(fromField) if fromField.fieldType weak_<:< toField.fieldType =>
q"${TermName(toField.fieldName)} = $fromFieldTerm"
case _ =>
val value = q"""${TermName(builderDefaultValuePrefix$ + toField.fieldName)}"""
q"${TermName(toField.fieldName)} = $value"

}
}

Expand All @@ -186,16 +236,18 @@ class TransformerMacro(override val c: whitebox.Context) extends AbstractMacroPr
(collectionsFlags1.isVector && collectionsFlags2.isVector) ||
(collectionsFlags1.isOption && collectionsFlags2.isOption))
&& genericType1.nonEmpty && genericType2.nonEmpty =>
// scalafmt: { maxColumn = 400 }
q"""
$packageName.Transformer[$fromFieldType, $toFieldType].transform($fromFieldTerm)
${TermName(toField.fieldName)} = $packageName.Transformer[$fromFieldType, $toFieldType].transform($fromFieldTerm)
"""
case (information1, information2) =>
c.warning(
c.enclosingPosition,
s"No implicit `Transformer` is defined for ${information1.fieldType} => ${information2.fieldType}, which may cause compilation errors!!!" +
s"Please consider using `setType` method, or define an `Transformer[${information1.fieldType}, ${information2.fieldType}]` implicit !"
s"\nPlease consider using `setType` method, or define an `Transformer[${information1.fieldType}, ${information2.fieldType}]` implicit !"
)
q"""$packageName.Transformer[${information1.fieldType}, ${information2.fieldType}].transform($fromFieldTerm)"""
// scalafmt: { maxColumn = 400 }
q"""${TermName(toField.fieldName)} = $packageName.Transformer[${information1.fieldType}, ${information2.fieldType}].transform($fromFieldTerm)"""
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -355,4 +355,33 @@ class TransformableTest extends AnyFlatSpec with Matchers {
a.transform[A2].toString shouldEqual "A2(hello,1,2,None)"

}

"TransformableTest setDefaultValue" should "ok" in {
case class A1(a: String, b: Int, cc: Long)
case class A2(a: String, b: Int, c: Int, d: Option[String])

val a = A1("hello", 1, 2)

implicit val b: Transformer[A1, A2] = Transformable[A1, A2]
.setName(_.cc, _.c)
.setType[Long, Int](_.cc, fromField => fromField.toInt)
.setDefaultValue(_.d, None)
.instance

a.transform[A2].toString shouldEqual "A2(hello,1,2,None)"
}

"TransformableTest not setDefaultValue" should "compile failed" in {
"""
| case class A1(a: String, b: Int, cc: Long)
| case class A2(a: String, b: Int, c: Int, d: Option[String])
|
| val a = A1("hello", 1, 2)
|
| implicit val b: Transformer[A1, A2] = Transformable[A1, A2]
| .setName(_.cc, _.c)
| .setType[Long, Int](_.cc, fromField => fromField.toInt)
| .instance
|""".stripMargin shouldNot compile
}
}