0

Try to write a generic macro for deserialising case classes using uPickle read in Scala 3:

inline def parseJson[T:Type](x: Expr[String])(using Quotes): Either[String, Expr[T]] = '{
  try 
    Right(read[T]($x.toString))
  catch  
    case e: Throwable => Left(s"Deserialization error: ${e.getMessage}") 
}

get error:

  Right(read[T]($x.toString))
        ^^^^^^^^^^^^^^^^^^^^
missing argument for parameter evidence$3 of method read in trait Api: (implicit evidence$3: upickle.default.Reader[T]): T

I use the uPickle read for a bunch of case classes, is there a solution with less boilerplate code? Thanks for any help!

1 Answer 1

2

You need to:

  1. resolve the implicit and place the found value in Expr - you can expand a macro inside an inline def (just like vampyre macros from Scala 2), but NOT when you are constructing expressions with '{}, then you have to have all implicits resolved
  2. return Expr[Either[Throwable, T]] - when creating a body of a Scala 3 macro, the whole returned value has to be an Expr, otherwise you cannot unquote it within ${} (it might work as a value returned from an helper which would turn it into Expr, but ${} accepts only single call to something defined in top level scope)
  3. separate the body of Expr => Expr macro definition from its expansion - you cannot write "just" inline def something[T: Type](a: Expr[T])(using Quotes): Expr[T] = ... - body of a macro is a (non-inline) def taking Types and Exprs and Quotes and returning a single Expr. inline def has to unquote a single Expr with ${}... or just be a normal def which would be copy-pasted into the call site and resolved there. But then it unquotes no Exprs

So it's either:

// Quotes API requires separate inline def sth = ${ sthImpl }
// and def sthImpl[T: Type](a: Expr[...])(using Quotes): Expr[...]

inline def parseJson[T](x: String)(using r: Reader[T]): Either[Throwable, T] =
  ${ parseJsonImpl[T]('x)('r) }

// Since it builds a complete TASTy, it cannot "defer" implicit
// resolution to callsite. It either already gets value to put
// into using, or has to import quotes.*, quotes.reflect.* and
// then use Expr.summon[T].getOrElse(...)

def parseJsonImpl[T:Type](
  x: Expr[String]
)(
  reader: Expr[Reader[T]]
)(using Quotes): Expr[Either[String, T]] = '{
  try 
    Right(read[T]($x)(using $reader))
  catch  
    case e: Throwable => Left(s"Deserialization error: ${e.getMessage}") 
}

or

// inline def NOT using quotation API - NO Quotes, NO '{}

inline def parseJson[T: Reader](x: String): Either[String, T] = {
  try 
    // Here, you can use something like a "vampyre macros" and 
    // let the compiler expand macros at the call site
    Right(read[T](x))
  catch  
    case e: Throwable => Left(s"Deserialization error: ${e.getMessage}") 
}
2
  • Thanks a lot! Additionally (given r: Reader[T]) is necessary in the second example.
    – RobertJo
    Commented May 22 at 6:48
  • You're right, I corrected the answer. Commented May 22 at 8:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.