This kotlin code:
fun badKotlin(text: String?): Boolean {
if (text == null) {
return true
}
var temp = text
if (false) {
temp = Arrays.deepToString(arrayOf(text))
}
return temp.isBlank() // <-- only safe (?.) or non null asserted (!!.) calls
}
does not compile with message: only safe (?.) or non null asserted (!!.) calls are allowed on a nullable receiver of type String?
But if I add else
:
fun badKotlin(text: String?): Boolean {
if (text == null) {
return true
}
var temp = text
if (false) {
temp = Arrays.deepToString(arrayOf(text))
} else {
temp = Arrays.deepToString(arrayOf(text))
}
return temp.isBlank()
}
all compiled. So, why type inferrence failed?
If I change type of temp to var temp: String = text
it is successfully copmiled! So, moreover, If we change assignment of temp like this: temp = String.format("%s", text)
it is compiled too.
UPDATE:
Successfully copmpiled:
fun badKotlin(text: String?): Boolean {
if (text == null) {
return true
}
var temp = text
if (false) {
temp = String.format("%s", text)
}
return temp.isBlank() // <-- only safe (?.) or non null asserted (!!.) calls
}
And this:
fun badKotlin(text: String?): Boolean {
if (text == null) {
return true
}
var temp: String = text
if (false) {
temp = Arrays.deepToString(arrayOf(text))
}
return temp.isBlank() // <-- only safe (?.) or non null asserted (!!.) calls
}
You might be thinking that after
if (text == null) {
return true
}
the type of text
is refined to String
instead of String?
.
But it seems like it isn't; instead the compiler inserts a smart cast when it sees text
is used where a String
is required. In the
var temp = text
line there is no reason to insert a cast, so the compiler doesn't, and the type of temp
is String?
.
If you write
var temp: String = text
the cast is necessary and so the compiler does insert it.
If you write
if (...) {
temp = Arrays.deepToString(arrayOf(text))
} else {
temp = Arrays.deepToString(arrayOf(text))
}
the compiler sees that whatever happens, temp
has been assigned a value of platform type String!
which again can be smart-cast to String
. Without a else
branch, this doesn't happen.
EDIT:
Curiously, if you just remove if
and leave
fun badKotlin(text: String?): Boolean {
if (text == null) {
return true
}
var temp = text
return temp.isBlank()
}
it does compile, and if my explanation was complete I wouldn't expect it to. So the compiler does maintain information needed for the smart cast, but it appears not to get applied because
More specifically, smart casts are applicable according to the following rules: ...
var
local variables - if the variable is not modified between the check and the usage, is not captured in a lambda that modifies it, and is not a local delegated property;
In the if-else
case, the assignments in two branches together serve as another check; in the if
-only, the one branch doesn't.