2

Trying to understand why Kotlin's smart cast doesn't trigger for a fairly simple use case:

val x: Int? = 1

val notNull: Boolean = x != null

if (notNull) {
    val y: Int = x // fails to smart cast
}

However it works if we remove the intermediate val:

val x: Int? = 1

if (x != null) {
    val y: Int = x // smart cast works
}

val essentially defines a final value, so I don't see the reason why the first version wouldn't work.

For the record Java's analog to this (https://github.com/uber/NullAway) also fails for such use cases so there might be some underlying complication that makes such deductions impossible?

Grozz
  • 8,317
  • 4
  • 38
  • 53
  • It isn't impossible. But it would take effort and may be harder for users to understand when they can expect a cast and when they can't. – Alexey Romanov Feb 08 '19 at 13:59
  • 4
    The compiler does **not** track boolean variables keeping track that `notNull` is true iff `x` is not null. It only checks for what it sees in the condition expression. So it sees that `notNull` is a boolean which does not imply anything on `x`. Keep in mind that you could have an abrirtary number of `x` values and corresponding conditions and the `if` condition could be arbitrarily complex. It's a lot of work for a small gain. [The documentation](https://kotlinlang.org/docs/reference/typecasts.html#smart-casts) never states that boolean variables are somehow "tracked" – Giacomo Alzetta Feb 08 '19 at 14:02
  • 1
    It fails because the implementation wasn't designed to make multiple steps of inferences. What more explanation can we give? – Michael Feb 08 '19 at 14:12
  • Request this feature on kotlin issue tracker: https://youtrack.jetbrains.com/issues/KT Browse for "#Open smart cast" issues to see what else isn't permitted yet. – Eugen Pechanec Feb 09 '19 at 16:58

1 Answers1

0

You have to see it like that. notNull is assigned with true (the result of x != null). After this line there is no information available for the compiler to make the connection between that value and the nullability of x.

It's basically the same as writing it like this right away:

val notNull = true // this is all the compiler knows the time it evaluates the if expression.

if (notNull) {
    val y: Int = x // fails to smart cast
}

You could argue the same for

if (x != null) {
    val y: Int = x // smart cast works
}

you will say. True, but in this case the compiler offers x in the if block right away as non-null. That's where it draws the line. If you don't stop somewhere you will end up with a lot of inference making the process of compilation less efficient.

Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
  • I'm just debating here: The code graph isn't available at runtime, fine. But it is available at compile time. The compiler knows the x will never be anything other than non-null value, 1. I mean, that's how it arrived at `val notNull = true` in the first place, right? – Eugen Pechanec Feb 09 '19 at 16:55
  • @EugenPechanec sure, the compiler could know it, but you have to draw a line somewhere otherwise you sacrifice performance (using multi level inference). – Willi Mentzel Feb 09 '19 at 17:11