Search code examples
scalascala-collectionsimplicitscala-2.13

scala 2.13 auto implicit resolution error


I am facing this weird problem related to scala implicit resolution

Here is the code snippet

import scala.collection.Factory
import scala.collection.immutable.Seq

sealed trait A

sealed trait B

case class BImpl() extends B

case class AImpl() extends A

object implicitsContainer {

  type AB = (A, B)

  implicit def toStringAnyTuples[C[X] <: Iterable[X], A <: AB]
  (col: C[A])
  (implicit factory: Factory[(String, Any), C[(String, Any)]])
  : C[(String, Any)] = {

    factory.fromSpecific(col.iterator.map(f => f._1.toString -> f._2))
  }
}

object Main extends App {

  import implicitsContainer._

  def a(f: Seq[(String, Any)]): Seq[(String, Any)] = f

  val w: Seq[(AImpl, BImpl)] = Seq(AImpl() -> BImpl())

  val x: Seq[(String, Any)] = a(w)

  //    Won't compile
  //    val y: Seq[(String, Any)] = a(Seq(AImpl() -> BImpl()))
}

Scala automatically picking up the implicit method

implicit def toStringAnyTuples[C[X] <: Iterable[X], A <: AB](col: C[A])
  (implicit factory: Factory[(String, Any), C[(String, Any)]])
  : C[(String, Any)] = {

    factory.fromSpecific(col.iterator.map(f => f._1.toString -> f._2))
  }

for this: -

  val w: Seq[(AImpl, BImpl)] = Seq(AImpl() -> BImpl())

  val x: Seq[(String, Any)] = a(w)

but throws an error for this

val y: Seq[(String, Any)] = a(Seq(AImpl() -> BImpl()))

and the error is:-

Error:(44, 47) type mismatch;
 found   : (AImpl, BImpl)
 required: (String, Any)
    val y: Seq[(String, Any)] = a(Seq(AImpl() -> BImpl()))

and one more point, if I remove the type from w

  val w = Seq(AImpl() -> BImpl())

  val x: Seq[(String, Any)] = a(w)

then also this will work fine. The only error is with

val y: Seq[(String, Any)] = a(Seq(AImpl() -> BImpl()))

I am using: -

SCALA -> 2.13.3

SBT -> 1.3.13

JAVA -> 14


Solution

  • What you are experiencing is a symptom of the way inference works in the Scala compiler. Here is a smaller example that shows the same issue:

    object Test {
      class C[T](x: T)
      implicit def conv(c: C[Int]): C[String] = ???
      def m(cs: C[String]) = 1
      val ci = new C(1)
      def t1: Int = m(ci)       // OK
      def t2: Int = m(new C(1)) // found: Int, expected: String
    }
    

    When type-checking new C(1), the compiler pushes down the expected type String to type checking the expression 1, which fails. In the line above, type checking ci with expected type C[String] succeeds thanks to the implicit conversion. My suggestion here would be to define an extension method that performs the conversion, instead of making the conversion implicit. This is also recommended for clarity - implicit conversions like the one defined in your example can lead to surprising, hard to diagnose issues. In my example, it would look like this:

    object Test {
      class C[T](x: T)
      implicit class CExt(private val c: C[Int]) extends AnyVal {
        def toCString: C[String] = ???
      }
      def m(cs: C[String]) = 1
      val ci = new C(1)
      def t1: Int = m(ci.toCString)
      def t2: Int = m(new C(1).toCString)
    }