When using an ecosystem with complex type classes it is easy to get lost in the large type error messages. One method to mitigate it is using a compiler plugin like scala-clippy (which is not available for Scala 3).
This PR proposes a new approach with an addition of compiletime.hasCustomShow and compiletime.CustomShow by enabling library owners to define custom compiletime show strings for their classes. These strings will be fetched by scalac when running show if the class/type that is annotated with compiletime.hasCustomShow and has an available implicit set by compiletime.CustomShow.
Example:
import scala.compiletime.{CustomShow, hasCustomShow}
@hasCustomShow
trait Foo[T]
type Baz = Foo["To Infinity And Beyond!"]
object Foo:
given CustomShow[Baz] with {
type Out = "Baz"
}
val x: Foo["To Infinity And Beyond!"] = 1 // error
Output:
val x: Foo["To Infinity And Beyond!"] = 1 // error
| ^
| Found: (1 : Int)
| Required: Baz
See draft PR here:
For discussion:
Better/alternative approaches?
Do we need also better mechanisms to customize error messages in general? Even if we manage to reduce the type signature complexity using CustomShow, maybe itās good to also have a way to define an implicit like CustomError[ErrID, T], that will replace the specific error for type T or other filtering possibilities.
Does this change require a SIP or not? Can be considered part of the dotty standard library under scala.compiletime.
In my library I can do something like (Bit, Bits[8]) <> VAR for values or types (using match type), which results with the type signature of
DFVal[DFTuple[(DFVal[Bit, Modifier[X,Y,Z,VAL]], DFVal[Bits[8], DFVal[Bit, Modifier[X,Y,Z,VAL]])], Modifier[X, Y, Z, VAR]] //The `X`, `Y` and `Z` tags can contain additional information.
This is crazy. I do not want to see the full signature, but just the syntax leading up to it: (Bit, Bits[8]) <> VAR.
BTW, the same goes for the Named Tuples SIP. Since Named Tuples are just composition of opaque types and tuples, then we can end up with a very complex type signature, instead of the condensed named tuple representation.
To date, everything added under scala.compiletime added something that cannot be done in userland, yet without any SIP.
MUnit has a pretty reasonable default behavior here, and falls back to structural printing which I think is generated via reflection when the values are different but the rendered string representation is the same.
Itās occasionally horrifically ugly (itās particularly bad at Chain), but itās much better than having to deal with a test failing with seemingly identical expected and actual values.
Having to supply both isnāt very ergonomic. Is there any problem with simply looking for the given? Obviously if someone tried to actively use it to be misleading (by writing their own given somewhere else) it would be a mess, but weāre not anticipating an adversarial situation here, right?
This is to limit redundant implicit lookup upon show. It should be quite rare to have custom type show, so it does not make sense to always look it up.
Another tangential use-case where a better representation for types would be useful is this use-case . It would be nice if we could include that use-case within this design.