Search code examples
scalahigher-kinded-typesimplicits

Implicit class resolution for parameterized types


In the following example, it seems that the Scala compiler only recognizes an implicit class when it is defined to take the higher-kinded representation of Wrapper. Why is that?

scala> case class Nested(n: Int)
defined class Nested

scala> case class Wrapper[A <: Product](nested: A)
defined class Wrapper

scala> implicit class I1[W <: Wrapper[A], A <: Product](underlying: W) {
     | def ok1() = true
     | }
defined class I1

scala> Wrapper(Nested(5)).ok1()
<console>:26: error: value ok1 is not a member of Wrapper[Nested]
       Wrapper(Nested(5)).ok1()
                          ^
scala> implicit class I2[W <: Wrapper[_]](underlying: W) {
     | def ok2() = true
     | }
defined class I2

scala> Wrapper(Nested(5)).ok2()
res1: Boolean = true

Is there a workaround for implicit resolution that maintains full information about the nested type, allowing typeclass evidence, e.g., TypeTag, to be attached to it?

Note: the example above shows Nested and Wrapper to be case classes but that's not integral to the question. It's simply a convenience for a shorter and simpler console session.


Solution

  • This is happening because of a limitation in Scala's type inference. See SI-2272.

    The implicit fails to resolve because the compiler cannot properly infer A. We can see this if we enable -Xlog-implicits. Notice that A is inferred as Nothing:

    I1 is not a valid implicit value for Test.w.type => ?{def ok: ?} because:
    inferred type arguments [Wrapper[Nested],Nothing] do not conform to method I1's type parameter bounds [W <: Wrapper[A],A <: Product]
    

    The same thing happens if we try to instantiate I1 manually:

    scala> val w = Wrapper(Nested(5))
    w: Wrapper[Nested] = Wrapper(Nested(5))
    
    scala> new I1(w)
    <console>:21: error: inferred type arguments [Wrapper[Nested],Nothing] do not conform to class I1's type parameter bounds [W <: Wrapper[A],A <: Product]
           new I1(w)
           ^
    <console>:21: error: type mismatch;
     found   : Wrapper[Nested]
     required: W
           new I1(w)
                  ^
    

    Now, the work-arounds.

    First, Wrapper is a case class, so there shouldn't be a reason for it to have sub-types. You can remove the W type parameter, and change underlying to a Wrapper[A]:

    implicit class I1[A <: Product](underlying: Wrapper[A]) {
      def ok = true
    }
    

    If you still wish to require two type parameters, you can also require implicit evidence that W <:< Wrapper[A], while removing the upper-bound on the type parameter W:

    implicit class I1[W, A <: Product](underlying: W)(implicit ev: W <:< Wrapper[A]) {
      def ok = true
    }