scala3 icon indicating copy to clipboard operation
scala3 copied to clipboard

unhelpful error message for ValueOf[N] when no singleton available for N (bounded by singleton)

Open cayhorstmann opened this issue 2 years ago • 3 comments

Compiler version

3.2.0-RC1

Minimized code

import scala.compiletime.*

class Vec[N <: Int & Singleton] :
  private val xs: Array[Double] = Array.ofDim[Double](summon[ValueOf[N]].value)

Output

No singleton value available for N.

Expectation

My expectation is that the Singleton bound makes ValueOf realize that N is a singleton. But I am no expert, and the fact that https://github.com/lampepfl/dotty/issues/8257 was closed makes me wonder. But then my expectation would be to get an error message that is clearer.

A bit more detail at https://users.scala-lang.org/t/how-do-i-compute-type-level-m-n/8715/2

cayhorstmann avatar Aug 07 '22 16:08 cayhorstmann

The message we see here is a customized error for the missing implicit value of ValueOf[N]. As far as I'm aware, Singleton bound does not automatically add the implicit value needed for ValueOf to the scope and is probably correct by design. As you've observed in the discussion from the link above, adding using ValueOf[N] fixes the issue, it can be written in a shorter form

class Vec[N <: Int: ValueOf] :
  private val xs: Array[Double] = Array.ofDim[Double](valueOf[N])

WojciechMazur avatar Aug 08 '22 08:08 WojciechMazur

The problem is that ValueOf[N] is only implicitly available for concrete types, not abstract types. In the definition of xs, N is still abstract. N is only known when Vec[N] is instantiated. So then you add the (using ValueOf[N]) to the class constructor (or context bound as in https://github.com/lampepfl/dotty/issues/15829#issuecomment-1207817015)

bishabosha avatar Aug 08 '22 08:08 bishabosha

It seems from the forum discussion and the nature of this issue being opened that we should do more to signal to the user what to do when the implicit search fails - so let's keep this open to improve the error message presented.

However, the error message for the missing implicit is defined in the Scala 2 library - so not much we can do here to change the basic message - however we have the possibility to add an addendum to that message to explain why synthesis of ValueOf failed: https://github.com/lampepfl/dotty/blob/e560c2d6970c864ceea5607597d74cf49c443965/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala#L216-L233

so for the case where a constant type, unit type, or singleton term-ref cant be extracted, we can add an error message with a recommendation/explanation of the problem

bishabosha avatar Aug 08 '22 08:08 bishabosha

the fact that https://github.com/lampepfl/dotty/issues/8257 was closed makes me wonder

@mbovel you say that 8257 actually eventually got fixed...?

SethTisue avatar Sep 06 '22 15:09 SethTisue

@SethTisue Yes, added a comment in https://github.com/lampepfl/dotty/issues/8257.

mbovel avatar Sep 06 '22 15:09 mbovel

Trying to summon ValueOf:

def f[N <: Int & Singleton](n: N) =
  println(summon[ValueOf[N]].value)

yields the following error:

-- Error: /Users/mbovel/dotty/test.scala:3:28 ----------------------------------
3 |  println(summon[ValueOf[N]].value)
  |                            ^
  |          No singleton value available for N
  |
  |          where:    N is a type in method f with bounds <: Int & Singleton
  |          .

And calling valueOf:

def f[N <: Int & Singleton](n: N) =
  println(valueOf[N])

yields the following error:

-- Error: /Users/mbovel/dotty/test.scala:3:17 ----------------------------------
 3 |  println(valueOf[N])
   |          ^^^^^^^^^^
   |          cannot reduce summonFrom with
   |           patterns :  case given ev @ _:ValueOf[N]
   |----------------------------------------------------------------------------
   |Inline stack trace
   |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   |This location contains code that was inlined from Predef.scala:25
25 |  inline def valueOf[T]: T = summonFrom {
   |                                        ^
26 |    case ev: ValueOf[T] => ev.value
27 |  }
    ----------------------------------------------------------------------------

mbovel avatar Sep 06 '22 15:09 mbovel

@cayhorstmann I'm curious what the thought process was, or what documentation you consulted, that led you to write summon[ValueOf[...]] rather than valueOf[...]. Matt's comment shows that the error message is different when you write valueOf, and valueOf does seem more idiomatic to me, it's what I would expect to see in user code, based on the documentation.

SethTisue avatar Sep 06 '22 15:09 SethTisue

Assuming it's the "No singleton value available for N." version of the error we want to improve, perhaps that @implicitNotFound should simply be removed from the Scala 2 standard library, because it's concealing what's going on. It's making the eventual error message less helpful, not more helpful.

SethTisue avatar Sep 06 '22 15:09 SethTisue

Over on the Scala 2 side, the status quo is:

scala> def foo[T] = valueOf[T]
                           ^
       error: No singleton value available for T.

and if we remove the @implicitNotFound from src/library/scala/ValueOf.scala and try again, it changes to:

scala> def foo[T] = valueOf[T]
                           ^
       error: could not find implicit value for parameter vt: ValueOf[T]

which in this context actually better, since it's clear that ValueOf is what's missing, and that implicit search is the context in which it is missing.

But what if we do have a ValueOf context bound, and the problem is that T isn't a singleton? Because that's what the original error message seems intended for.

Scala 2 again:

scala> def foo[T : ValueOf] = valueOf[T]
def foo[T](implicit evidence$1: ValueOf[T]): T

scala> foo[Int]
          ^
       error: No singleton value available for Int.

and without the @implicitNotFound:

scala> def foo[T : ValueOf] = valueOf[T]; foo[Int]
                                             ^
       error: could not find implicit value for evidence parameter of type ValueOf[Int]

It's a bit less clear here whether the custom error message is helping or not.

SethTisue avatar Sep 06 '22 16:09 SethTisue

There's a broader design question here, which is: when a custom implicitNotFound message is available, perhaps it would be nicer if the compiler showed both the custom message, and the more general message that indicates that implicit search failed, and what type is what looking for?

SethTisue avatar Sep 06 '22 16:09 SethTisue

Possible improvement of the error message:

diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
index e3f5382ecad..1b84e243bbb 100644
--- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
+++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
@@ -227,7 +227,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
           case n: TermRef =>
             withNoErrors(success(ref(n)))
           case tp =>
-            EmptyTreeNoError
+            withErrors(f"eligible types include literals and stable paths.")
       case _ =>
         EmptyTreeNoError
   end synthesizedValueOf

@SethTisue will further look into this, possibly changing the @implicitNotFound custom error message in the Scala standard library directly.

mbovel avatar Sep 06 '22 16:09 mbovel

I think I tried to make implicitNotFound show an addendum; maybe that was for overrides?

https://github.com/scala/scala/pull/8280

som-snytt avatar Sep 06 '22 16:09 som-snytt

Scala 2 PR: https://github.com/scala/scala/pull/10134

If the Scala 2 PR is accepted, then I think we could close this ticket.

SethTisue avatar Sep 06 '22 17:09 SethTisue

The Scala 2 PR was merged, and the Scala 2.13.10 standard library upgrade was included in Scala 3.2.1-RC4.

SethTisue avatar Oct 26 '22 09:10 SethTisue