Better "cannot reduce summonFrom" error message

Hello, I have this typeclass with a custom implicit Not Found message

@implicitNotFound("${A} seems to be nullable")
sealed trait NotNull[A]

I also have a given using summonFrom

inline given instance[A]: NotNull[A] = 
  summonFrom {
    case given Nullable[A] => error("Type seems to be nullable")
    case given (A <:< AnyVal) => ...
    case given (A <:< AnyRef) => ...
  }

My problem is that if none of the summonFrom cases matches, I get an ugly error message, not the custom message I defined in first code block.

Solution: Define a catchall case with error message

inline given instance[A]: NotNull[A] = 
  summonFrom {
    case given Nullable[A] => error("Type seems to be nullable.")
    case given (A <:< AnyVal) => ...
    case given (A <:< AnyRef) => ...
    case _ => error("${A} seems to be nullable")
  }

This kinda works, but there is no way of interpolating the A like implicitNotFound does.

Can we replace the “cannot reduce summonFrom” with the implicitNotFound message if it exists?
Another solution would be providing a compiletime method def nameOf[A]: String that returns the type name at the call site.

2 Likes

You can get the name by introducing an implicit from the sourcecode library, no?

Thanks for the suggestion. Unfortunately, it didn’t work because error needs a literal string.
Here is the code:

inline given instance[A](using inline A: sourcecode.Name): NotNull[A] = {
    summonFrom {
      case given Nullable[A] => error("Type seems to be nullable. Use .flatMap instead")
      case given (A <:< AnyVal) => NotNull.dummyInstance.asInstanceOf[NotNull[A]]
      case given (A <:< AnyRef) => NotNull.dummyInstance.asInstanceOf[NotNull[A]]
      case _ => error(s"$A seems to be a param or opaque, which we can't prove as NotNull. You need to propagate the evidence to the scope, problably something like `def foo[A: NotNull]` or `def foo[A](a: A)(using NotNull[A])`")
    }
  }

and here is the error message

[error] 68 | def useMapWithoutNotNullInScope[A](f: Int => A): A | Null = maybeA.map(f) // Shows a compile time error suggesting to use NotNull typeclass
[error] | ^
[error] |A literal string is expected as an argument to compiletime.error. Got root.scala.StringContext.apply(
[error] | ["",
[error] |
[error] | " seems to be a param or opaque, which we can’t prove as NotNull. You need to propagate the evidence to the scope, problably something like def foo[A: NotNull] or def foo[A](a: A)(using NotNull[A])"
[error] |
[error] | : String]*
[error] |).s(
[error] | [sourcecode.Name.apply(“useMapWithoutNotNullInScope”):sourcecode.Name : Any]*
[error] |)

Hmmm… Yeah that’s because sourcecode doesn’t return the singleton, but a String. You can utilize the same mechanism sourcecode is using internally and get the real literal, but then you need a macro.

Maybe if sourcecode was modified to return a singleton it would work. But I wonder, is string interpolation allowed at all? I’m not sure that is considered as a constant even if all its part are constant.

String interpolation doesn’t work. But String concatenation is constant folded:

scala> error(A + " seems to be nullable")
1 |error(A + " seems to be nullable")
  |^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |Foo seems to be nullable
1 Like

Pretty cool:

scala> import scala.quoted.{Type, Expr, Quotes}
     | 
     | def tpeNmeMacro[A: Type](using Quotes) = {
     |   val name = Type.show[A]
     |   Expr(name)
     | }
     | transparent inline def typeName[A] = ${tpeNmeMacro[A]}

scala> inline given instance[A]: NotNull[A] = 
     |   summonFrom {
     |     case given (A <:< AnyVal) => ???
     |     case _ => error(typeName[A] + " seems to be nullable")
     |   }
     | 

scala> summon[NotNull[String]]
1 |summon[NotNull[String]]
  |                       ^
  |                       java.lang.String seems to be nullable
3 Likes

Is it worth it to contribute this typeName macro to scala.compiletime? I can see anyone using summonFrom needs it to mimic the implicitNotFound functionality.

1 Like

Forgot to say thanks for the help! Works flawlessly.