0

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?

Maths noob
  • 1,684
  • 20
  • 42

1 Answers1

1

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)
      }
    }
  """
  }
}
SergGr
  • 23,570
  • 2
  • 30
  • 51
  • I have refactored my exception `CaseClassRedundantFieldException` to take a `weakTypeTag[T]` implicit parameter instead of `classTag`. However I'm having difficulty getting the macro to throw it. so I can't for example do: `throw RedundantFieldException[T](List("b","a"))($tpe)` why is that? – Maths noob Jan 29 '18 at 19:22
  • to put it another way, is there a way of reusing the type tag parameter in `materializeSerialisationImpl` in the body of the quasiquotes strings? – Maths noob Jan 29 '18 at 19:29
  • @Mathsnoob, my first question is why your`RedundantFieldException` needs a `ClassTag` in the first place? You can generate class name in the macro as in my example. – SergGr Jan 29 '18 at 19:30
  • only to log meaningul messages when mapping fails. ie "mapping from map to T failed" I have now replaced it with `WeakTypeTag`s. But I'm unable to throw it inside the q-Strings. – Maths noob Jan 29 '18 at 19:31
  • @Mathsnoob, so what's wrong with having name put into `RedundantFieldException` as a `String` by the marco? I believe my `IllegalArgumentException` contains the class name in the error message. – SergGr Jan 29 '18 at 19:33
  • Oh! well I suppose you could :) I had based on tags because I had defined the exceptions first. I'd still be interested however to know more about what is actually going on that stops us from having access to the implicits. and if there is a way around that. – Maths noob Jan 29 '18 at 19:38