Problems with overload resolution of generic extension methods with multiple type parameters


#1

Kotlin 1.0-beta-4

I have an AutoCloseable.use extension method mimicking stdlib’s Closeable.use extension method. To my big surprise, I just discovered that my AutoClosable.use method is used even for objects with static type Closeable. Why isn’t the more specific method from stdlib used in that case?

Next surprise: When I declare both overloads in my own code, overload resolution fails with an ambiguity:

inline fun <T : Closeable, R> T.myUse(action: (T) -> R): R = TODO()
inline fun <T : AutoCloseable, R> T.myUse(action: (T) -> R): R = TODO()

fun testMyUse() {
  val c: Closeable = RandomAccessFile("foo", "r")
  // "Cannot choose among the following candidates without completing type inference: ..."
  val x: Int = c.myUse { 42 }
  
  // "Overload resolution ambiguity. All these functions match: ..."
  val y: Int = c.myUse<Closeable, Int> { 42 }

Something seems to go wrong here, and it seems to be related to the presence of a second type argument (R). Other than that, overload resolution behaves as I would expect (I’ve pasted my full set of experiments below).

import java.io.Closeable
import java.io.RandomAccessFile

fun testStdlibUse() {
  val c: Closeable = RandomAccessFile("foo", "r")
  c.use {} // chooses AutoCloseable.use (unexpected)

  val ac: AutoCloseable = RandomAccessFile("foo", "r")
  ac.use {} // chooses AutoCloseable.use (expected)
}

fun testMyUse() {
  val c: Closeable = RandomAccessFile("foo", "r")
  // "Cannot choose among the following candidates without completing type inference: ..." (unexpected)
  c.myUse {}

  // "Cannot choose among the following candidates without completing type inference: ..." (unexpected)
  val x: Int = c.myUse { 42 }

  // "Overload resolution ambiguity. All these functions match: ..." (unexpected)
  val y: Int = c.myUse<Closeable, Int> { 42 }

  val ac: AutoCloseable = RandomAccessFile("foo", "r")
  ac.myUse {} // chooses AutoCloseable overload (expected)
}

fun testGenericExtension() {
  val c: Closeable = RandomAccessFile("foo", "r")
  c.foo() // chooses Closeable overload (expected)

  val ac: AutoCloseable = RandomAccessFile("foo", "r")
  ac.foo() // chooses AutoCloseable overload (expected)
}

fun testNonGenericExtension() {
  val c: Closeable = RandomAccessFile("foo", "r")
  c.bar() // chooses Closeable overload (expected)

  val ac: AutoCloseable = RandomAccessFile("foo", "r")
  ac.bar() // chooses AutoCloseable overload (expected)
}

fun testGenericNonExtension() {
  val c: Closeable = RandomAccessFile("foo", "r")
  baz(c) // chooses Closeable overload (expected)

  val ac: AutoCloseable = RandomAccessFile("foo", "r")
  baz(ac) // chooses AutoCloseable overload (expected)
}

fun testNonGenericNonExtension() {
  val c: Closeable = RandomAccessFile("foo", "r")
  bazz(c) // chooses Closeable overload (expected)

  val ac: AutoCloseable = RandomAccessFile("foo", "r")
  bazz(ac) // chooses AutoCloseable overload (expected)
}

// defined in stdlib:
// public inline fun <T : Closeable, R> T.use(block: (T) -> R): R
inline fun <T : AutoCloseable, R> T.use(action: (T) -> R): R = TODO()

inline fun <T : Closeable, R> T.myUse(action: (T) -> R): R = TODO()
inline fun <T : AutoCloseable, R> T.myUse(action: (T) -> R): R = TODO()

fun <T : Closeable> T.foo() {}
fun <T : AutoCloseable> T.foo() {}

fun Closeable.bar() {}
fun AutoCloseable.bar() {}

fun <T: Closeable> baz(c: T) {}
fun <T: AutoCloseable> baz(c: T) {}

fun bazz(c: Closeable) {}
fun bazz(ac: AutoCloseable) {}

#2

Opened an issue https://youtrack.jetbrains.com/issue/KT-10640


#3

It happens because your explicitly imported AutoCloseable.use takes precedence over implicitly imported Closeable.use from kotlin.io. AFAIK, this is intended behavior.

You can import Closeable.use explicitly and it will win again in overload resolution:

import kotlin.io.use

#4

@ilya.gorbunov Thanks for the explanation; sounds reasonable once you know it.