bug icon indicating copy to clipboard operation
bug copied to clipboard

No implicit view available from A => B despite having one in scope

Open steinybot opened this issue 3 years ago • 2 comments

Reproduction steps

Scala version: 2.13.8

sealed trait ImplicitlyStable[A, +B]

object ImplicitlyStable {

  implicit def convertibleToStable[A, B](implicit
                                         aToB: A => B,
                                         bToB: ImplicitlyStable[B, B]
                                        ): ImplicitlyStable[A, B] = ???
}

object Test {

  def testConvertibleToStable[A, B](implicit
                                    aToB: A => B,
                                    bToB: ImplicitlyStable[B, B]
                                   ): ImplicitlyStable[A, B] =
    implicitly[ImplicitlyStable[A, B]]
}

Problem

This fails with:

[error] !I e: ImplicitlyStable[A, B]
[error] ImplicitlyStable.convertibleToStable invalid because
[error] !I aToB: A => B
[error]   No implicit view available from A => B.
[error]
[error]     implicitly[ImplicitlyStable[A, B]]
[error]               ^

This seems like it ought to compile. The implicit view is right there in the parameters.

A few surprising changes make it compile:

  • Removing A from ImplicitlyStable
  • Changing B in ImplicitlyStable to be invariant
  • Removing either aToB or bToB from convertibleToStable
  • Changing implicitly[ImplicitlyStable[A, B]] to ImplicitlyStable.convertibleToStable[A, B]

If I change implicitly[ImplicitlyStable[A, B]] to ImplicitlyStable.convertibleToStable then it fails with:

[error]  both method $conforms in object Predef of type [A]A => A
[error]  and value aToB of type A => B
[error]  match expected type A => B
[error]     ImplicitlyStable.convertibleToStable
[error]                      ^

Is it one of those situations where is inferring Nothing. If so why?

steinybot avatar Apr 21 '22 03:04 steinybot

If I add:

  private def unexpected: Nothing = sys.error("Unexpected invocation")
  implicit def nothingStableAmbig1[A]: ImplicitlyStable[Nothing, A] = unexpected
  implicit def nothingStableAmbig2[A]: ImplicitlyStable[Nothing, A] = unexpected
  implicit def nothingStableAmbig3[A]: ImplicitlyStable[A, Nothing] = unexpected
  implicit def nothingStableAmbig4[A]: ImplicitlyStable[A, Nothing] = unexpected

then sure enough I get:

[error] /Users/jason/src/bug-reports/src/main/scala/com/goodcover/slinky/stable/Main.scala:31:15: ambiguous implicit values:
[error]  both method nothingStableAmbig3 in object ImplicitlyStable of type [A]com.goodcover.slinky.stable.ImplicitlyStable[A,Nothing]
[error]  and method nothingStableAmbig4 in object ImplicitlyStable of type [A]com.goodcover.slinky.stable.ImplicitlyStable[A,Nothing]
[error]  match expected type com.goodcover.slinky.stable.ImplicitlyStable[A,B]
[error]     implicitly[ImplicitlyStable[A, B]]
[error]               ^

I still can't figure out why it is so quick to infer Nothing

steinybot avatar Apr 21 '22 04:04 steinybot

Change the order of the implicit arguments in convertibleToStable - put bToB: ImplicitlyStable[B, B] first and aToB: A => B second.

When the compiler searches an implicit function of the form

implicit def foo[A, B](implicit arg: Something[A, B], ...): ...

then both A and B are chosen for the entire subsequent implicit algorithm by the first arguments which specify them.

In your case the compiler sees it needs an implicit aToB: A => B for any free A and B. Well it's easy enough to infer B to Nothing and it gets A => Nothing for free so it does that.

If you put bToB first then B is fixed to what you actually need - the thing from scope - and then in the second argument the compiler picks A => B correctly from the implicits in scope, with a correctly-chosen B.

Whether this behaviour is a bug or not - I don't know. The way it behaves makes sense to me with my (very rudimentary) understanding of the compiler. This is not a technical explanation and I'm sure somebody who knows more can come and correct my specifics!

Note: Chaning +B to B means Nothing is no longer a candidate to satisfy the second type argument of the implicit def, so it would not find A => Nothing as an option for the first argument. Hence it works with an invariant B as written in the opening post

jdrphillips avatar Jul 20 '22 13:07 jdrphillips