Description
Compiler version
3.6.2
Minimized example
This is not a minimized example because the problem is simple and this better illustrates the benefits.
Say you are using implicits to perform some non-trivial compile-time verification of the input types. In that case, you probably are checking subcomponents of the input types, not only doing simple matches against the top-level types. In this toy example you check the shape of the values part of a named tuple.
You want to offer some helpful hints to the end-user of your library, and since the validation failed on some part inside of the top-level type, you want the error message to talk about this incorrect part only.
import language.experimental.namedTuples
import NamedTuple.{NamedTuple, AnyNamedTuple}
type OnlyInts[T <: Tuple] = T match
case EmptyTuple => true
case Int *: ts => OnlyInts[ts]
case _ => false
type IsOnlyInts[T <: Tuple] = OnlyInts[T] =:= true
def f[A <: AnyNamedTuple](using @annotation.implicitNotFound("Types ${NamedTuple.DropNames[A]} contain non-ints") e: IsOnlyInts[NamedTuple.DropNames[A]]): Unit = ()
@main def main(): Unit = f[(a: Int, b: String, c: Int)]
Output Error/Warning message
[error] -- [E172] Type Error: Main.scala:13:55
[error] 13 |@main def main(): Unit = f[(a: Int, b: String, c: Int)]
[error] | ^
[error] | Types ?NamedTuple.DropNames[A] contain non-ints
[error] one error found
Why this Error/Warning was not helpful
What happened here is that the pretty printer inside implicitNotFound
has interpreted the whole NamedTuple.DropNames[A]
as a type variable name and, since it could not find it, it printed ?
.
Suggested improvement
To increase utility of annotations like implicitNotFound
for explaining users what they should improve, the compiler should interpret expressions inside ${}
as not only variables and normalize/simplify them before printing. What I would like to see here:
[error] 13 |@main def main(): Unit = f[(a: Int, b: String, c: Int)]
[error] | ^
[error] | Types (Int, String, Int) contain non-ints