Summoning an instance by means of a method context parameter allows to customize the error message declaratively annotating the parameter with @implicitNotFound which requires a literal string that supports a type placeholder.
import scala.annotation.implicitNotFound
inline def singleInhabitantOf[A](using @implicitNotFound("The received type ${A} is not a singleton") voA: ValueOf[A]): A =
voA.value
singleInhabitantOf[Option[Int]] // Compile error: The received type Option[Int] is not a singleton
If instead the summoning is implemented with summonFrom
, we can customize the error message imperatively calling the error
method which also requires a literal string but has no type placeholder support.
import scala.compiletime.{summonFrom, error}
inline def singleInhabitantOf2[A]: A = summonFrom {
case voA: ValueOf[A] => voA.value
case _ => error("The received type ${A} is not a singleton")
}
singleInhabitantOf2[Option[Int]] // compiler error: The received type ${A} is not a singleton
Is there a way, other than using macros, to summon an implicit instance in the body (like summonFrom
does) that has type placeholder support or, much better, dynamic string interpolation?
Many standard inline methods (error
, summonFrom
, summonInline
, constValue
etc.) are actually implemented as macros or compiler internals.
For example you seem to have nothing against using error
. It's implemented in compiler internals. Suppose it wouldn't be implemented there. Then we'd have to use our own macro
import scala.quoted.{Quotes, Expr, quotes}
inline def error(inline msg: String): Nothing = ${errorImpl('msg)}
def errorImpl(msg: Expr[String])(using Quotes): Expr[Nothing] =
import quotes.reflect.*
report.errorAndAbort(msg.valueOrAbort)
There is shapeless3.typeable.Typeable[A].describe
but it's not inline, so can't be used in error
.
summonFrom { case m: Mirror.Of[A] => error("The received type " + constValue[m.MirroredLabel] + " is not a singleton") }
can be used for sealed traits and case classes but not arbitrary A
.
I can't find this in standard library (although it's worth to be there) but you can define your own small macro and use it everywhere in ordinary inline methods
// different file
import scala.quoted.{Quotes, Type, Expr}
inline def typeOf[A]: String = ${typeOfImpl[A]}
def typeOfImpl[A: Type](using Quotes): Expr[String] = Expr(Type.show[A])
import scala.compiletime.{summonFrom, error}
// ordinary inline method, not macro
inline def singleInhabitantOf2[A]: A = summonFrom {
case voA: ValueOf[A] => voA.value
case _ => error("The received type " + typeOf[A] + " is not a singleton")
}
error
argument can't be written as s"The received type ${typeOf[A]} is not a singleton"
because interpolated string is not a string literal.