Description
Compiler version
3.2.1-RC2, 3.2.0, 3.1.3
Minimized code
https://scastie.scala-lang.org/WspTUC2IRQehfdFeA5EvMg
package example
import scala.language.implicitConversions
final class Functoid[+R](val function: Product => R)
object Functoid {
implicit def apply[A, R](function: A => R): Functoid[R] = {
println(s"arity 1")
new Functoid({ case Tuple1(a: A) => function(a) })
}
implicit def apply[A, B, R](function: (A, B) => R): Functoid[R] = {
println("arity 2")
new Functoid({ case (a: A, b: B) => function(a, b) })
}
}
final case class ContainerConfig(image: String, version: Int, cmd: String)
final class ContainerResource
object ContainerResource {
implicit final class DockerProviderExtensions(private val self: Functoid[ContainerResource]) extends AnyVal {
def modifyConfig(modify: Functoid[ContainerConfig => ContainerConfig]): Functoid[ContainerConfig => ContainerConfig] = modify
// removing this overload fixes the implicit conversion and returns `arity 2` print
def modifyConfig(modify: ContainerConfig => ContainerConfig): Functoid[ContainerConfig => ContainerConfig] = new Functoid(_ => modify)
}
}
object App extends App {
println {
new Functoid(_ => new ContainerResource)
.modifyConfig {
// applying Functoid.apply explicitly instead of via implicit conversion also avoids untupling
// Functoid {
(image: String, version: Int) => (cfg: ContainerConfig) => cfg.copy(image, version)
// }
}
.function.apply(Tuple2("img", 9))
.apply(ContainerConfig("a", 0, "b"))
}
}
Output
arity 1
Exception in thread "main" scala.MatchError: (img,9) (of class scala.Tuple2)
at example.Functoid$.apply$$anonfun$1(example.scala:10)
at example.example$package$.example(example.scala:38)
at example.example.main(example.scala:30)
Expectation
Expected to print
arity 2
ContainerConfig(img,9,b)
as on Scala 2 (https://scastie.scala-lang.org/bH7AUwrhS5qQnotQPPi30Q)
The existence of def modifyConfig(modify: ContainerConfig => ContainerConfig)
overload causes a very weird implicit conversion of the function, instead of hitting the 2 arity Functoid.apply conversion, the function is auto-untupled and turned into a 1-arity function before hitting the 1-arity Functoid.apply. Functoid.apply being an overload itself has no effect, the issue still manifests if the conversions are called apply1
& apply2
.
Removing the overload or applying Functoid.apply explicitly works around the issue.
This code is a minimized version of real library code in https://github.com/izumi/izumi which we're trying to port to Scala 3