Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Smartcast not work after nullcheck

Tags:

kotlin

I'm attempting advent of code and wanted to create a class for day 10. I know that the values can be null, so I declared them as nullable. At some point, I need to check whether the value is assigned and do something with it. There comes the issue. I check beforehand via high != null, but in the line that follows, I have to use !! to convince the compiler that it actually is null.

It seems that it can't find the proper compareTo method, despite nullchecking it first. I guess, it didn't smartcast my variable

private class Bot(val number: Int, var low: Int?, var high: Int?) {

  fun acceptValue(value: Int) {
    if (low == null && high == null) {
      high = value
    } else {
      if (high != null) {
        if (high!! > value) { //it doesn't compile, because appareantly, high is still not considered nonnull at this point
          low = value
        } else {
          low = high
          high = value
        }
      }
    }
  }
}

the kotlin-version is use is 1.1.3-2

Is that a bug? Am I doing something wrong?

like image 225
TormundThunderfist Avatar asked Aug 10 '17 08:08

TormundThunderfist


3 Answers

Since you defind high as a var, it is mutable. You cannot guarantee the variable is not null even if you have an explicit null check before that.

Official explanation:

Note that smart casts do not work when the compiler cannot guarantee that the variable cannot change between the check and the usage. More specifically, smart casts are applicable according to the following rules:

  • val local variables - always;
  • val properties - if the property is private or internal or the check is performed in the same module where the property is declared. Smart casts aren't applicable to open properties or properties that have custom getters;
  • var local variables - if the variable is not modified between the check and the usage and is not captured in a lambda that modifies it;
  • var properties - never (because the variable can be modified at any time by other code).

In you case, you can use .let:

high?.let {
    if (it > value) {
        low = value
    } else {
      low = it
      high = value
    }
} ?: run {
    //high == null
}

Suggested reading: In Kotlin, what is the idiomatic way to deal with nullable values, referencing or converting them

like image 58
BakaWaii Avatar answered Nov 12 '22 22:11

BakaWaii


Between high != null and high > value another thread could do high = null, invalidating the null check. So this is the expected behavior.

The way to solve this is to use a temporary variable which cannot be externally changed:

val cHigh = high
if (cHigh != null) {
    if (cHigh > value) {
        ....
like image 38
Kiskae Avatar answered Nov 12 '22 22:11

Kiskae


You can solve this issue if you declare the propertiries private:

private class Bot(val number: Int, 
                  private var low: Int?, 
                  private var high: Int?) {

    ...
}
like image 1
voddan Avatar answered Nov 13 '22 00:11

voddan