scalainlinecompile-timescala-3

Is it possible to emit a dynamically generated compiler error message in inline methods or clauses?


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?


Solution

  • 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.