JluoH
JluoH

Reputation: 160

Extension method, when called on a null object, is called on the wrong type

fun main() {
    val set: Set<Int>?
    set = null
    val emptySet: Set<Int> = set.orEmpty()
}

Can't figure out why even when explicitly typing the set variable as Set <Int>? the compiler considers that in the extension method set.orEmpty () set - is a string and, accordingly, crashes with an error:

Kotlin: Type mismatch: inferred type is String but Set was expected

But when declaring and initializing in one line, everything happens correctly:

fun main() {
    val set: Set<Int>? = null
    val emptySet: Set<Int> = set.orEmpty()
}

Upvotes: 9

Views: 332

Answers (2)

Ilya
Ilya

Reputation: 23164

The behavior you're observing can be explained by the interaction of two Kotlin features:

  • first, the type of set variable is narrowed to Nothing? as a result of a smart cast after the assignment of null value to it. The smart cast after an assignment can be useful in cases when it narrows variable type to a more specific type, but narrowing to Nothing? does more harm than good.
  • second, among all overloads of orEmpty function available for a value of type Nothing?, the non-generic one String?.orEmpty() is chosen due to the specific rule of Kotlin overload resolution: a non-generic candidate is preferred to generic ones.

This behavior indeed can be puzzling, so I've reported this problem as KT-50661.

Upvotes: 1

lukas.j
lukas.j

Reputation: 7172

I think this is related to the fact that the compiler is not so smart that it could deduce that the code set = null will be executed exactly once – it could be zero times or more than once.

If you know that it will run exactly one, you can tell the compiler by using a feature called kotlin.contracts:

import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

@ExperimentalContracts
fun main() {
  val set: Set<Int>?
  once { set = null }
  val emptySet: Set<Int> = set.orEmpty()
}

@ExperimentalContracts
fun once(lambda: () -> Unit) {
  contract { callsInPlace(lambda, InvocationKind.EXACTLY_ONCE) }
  lambda()
}

See https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.contracts/

Upvotes: 0

Related Questions