Why DOESN’T Kotlin smart cast work, even when both are `val`s

In the below code, both the variables are val s, but the smart cast doesn’t work even after the null check. Why?

fun SLLNode?.sumListWith(node: SLLNode?, carry: Int = 0): SLLNode? =
    when {
        this == null && node == null -> if (carry == 0) null else SLLNode(carry)
        this == null -> node.also { it!!.value += carry } // Smart cast doesn't work here.
        node == null -> this.also { value += carry } // Works here.
        else -> {
            ...
        }
    }

If we look at the first condition, you can see it can fail either because this is not null or because node is not null, if this condition fails we don’t have any information about whether node is not null, as far as the compiler can tell, the condition could have failed because of this != null,

so when the compiler infer the type of node in the second condition, it just doesn’t know if node is not null.

Your code is right tho, node can not be null in the second condition, but it seems the compiler is not able to determine that. it probably isn’t able to “link” the first and second condition to infer that node is in fact not null.

if you want smart cast just check that node is not null in your second condition, maybe type inference will be enhanced enough in the 1.4 release to cover that kind of use case.

EDIT: I tried this code in the Kotlin playground with 1.4 M1 and the smart cast is not made :confused:

The list of cases that allow smart casts are here: Smart Casts

It really just looks at simple direct checks and inverse checks that can alone direct control flow. It doesn’t do any “is it possible?” analysis or logic simplification.

I ran across this and was going to reply some optimization to that code, but it occurred to me that the real issue is an incomplete design. In reality you should be using immutable data structures, not mutable ones, but I will stick with mutable.

What you need is this method:

operator fun SLLNode?.plusAssign(operand: Int): SLLNode? =
    when {
        operand == 0 -> this
        else -> this?.also { value += operand } ?: SLLNode(operand)
    }

Then your original code just becomes:

fun SLLNode?.sumListWith(node: SLLNode?, carry: Int = 0): SLLNode? =
    when {
        this == null -> node += carry
        node == null -> this += carry
        else -> {
            ...
        }
    }
3 Likes