Home > Software engineering >  Different results on similar code with safe call operator in Kotlin
Different results on similar code with safe call operator in Kotlin

Time:07-19

I'm new to Kotlin and these two below codes give different results.

fun main() {
  var name: String? = "Rajat"
  name = null
  print(name?.toLowerCase())
}

Output: Compilation Error (illegal access operation)

fun main() {
  var name: String? = null
  print(name?.toLowerCase())
}

Output: null

CodePudding user response:

When you do this assignment:

name = null

name is smart casted to Nothing?, which is problematic. Nothing is the subtype of every type, and so you become able to call any accessible extension functions of any type, according to the overload resolution rules here.

Compare:

fun main() {
    var name: String? = "Denis"
    name = null
    print(name?.myExtension()) // works

    val nothing: Nothing? = null
    print(nothing?.myExtension()) // also works
}

fun Int.myExtension(): Nothing = TODO()

Note that allowing you to call any extension function on Nothing is perfectly safe - name is null anyway, so nothing is actually called.

Char.toLowerCase and String.toLowerCase happen to be two of the extension functions that are accessible, and you can call both on name, which is now a Nothing?. Therefore, the call is ambiguous.

Note that smart casts only happens in assignments, not in initialisers like var name: String? = null. Therefore, name is not smart casted to Nothing? in this case:

fun main() {
    var name: String? = null
    print(name?.toLowerCase()) // better to use lowercase(), toLowerCase is deprecated!
}

For the reason why, see my answer here.

CodePudding user response:

The actual error on your first example is

Overload resolution ambiguity: public inline fun Char.toLowerCase(): Char defined in kotlin.text public inline fun String.toLowerCase(): String defined in kotlin.text

Looks like the Kotlin compiler is being too smart for its own good here. What's happening, is that on the second example, you are explicitly defining a variable of type String? and assigning it some value (null in this case, but that doesn't matter).

On the second example, you are defining a variable of some type, and then telling the compiler "hey, after this assignment, name is always null". So then it remembers the more-specific "name is null" instead of "name is String?".

The standard library has two methods called toLowerCase, one on Char and one on String. Both of them are valid matches now, and the compiler is telling you it doesn't know which one to pick. In the end that won't matter, because name is null, but the compiler apparently doesn't use that final thing to throw out the method call altogether.

  • Related