Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implicitNotFound should allow type computation inside its pretty printer #22392

Open
belamenso opened this issue Jan 16, 2025 · 5 comments
Open
Labels
area:annotations area:reporting Error reporting including formatting, implicit suggestions, etc better-errors Issues concerned with improving confusing/unhelpful diagnostic messages itype:enhancement

Comments

@belamenso
Copy link

belamenso commented Jan 16, 2025

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
@belamenso belamenso added area:reporting Error reporting including formatting, implicit suggestions, etc better-errors Issues concerned with improving confusing/unhelpful diagnostic messages itype:enhancement stat:needs triage Every issue needs to have an "area" and "itype" label labels Jan 16, 2025
@belamenso
Copy link
Author

belamenso commented Jan 16, 2025

And also please note that moving the implicitNotFound annotation somewhere else, where type types are supposed to be already simplified, does not work either:

import language.experimental.namedTuples
import NamedTuple.{NamedTuple, AnyNamedTuple}

type OnlyInts[T <: Tuple] = T match
  case EmptyTuple => true
  case Int *: ts => OnlyInts[ts]
  case _ => false

@annotation.implicitNotFound("Types ${T} contain non-ints")
type IsOnlyInts[T <: Tuple] = OnlyInts[T] =:= true

def f[A <: AnyNamedTuple](using IsOnlyInts[NamedTuple.DropNames[A]]): Unit = ()
@main def main(): Unit = f[(a: Int, b: String, c: Int)]

This produces:

[error] -- [E172] Type Error: Main.scala:13:55 
[error] 13 |@main def main(): Unit = f[(a: Int, b: String, c: Int)]
[error]    |                                                       ^
[error]    |Types NamedTupleDecomposition.DropNames[(a : Int, b : String, c : Int)] contain non-ints

perhaps because of #22348 or #22333 .

@soronpo
Copy link
Contributor

soronpo commented Jan 17, 2025

Checkout my implementation of AssertGiven
https://github.com/DFiantHDL/DFHDL/blob/83af361440b9dcd9eec378298a02c1ba4800f273/internals/src/main/scala/dfhdl/internals/helpers.scala#L240
This may help expand it to get you what you want

@belamenso
Copy link
Author

@soronpo You could achieve arbitrary error messages with macros, but I believe this proposition

  • uses simpler language constructs,
  • extends them in a natural way,
  • seems easy to implement.

@Gedochao Gedochao added area:annotations and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Jan 17, 2025
@sjrd
Copy link
Member

sjrd commented Jan 17, 2025

  • seems easy to implement.

Famous last words.

I don't think this is easy to implement at all. In fact, I don't think it's possible to reliably implement. You can't take a string representation of a type, put it in a different context, and somehow hope to be able to parse and typecheck it somewhere else.

@som-snytt
Copy link
Contributor

Previous use case at previous ticket #22367 (comment)

There is an ancient Scala 2 ticket to improve implicitNotFound along these lines. Surely Scala 3 compiletime and inline facilities make it feasible. Maybe it must wait for the Great Collection Literal Syntax Debate of 2025 to subside. There are other issues with the annotation, when it appears on parent classes, so maybe it's an opportunity to re-design the feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:annotations area:reporting Error reporting including formatting, implicit suggestions, etc better-errors Issues concerned with improving confusing/unhelpful diagnostic messages itype:enhancement
Projects
None yet
Development

No branches or pull requests

5 participants