4

Error on the second println :

Smart cast to 'Boolean' is impossible, because 'r.isSquare' is a mutable property that could have been changed by this time

fun main(args: Array<String>) {
    val r: Rectangle = Rectangle(5,5)
    println(r.isSquare)
    r.isSquare = true
    println(r.isSquare) // error but works with println(r.isSquare?:false)

}

data class Rectangle(var height: Int, var width: Int){
    var isSquare: Boolean? = null
}

If it was null, it would print null like the first println, why do i have to do this ?

Edit 2

Thanks for all your answers, what i understand now : First println is

println(message: Any?)

Second println is

println(message: Boolean)

Because r.isSquare = true make compiler trust that isSquare is Boolean and not anymore Boolean?

Edit2

Here is how i handle the compiler to keep trusting isSquare is Boolean?

fun main(args: Array<String>) {
    val r: Rectangle = Rectangle(5, 5)
    println(r.isSquare)
    r.isSquare = true as Boolean? // if no cast, he will try wrong println signature
    println(r.isSquare)
}

data class Rectangle(var height: Int, var width: Int){
    var isSquare: Boolean? = null
}
Joshua
  • 40,822
  • 8
  • 72
  • 132
Z3nk
  • 365
  • 4
  • 17

4 Answers4

5

Since the r.isSquare is a mutable property, the compiler cannot smart cast it to a non-null property after a null check.

You can use let:

r.isSquare.let { println(it) }

let reads the value of r.isSquare only once and it provides the same value as it inside the lambda. So you don't have to use ? or !! to access the boolean even after the null check.

From the Kotlin spec:

The language uses information about preceding checks for null, checks for types (is, !is), safe call operators (?.) and Nothing-returning expression to infer additional information about types of variable (beyond that explicitly specified or inferred from initializers at their declarations) that may be more specific in certain blocks or even expressions. This information is then used to enable wider set of operations on those expressions and to select more specific overloads.

fun main(args: Array<String>) {
    var x : Any
    x = ""
    x.toUpperCase() // OK, smart cast to String
}

The first println uses this println(message: Any?)

Since you are assigning true to the isSquare next, the compiler tries to smart cast the isSquare to the Boolean type, when you try to print that. But it couldn't smart cast because the property is a mutable type.

If you remove the line, r.isSquare = true, then the compiler does not try to smart cast it to Boolean and uses the println with Any? as parameter.

Bob
  • 13,447
  • 7
  • 35
  • 45
  • But `println` accepts a nullable: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/println.html – Héctor Aug 23 '17 at 09:56
  • The nullable type is allowed only for `Any`. In your example, the `println` is the one which has `Boolean` as the parameter. You can force that to use the `println` with `Any?` like this: `println(r.isSquare as Any?)` – Bob Aug 23 '17 at 10:03
  • println(r.isSquare as Boolean?) make it works but there is a warning : "no cast needed", if i remove this cast i go back in the error .. So, on one hand compiler works but say i don't need this cast, on other hand i remove the cast because no need but it doesn't work anymore – Z3nk Aug 23 '17 at 10:09
  • When do the compiler smart cast it to a non-null property after a null check ? – Z3nk Aug 23 '17 at 10:11
  • If the property is a `val`, the compiler will smart cast it to a non-null property. – Bob Aug 23 '17 at 10:12
  • 1
    The actual type of the `isSquare` is `Boolean?`. That's why you are getting "no cast needed" when you cast it to `Boolean?`. If you don't cast it to anything, the compiler tries to cast it to `Boolean` type for the `println`. But since its a var mutable type, it couldn't do that and shows error. – Bob Aug 23 '17 at 10:16
  • First println signature : println(message: Any?) Second println signature : println(message: Boolean) Means that, r.isSquare = true, make compiler forget that isSquare is Boolean? and trust that is Boolean – Z3nk Aug 23 '17 at 10:17
  • Updated the answer with more info. – Bob Aug 23 '17 at 10:21
  • I updated the topic with all this informations, thanks – Z3nk Aug 23 '17 at 10:26
  • Are there any discussion from other community about this behavior? It looks wired for me. Why do it try to smart cast a `var` property? – BakaWaii Aug 23 '17 at 11:18
0

In order for it to work you have to add a non null asserted call (!!) after your variable. Either r!!.isSquare or r.isSquare!!

fun main(args: Array<String>) { 
  val r: Rectangle = Rectangle(5,5) 
  println(r.isSquare) 
  r.isSquare = true println(r.!! isSquare) 
}

data class Rectangle(var height: Int, var width: Int) {
  var isSquare: Boolean? = null 
}
Héctor
  • 24,444
  • 35
  • 132
  • 243
  • you don't answer the question, i know that i have to do a non null asserted call or like i wrote in my comment println(r.isSquare?:false) My question is why i have to do this in second println and not the first one ? Furthermore, println can print null value (only isSquare can be null in my code, not r) – Z3nk Aug 23 '17 at 09:57
0

As isSquare is a mutable property (var). It means between lines where do you write a value and then you read it, another thread can modify it and get a NPE because of that.

r.isSquare = true
//Another thread set r.isSquare = null
println(r.isSquare) // you get a null pointer exception

You must check the property nullability every time you work with nullable vars.

crgarridos
  • 8,758
  • 3
  • 49
  • 61
0

Because println() function in Android doesn't support Boolean?, and Boolean? with mutable property in kotlin cannot be unwrapped automatically by smart cast of Kotlin.

Try out String? or Int? or any types with mutable and nullable properties will get the same thing happen.

dwqdq13213
  • 317
  • 1
  • 10