Why is this.isNullOrEmpty() not equivalent to (this == null || this.isEmpty())


#1

I have noticed that for String type isNullOrEmpty() is not treated the same as (this == null || this.isEmpty()). It seems that the two are semantically identical, at least from the point of view of the function name.

Below, I added infered type to with(), explicitly:

object Test {   val isNullable: String? = null   val isNotNullable = ""   val test1 = with<String?, String?>(isNullable) { if (this.isNullOrEmpty()) isNotNullable else this }   val test2 = with<String?, String>(isNullable) { if (this == null || this.isEmpty()) isNotNullable else this } }

The behaviour of isNullOrEmpty() seems counter intuitive. It applies across the board not just lambdas or with() expressions. The same holds for if()...

What am I missing in this code that causes the compiler or at least the Kotlin plugin to treat the two results as having different nullability?


#2

isNullOrEmpty is a plain function and the compiler doesn’t know anything about its contract: when it returns false it means that the receiver string was definitely not null.

It is possible to specify contract of a function with a @Contact annotation: https://www.jetbrains.com/idea/help/contract-annotations.html, but unfortunately the data flow analysis (a process which allows to smart cast values after their type was statically proven) doesn’t take this annotation into account.
I think that annotation may be supported later, I’ve found it is mentioned in the issue https://youtrack.jetbrains.com/issue/KT-8889. Feel free to upvote.


#3

Thanks Ilya, it makes perfect sense. That makes the isNullOrEmtpy() not very useful in Kotlin.

Since most of the time the test is used to provide an alternate value, an easy workaround is to define a member function similar to SQL IFNULL()

fun String?.ifNullOrEmpty(altValue:String): String {   return if (this == null || this.isEmpty()) altValue else this }

object Test {
  val isNullable: String? = null
  val isNotNullable = “”
  val test1 = with<String?, String>(isNullable) { this.ifNullOrEmpty(isNotNullable) }
  val test2 = with<String?, String>(isNullable) { if (this == null || this.isEmpty()) isNotNullable else this }
}

I just love Kotlin.


#4

I thought of a couple of usefull aditions to strings to replace the isNullOrEmpty(), isNullOrBlank() and to compensate for lack of tertiary operator

fun String?.ifNullOr(condition:Boolean, altValue:String): String {   return if (this == null || condition) altValue else this }

fun String?.ifNullOrNot(condition:Boolean, altValue:String): String {
  return if (this == null || !condition) altValue else this
}

inline fun String?.ifNullOr(condition:(String)->Boolean, altValue:String): String {
  return if (this == null || condition(this)) altValue else this
}

inline fun String?.ifNullOrNot(condition:(String)->Boolean, altValue:String): String {
  return if (this == null || !condition(this)) altValue else this
}

fun String?.ifNullOrEmpty(altValue:String): String {
  return if (this == null || this.isEmpty()) altValue else this
}

fun String?.ifNullOrBlank(altValue:String): String {
  return if (this == null || this.isBlank()) altValue else this
}


#5

I know this topic is pretty old but is there any chance that Kotlin’s standard functions will be annotated by @Contract so that the IDE/compiler for instance knows that certain values can never be null (for example in if (!string.isNullOrEmpty() && string.toLowerCase() == "hello"))? This would greatly help in avoiding the usage of the double bang !! operator.


#6

These functions are also inline and therefore the compiler should be able to inspect/evaluate the code instead.


#7

The compiler’s ability to inspect the code has nothing to do with whether a function is inline or not.


#8

Perhaps I should be clearer, I meant that for non-inline functions living in some library the compilation at the usage side will not be able to infer the semantics (even if it could, class substitution could break it after-the-fact). Inline functions (when used inline) can be semantically inlined into the call-site and then be used in the nullability analysis (it might require a second pass tough (where the first pass is needed for overload resolution of the inline function itself)).


#9

I saw that isNullOrEmpty() is an inline function and was wondering why the compiler cannot (yet?) evaluate the expression (see my code example above).


#10

Separate compilation is the only issue here. Otherwise, an inline function is compiled to the same bytecode as a non-inline one, and the abililty to analyze it is the same.

Of course, the main problem here is the cost of analyzing the function in terms of compilation performance (especially given that a function can call other functions, so the depth of analysis required may be significant).


#11

Would the class file representation of an inline function not contain (a representation of) the source code / abstract syntax tree? Without that things get hard. Of course there may need be a limit on function complexity as well (esp. with recursive inline functions), but for this specific case that is obviously not needed. Java may be sufficiently abstract to get away with just storing the compilation result, but machine code (native target) certainly is not, LLVM IR may be (haven’t looked into that).


#12

No. Inlining of functions works on bytecode level.


#13

There is a new code shown in kotlin 1.2.0. This has “contract”.

@kotlin.internal.InlineOnly
public inline fun CharSequence?.isNullOrEmpty(): Boolean {
contract {
returns(false) implies (this@isNullOrEmpty != null)
}

return this == null || this.length == 0

}


#14

Where can we learn more about it. Googling it yields unrelated results.


#15

There is a brief explanation here:

https://youtrack.jetbrains.com/issue/KT-8889

Apparently this is being prepared for 1.3.