I am looking at a piece of Scala macro which provides an implicit implementation of a class. This class converts a map of field values to a case class. The macro can be found here and this is the explanation behind it.
Currently the implementation ignores redundant field provided in the input map. I want to add a method similar to fromMap
which would throw an exception if the input map has redundant entries but I am not sure if I understand it well enough.
My understanding is that toMapParams
and fromMapParams
are expressions that take the inputs and apply either Map
or the Companion object's Apply method on them.
As such, fromMapParams
should be modified to output the redundant values as a list of string in an exception of the form:
case class CaseClassRedundantFieldException[T]
(redundantFields: List[String], cause: Throwable = None.orNull)
(implicit c: ClassTag[T])
extends Exception(s"Conversion between map of data-fields to ${c.runtimeClass.asInstanceOf[T]} failed"
+ "as redundant fields were provided: $redundantFields",
cause)
I think I need to simply have something like:
def fromMap(map: Map[String, Any]): $tpe = {
val redundant vals: fields diff $map
if(vals.size > 0){ CaseClassRedundantFieldException(vals) } //(these two lines don't have the right syntax.)
$companion(..$fromMapParams )
}
How can I do this?
Unfortunately you didn't provide a Minimal, Complete, and Verifiable example of what you have now so I had to go back to what you started with. I think this modified macro does something quite similar to what you want:
def materializeMappableImpl[T: c.WeakTypeTag](c: Context): c.Expr[Mappable[T]] = {
import c.universe._
val tpe = weakTypeOf[T]
val className = tpe.typeSymbol.name.toString
val companion = tpe.typeSymbol.companion
val fields = tpe.decls.collectFirst {
case m: MethodSymbol if m.isPrimaryConstructor ⇒ m
}.get.paramLists.head
val (toMapParams, fromMapParams, fromMapParamsList) = fields.map { field ⇒
val name = field.name.toTermName
val decoded = name.decodedName.toString
val returnType = tpe.decl(name).typeSignature
(q"$decoded → t.$name", q"map($decoded).asInstanceOf[$returnType]", decoded)
}.unzip3
c.Expr[Mappable[T]] {
q"""
new Mappable[$tpe] {
private val fieldNames = scala.collection.immutable.Set[String](..$fromMapParamsList)
def toMap(t: $tpe): Map[String, Any] = Map(..$toMapParams)
def fromMap(map: Map[String, Any]): $tpe = {
val redundant = map.keys.filter(k => !fieldNames.contains(k))
if(!redundant.isEmpty) throw new IllegalArgumentException("Conversion between map of data-fields to " + $className + " failed because there are redundant fields: " + redundant.mkString("'","', ","'"))
$companion(..$fromMapParams)
}
}
"""
}
}