Reputation: 160
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
Reputation: 23164
The behavior you're observing can be explained by the interaction of two Kotlin features:
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.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
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