Maths noob
Maths noob

Reputation: 1802

Scala: pattern inside a q interpolator

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?

Upvotes: 0

Views: 74

Answers (1)

SergGr
SergGr

Reputation: 23788

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)
      }
    }
  """
  }
}

Upvotes: 1

Related Questions