singleton-ops icon indicating copy to clipboard operation
singleton-ops copied to clipboard

RequireMsg compile-time error message escaping code context

Open tribbloid opened this issue 3 years ago • 6 comments
trafficstars

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

tribbloid avatar Mar 14 '22 23:03 tribbloid

This could be due to the caching mechanism in singleton-ops.

soronpo avatar Mar 15 '22 01:03 soronpo

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.

tribbloid avatar Mar 15 '22 01:03 tribbloid

Actually this kind of looks like a Scalac bug, but maybe as a result of that tampering with @implicitNotFound via macros.

soronpo avatar Mar 15 '22 02:03 soronpo

@soronpo It turns out that the error is only triggered in certain cases. Please see the new test case in my PR

tribbloid avatar Mar 19 '22 21:03 tribbloid

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:

  1. revert the annotation immediately after aborting the context
  2. apply the annotation on the function (should be a scala 2.13 feature), instead of the return type
  3. 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

tribbloid avatar Mar 20 '22 18:03 tribbloid

... Sorry the last option is out of context & doesn't apply to your case. The implicit error will always write over the previous error

tribbloid avatar Mar 20 '22 20:03 tribbloid