bug icon indicating copy to clipboard operation
bug copied to clipboard

Type inference failure while picking among conversions

Open vhminh opened this issue 7 months ago • 2 comments

Reproduction steps

Scala version: 2.13.16

//> using scala 2.13.16
import scala.language.implicitConversions

trait LowPriorityConversions {
  implicit def convertIterable1[A, I[T] <: Iterable[T]](iterable: I[A]): String = {
    "convertIterable1 wins"
  }

  // implicit def convertIterable2[A, I[_]](iterable: I[A])(implicit ev: I[A] <:< Iterable[A]): String = {
  //   "convertIterable2 wins"
  // }
}

object Conversions extends LowPriorityConversions {
  implicit def convertMap[K, V, M[A, B] <: Map[A, B]](map: M[K, V]): String = {
    "convertMap wins"
  }
}

object OverloadTest {
  // force conversion to String
  def printStr(str: String): Unit = {
    println(str)
  }

  def main(args: Array[String]): Unit = {
    import Conversions._
    printStr(Map("key" -> "value"))               // ok: got "convertMap wins"
    printStr(Map("key" -> "value").map(identity)) // expect "convertMap wins", but got "convertIterable1 wins"
  }
}

Problem

  • Map#map has 2 alternatives: map[K2, V2](f: ((K, V)) => (K2, V2)): Map[K2, V2] and map[B](f: ((K, V)) => B): Iterable[B]. The first one is more specific so I expect Map("key" -> "value").map(identity) to return a Map, not an Iterable
  • It works as expected when we replace convertIterable1 with convertIterable2
  • This doesn't happen on Scala 3.3.5

vhminh avatar May 07 '25 18:05 vhminh

I don't understand the details yet but -Vlog:typer suggests that it (spuriously) can't infer type args for convertMap but can for convertIterable1.

[log typer] infer method inst Conversions.convertMap[K, V, M], tparams = List(type K, type V, type M), args = List(immutable.this.Map[?K2,scala.this.Nothing]), pt = String, lobounds = List(scala.this.Nothing, scala.this.Nothing, scala.this.Nothing), parambounds = List(, , [A, B] <: Map[A,B])
[log typer] error tree = <argument>
[log typer] implicit adapt failed: no type parameters for method convertMap: (map: M[K,V]): String exist so that it can be applied to arguments (immutable.this.Map[?K2,scala.this.Nothing])
 --- because ---
no unique instantiation of type variable K2 could be found
test/files/run/t13102.scala:22: convertMap is not a valid implicit value for scala.this.Function1[immutable.this.Map[?K2,scala.this.Nothing],String] because:
no type parameters for method convertMap: (map: M[K,V]): String exist so that it can be applied to arguments (immutable.this.Map[?K2,scala.this.Nothing])
 --- because ---
no unique instantiation of type variable K2 could be found
    import Conversions._
           ^

I also don't understand why convertIterable2 fails with

[log typer] implicit adapt failed: Cannot prove that scala.collection.immutable.Iterable[A] <:< Iterable[A].

Then convertMap eventually succeeds (after failing to infer type args) because it tries picking the overload first, and that allows it to proceed successfully.

I'll tweak the title to reflect that it's a problem with type inference and not overload resolution per se.

som-snytt avatar May 10 '25 01:05 som-snytt

for reference, the Map construction is not involved (but produces noisy debug)

//> using scala 2.13.16
//> using options -Xsource:3-cross -Vlog:typer -Vdebug -Vtyper
import scala.language.implicitConversions

trait LowPriorityConversions {
  implicit def convertIterable1[A, I[T] <: Iterable[T]](iterable: I[A]): String = "convertIterable1 wins"

  //implicit def convertIterable2[A, I[_]](iterable: I[A])(implicit ev: I[A] <:< Iterable[A]): String = "convertIterable2 wins"
}

object Conversions extends LowPriorityConversions {
  implicit def convertMap[K, V, M[A, B] <: Map[A, B]](map: M[K, V]): String = "convertMap wins"
}

object OverloadTest {
  // force conversion to String
  def printStr(str: String): Unit = println(str)

  def sut = Map("key" -> "value")

  def main(args: Array[String]): Unit = {
    import Conversions._
    //printStr(Map("key" -> "value"))               // ok: got "convertMap wins"
    //printStr(Map("key" -> "value").map(identity)) // expect "convertMap wins", but got "convertIterable1 wins"
    printStr(sut.map(identity)) // expect "convertMap wins", but got "convertIterable1 wins"
  }
}

som-snytt avatar May 10 '25 01:05 som-snytt