singleton-ops
singleton-ops copied to clipboard
RequireMsg compile-time error message escaping code context
This is a short code snippet that demonstrates the problem:
import singleton.ops.RequireMsg
object RequireMsgSpike {
trait HasM {
type M = Int
}
trait Foo {
trait FF[T]
}
object Foo3 extends Foo {
implicit def ev3[T <: HasM](
implicit
r: RequireMsg[false, "Just Bad Type"]
): Set[FF[T]] = ???
}
object Foo4 extends Foo {
implicit def ev4[T <: HasM](
implicit
r: Int
): Set[FF[T]] = ???
}
implicitly[Set[Foo3.FF[HasM]]]
implicitly[Set[Foo4.FF[HasM]]]
}
error message:
[Error] /***/RequireMsgSpike.scala:34:13: Just Bad Type
[Error] /***/RequireMsgSpike.scala:35:13: Just Bad Type
The second implicit error message got corrupted by the first.
if you delete the first implicit line implicitly[Set[Foo3.FF[HasM]]], the error message goes back to normal:
[Error] /home/peng/git/shapesafe/core/src/test/scala/shapesafe/core/debugging/RequireMsgSpike.scala:35:13: could not find implicit value for parameter e: Set[shapesafe.core.debugging.RequireMsgSpike.Foo4.FF[shapesafe.core.debugging.RequireMsgSpike.HasM]] (No implicit view available from shapesafe.core.debugging.RequireMsgSpike.Foo4.FF[shapesafe.core.debugging.RequireMsgSpike.HasM] => Boolean.)
I vaguely remember that RequireMsg macro overwrites @ImplicitNotFound annotation to achieve its purpose, and this implementation is derived from shapeless. So I guess the easiest way for me is to try the following solution:
- try to reproduce it in shapeless
- if succeed, forward-port it to singleton-ops
- otherwise, file the same issue for shapeless, and try to write an original fix or circumvention
This could be due to the caching mechanism in singleton-ops.
you are most likely right, there is no difference in shapeless code.
In the meantime I'll try to PR a piece of unit test first.
Actually this kind of looks like a Scalac bug, but maybe as a result of that tampering with @implicitNotFound via macros.
@soronpo It turns out that the error is only triggered in certain cases. Please see the new test case in my PR
This could be due to the caching mechanism in singleton-ops.
I'm now 90% sure it wasn't. By observing what is being annotated by your code:
After logging the abort function:
def abort(msg: String, annotatedSym : Option[TypeSymbol] = defaultAnnotatedSym): Nothing = {
VerboseTraversal(s"!!!!!!aborted with: $msg at $annotatedSym, $defaultAnnotatedSym")
annotatedSym.foreach {
sym =>
val oldAnnotation = sym.annotations
setAnnotation(msg, sym)
println(s"setting annotation of `${annotatedSym.get}`: `${oldAnnotation.mkString(",")}` --> `${msg}`")
}
...
I observe the following message in compile-time:
setting annotation of `type Set`: `` --> `Testing 456`
...
[error] /home/peng/git-release/singleton-ops/src/test/scala/singleton/ops/RequireSpec.scala:43:13: Type-checking failed in an unexpected way.
[error] Expected error matching: could not find implicit value.*
[error] Actual error: Testing 456
[error] illTyped("""implicitly[Set[Int]]""","could not find implicit value.*")
[error]
Clearly, the annotation of "Set" was changed, which even without caching, affects all other implicits with "Set[*]" return type.
To solve this, you'll have 3 options:
- revert the annotation immediately after aborting the context
- apply the annotation on the function (should be a scala 2.13 feature), instead of the return type
- leverage the following property of blackbox macro to fail with an error message immediately, instead of delegating to implicit search:
If a blackbox macro (even implicit blackbox macro) throws an exception then it will be a compile error during compilation of main code. If a whitebox implicit macro throws an exception then during compilation of main code the implicit will be silently removed from candidates.
(Source: https://stackoverflow.com/questions/64538997/what-should-a-scala-implicit-macro-have-to-return-to-tell-the-compiler-forget-m)
Not sure if this information is helpful
... Sorry the last option is out of context & doesn't apply to your case. The implicit error will always write over the previous error