scala3
scala3 copied to clipboard
unhelpful error message for ValueOf[N] when no singleton available for N (bounded by singleton)
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
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])
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)
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
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 Yes, added a comment in https://github.com/lampepfl/dotty/issues/8257.
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 | }
----------------------------------------------------------------------------
@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.
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.
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.
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?
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.
I think I tried to make implicitNotFound
show an addendum; maybe that was for overrides?
https://github.com/scala/scala/pull/8280
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.
The Scala 2 PR was merged, and the Scala 2.13.10 standard library upgrade was included in Scala 3.2.1-RC4.