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

Commit 64e48bf

Browse files
support default value (#208)
* support default value
1 parent aae4c5c commit 64e48bf

File tree

4 files changed

+123
-32
lines changed

4 files changed

+123
-32
lines changed

smt-common/src/main/scala/org/bitlap/common/MacroCache.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,6 @@ object MacroCache {
4444
lazy val classFieldNameMapping: mutable.Map[Int, mutable.Map[String, String]] = mutable.Map.empty
4545

4646
lazy val classFieldTypeMapping: mutable.Map[Int, mutable.Map[String, Any]] = mutable.Map.empty
47+
48+
lazy val classFieldDefaultValueMapping: mutable.Map[Int, mutable.Map[String, Any]] = mutable.Map.empty
4749
}

smt-common/src/main/scala/org/bitlap/common/Transformable.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ class Transformable[From, To] {
6666
): Transformable[From, To] =
6767
macro TransformerMacro.mapNameImpl[From, To, FromField, ToField]
6868

69+
/** Defines default value for missing field to successfully create `To` object. This method has the lowest priority.
70+
*
71+
* Only the `selectToField` field does not have the same name found in the `From` and is not in the name mapping.
72+
*/
73+
@unchecked
74+
def setDefaultValue[ToField](selectToField: To => ToField, defaultValue: ToField): Transformable[From, To] =
75+
macro TransformerMacro.setDefaultValueImpl[From, To, ToField]
76+
6977
def instance: Transformer[From, To] = macro TransformerMacro.instanceImpl[From, To]
7078

7179
}

smt-common/src/main/scala/org/bitlap/common/TransformerMacro.scala

Lines changed: 84 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,13 @@ class TransformerMacro(override val c: whitebox.Context) extends AbstractMacroPr
3333

3434
import c.universe._
3535

36-
protected val packageName = q"_root_.org.bitlap.common"
37-
private val builderFunctionPrefix = "_TransformableFunction$"
38-
private val annoBuilderPrefix = "_AnonObjectTransformable$"
39-
private val fromTermName = TermName("from")
36+
import scala.collection.immutable
37+
38+
protected val packageName = q"_root_.org.bitlap.common"
39+
private val builderFunctionPrefix = "_TransformableFunction$"
40+
private val builderDefaultValuePrefix$ = "_TransformableDefaultValue$"
41+
private val annoBuilderPrefix = "_AnonObjectTransformable$"
42+
private val fromTermName = TermName("from")
4043

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

57+
def setDefaultValueImpl[From, To, ToField](
58+
selectToField: Expr[To => ToField],
59+
defaultValue: Expr[ToField]
60+
): Expr[Transformable[From, To]] = {
61+
val Function(_, Select(_, toName)) = selectToField.tree
62+
val builderId = getBuilderId(annoBuilderPrefix)
63+
MacroCache.classFieldDefaultValueMapping
64+
.getOrElseUpdate(builderId, mutable.Map.empty)
65+
.update(toName.decodedName.toString, defaultValue)
66+
val tree = q"new ${c.prefix.actualType}"
67+
exprPrintTree[Transformable[From, To]](force = false, tree)
68+
}
69+
5470
def mapNameImpl[From, To, FromField, ToField](
5571
selectFromField: Expr[From => FromField],
5672
selectToField: Expr[To => ToField]
@@ -98,14 +114,27 @@ class TransformerMacro(override val c: whitebox.Context) extends AbstractMacroPr
98114
}
99115

100116
private def getPreTree: Iterable[Tree] = {
101-
val customTrees = MacroCache.classFieldTypeMapping.getOrElse(getBuilderId(annoBuilderPrefix), mutable.Map.empty)
102-
val (_, preTrees) = customTrees.collect { case (key, expr: Expr[Tree] @unchecked) =>
117+
val customFunctionTrees = buildPreTrees(
118+
MacroCache.classFieldTypeMapping.getOrElse(getBuilderId(annoBuilderPrefix), mutable.Map.empty)
119+
)
120+
val customDefaultValueTrees = buildPreTrees(
121+
MacroCache.classFieldDefaultValueMapping.getOrElse(getBuilderId(annoBuilderPrefix), mutable.Map.empty)
122+
)
123+
124+
customFunctionTrees ++ customDefaultValueTrees
125+
}
126+
127+
private def buildPreTrees(mapping: mutable.Map[String, Any]): Iterable[Tree] = {
128+
val (_, preTrees) = mapping.collect { case (key, expr: Expr[Tree] @unchecked) =>
129+
val wrapName = (prefix: String) => TermName(prefix + key)
103130
expr.tree match {
104-
case buildFunction: Function =>
105-
val functionName = TermName(builderFunctionPrefix + key)
106-
key -> q"lazy val $functionName: ${buildFunction.tpe} = $buildFunction"
131+
case function: Function =>
132+
key -> q"lazy val ${wrapName(builderFunctionPrefix)}: ${function.tpe} = $function"
133+
case tree: Tree =>
134+
key -> q"lazy val ${wrapName(builderDefaultValuePrefix$)} = $tree"
107135
}
108136
}.unzip
137+
109138
preTrees
110139
}
111140

@@ -114,32 +143,47 @@ class TransformerMacro(override val c: whitebox.Context) extends AbstractMacroPr
114143
val fromClassName = resolveClassTypeName[From]
115144
val toClassInfo = getCaseClassFieldInfo[To]()
116145
val fromClassInfo = getCaseClassFieldInfo[From]()
117-
if (fromClassInfo.size < toClassInfo.size) {
118-
c.abort(
119-
c.enclosingPosition,
120-
s"From type: `$fromClassName` has fewer fields than To type: `$toClassName` and cannot be transformed"
121-
)
122-
}
123-
146+
val customDefaultValueMapping =
147+
MacroCache.classFieldDefaultValueMapping.getOrElse(getBuilderId(annoBuilderPrefix), mutable.Map.empty)
124148
val customFieldNameMapping =
125149
MacroCache.classFieldNameMapping.getOrElse(getBuilderId(annoBuilderPrefix), mutable.Map.empty)
126150
val customFieldTypeMapping =
127151
MacroCache.classFieldTypeMapping.getOrElse(getBuilderId(annoBuilderPrefix), mutable.Map.empty)
128-
c.info(c.enclosingPosition, s"Field Name Mapping:$customFieldNameMapping", force = true)
129-
c.info(c.enclosingPosition, s"Field Type Mapping:$customFieldTypeMapping", force = true)
152+
153+
c.info(c.enclosingPosition, s"Field default value mapping: $customDefaultValueMapping", force = true)
154+
c.info(c.enclosingPosition, s"Field name mapping: $customFieldNameMapping", force = true)
155+
c.info(c.enclosingPosition, s"Field type mapping: $customFieldTypeMapping", force = true)
156+
157+
val missingFields = toClassInfo.map(_.fieldName).filterNot(fromClassInfo.map(_.fieldName).contains)
158+
val missingExcludeMappingName = missingFields.filterNot(customFieldNameMapping.contains)
159+
if (missingExcludeMappingName.nonEmpty) {
160+
val noDefaultValueFields = missingExcludeMappingName.filterNot(customDefaultValueMapping.keySet.contains)
161+
if (noDefaultValueFields.nonEmpty) {
162+
c.abort(
163+
c.enclosingPosition,
164+
s"From type: `$fromClassName` has fewer fields than To type: `$toClassName` and cannot be transformed!" +
165+
s"\nMissing field mapping: `$fromClassName`.? => `$toClassName`.`${missingExcludeMappingName.mkString(",")}`." +
166+
s"\nPlease consider using `setName` or `setDefaultValue` method for `$toClassName`.${missingExcludeMappingName
167+
.mkString(",")}!"
168+
)
169+
}
170+
}
171+
130172
val fields = toClassInfo.map { field =>
131173
val fromFieldName = customFieldNameMapping.get(field.fieldName)
132174
val realToFieldName = fromFieldName.fold(field.fieldName)(x => x)
175+
// scalafmt: { maxColumn = 400 }
133176
fromFieldName match {
134177
case Some(fromName) if customFieldTypeMapping.contains(fromName) =>
135-
q"""${TermName(builderFunctionPrefix + fromName)}.apply(${q"$fromTermName.${TermName(realToFieldName)}"})"""
178+
q"""${TermName(field.fieldName)} = ${TermName(builderFunctionPrefix + fromName)}.apply(${q"$fromTermName.${TermName(realToFieldName)}"})"""
136179
case None if customFieldTypeMapping.contains(field.fieldName) =>
137-
q"""${TermName(builderFunctionPrefix + field.fieldName)}.apply(${q"$fromTermName.${TermName(realToFieldName)}"})"""
180+
q"""${TermName(field.fieldName)} = ${TermName(builderFunctionPrefix + field.fieldName)}.apply(${q"$fromTermName.${TermName(realToFieldName)}"})"""
138181
case _ =>
139182
checkFieldGetFieldTerm[From](
140183
realToFieldName,
141184
fromClassInfo.find(_.fieldName == realToFieldName),
142-
field
185+
field,
186+
customDefaultValueMapping
143187
)
144188
}
145189
}
@@ -153,24 +197,30 @@ class TransformerMacro(override val c: whitebox.Context) extends AbstractMacroPr
153197
private def checkFieldGetFieldTerm[From: WeakTypeTag](
154198
realFromFieldName: String,
155199
fromFieldOpt: Option[FieldInformation],
156-
toField: FieldInformation
200+
toField: FieldInformation,
201+
customDefaultValueMapping: mutable.Map[String, Any]
157202
): Tree = {
158203
val fromFieldTerm = q"$fromTermName.${TermName(realFromFieldName)}"
159204
val fromClassName = resolveClassTypeName[From]
160205

161-
if (fromFieldOpt.isEmpty) {
206+
if (fromFieldOpt.isEmpty && !customDefaultValueMapping.keySet.contains(toField.fieldName)) {
162207
c.abort(
163208
c.enclosingPosition,
164-
s"value `$realFromFieldName` is not a member of `$fromClassName`, Please consider using `setName` method!"
209+
s"The value `$realFromFieldName` is not a member of `$fromClassName`!" +
210+
s"\nPlease consider using `setDefaultValue` method!"
165211
)
166212
return fromFieldTerm
167213
}
168214

169-
val fromField = fromFieldOpt.get
170-
if (!(fromField.fieldType weak_<:< toField.fieldType)) {
171-
tryForWrapType(fromFieldTerm, fromField, toField)
172-
} else {
173-
fromFieldTerm
215+
fromFieldOpt match {
216+
case Some(fromField) if !(fromField.fieldType weak_<:< toField.fieldType) =>
217+
tryForWrapType(fromFieldTerm, fromField, toField)
218+
case Some(fromField) if fromField.fieldType weak_<:< toField.fieldType =>
219+
q"${TermName(toField.fieldName)} = $fromFieldTerm"
220+
case _ =>
221+
val value = q"""${TermName(builderDefaultValuePrefix$ + toField.fieldName)}"""
222+
q"${TermName(toField.fieldName)} = $value"
223+
174224
}
175225
}
176226

@@ -186,16 +236,18 @@ class TransformerMacro(override val c: whitebox.Context) extends AbstractMacroPr
186236
(collectionsFlags1.isVector && collectionsFlags2.isVector) ||
187237
(collectionsFlags1.isOption && collectionsFlags2.isOption))
188238
&& genericType1.nonEmpty && genericType2.nonEmpty =>
239+
// scalafmt: { maxColumn = 400 }
189240
q"""
190-
$packageName.Transformer[$fromFieldType, $toFieldType].transform($fromFieldTerm)
241+
${TermName(toField.fieldName)} = $packageName.Transformer[$fromFieldType, $toFieldType].transform($fromFieldTerm)
191242
"""
192243
case (information1, information2) =>
193244
c.warning(
194245
c.enclosingPosition,
195246
s"No implicit `Transformer` is defined for ${information1.fieldType} => ${information2.fieldType}, which may cause compilation errors!!!" +
196-
s"Please consider using `setType` method, or define an `Transformer[${information1.fieldType}, ${information2.fieldType}]` implicit !"
247+
s"\nPlease consider using `setType` method, or define an `Transformer[${information1.fieldType}, ${information2.fieldType}]` implicit !"
197248
)
198-
q"""$packageName.Transformer[${information1.fieldType}, ${information2.fieldType}].transform($fromFieldTerm)"""
249+
// scalafmt: { maxColumn = 400 }
250+
q"""${TermName(toField.fieldName)} = $packageName.Transformer[${information1.fieldType}, ${information2.fieldType}].transform($fromFieldTerm)"""
199251
}
200252

201253
}

smt-common/src/test/scala/org/bitlap/common/TransformableTest.scala

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,4 +355,33 @@ class TransformableTest extends AnyFlatSpec with Matchers {
355355
a.transform[A2].toString shouldEqual "A2(hello,1,2,None)"
356356

357357
}
358+
359+
"TransformableTest setDefaultValue" should "ok" in {
360+
case class A1(a: String, b: Int, cc: Long)
361+
case class A2(a: String, b: Int, c: Int, d: Option[String])
362+
363+
val a = A1("hello", 1, 2)
364+
365+
implicit val b: Transformer[A1, A2] = Transformable[A1, A2]
366+
.setName(_.cc, _.c)
367+
.setType[Long, Int](_.cc, fromField => fromField.toInt)
368+
.setDefaultValue(_.d, None)
369+
.instance
370+
371+
a.transform[A2].toString shouldEqual "A2(hello,1,2,None)"
372+
}
373+
374+
"TransformableTest not setDefaultValue" should "compile failed" in {
375+
"""
376+
| case class A1(a: String, b: Int, cc: Long)
377+
| case class A2(a: String, b: Int, c: Int, d: Option[String])
378+
|
379+
| val a = A1("hello", 1, 2)
380+
|
381+
| implicit val b: Transformer[A1, A2] = Transformable[A1, A2]
382+
| .setName(_.cc, _.c)
383+
| .setType[Long, Int](_.cc, fromField => fromField.toInt)
384+
| .instance
385+
|""".stripMargin shouldNot compile
386+
}
358387
}

0 commit comments

Comments
 (0)