Search code examples
lambdakotlincallbacktype-inferencekotlin-null-safety

Why does this non-nullable val become "nullable"?


So I have a simple callback class:

class Callback<T>(
    val onResponse: (T) -> Unit,
    val onError:(Throwable)->Unit
)

Now I want to implement a method that handles errors. There may or may not be an callback that needs to be invoked.

private fun handleServerError(error:IServerError, callback:Callback<*>? = null){
    val reason = error.cause

    when(reason){
        is Because.ServerRejectsLogin -> {
            doAsync { uiThread { mainActivity.longToast("sorry, your session timed out. please log in again.") } }
            IntentManager.doLogin(mainActivity)
        }
        else -> callback?.onError(reason)
    }
}

This gets me an error:

Reference has a nullable type ((Throwable) -> Unit)? use explicit ?.invoke() to make a function-like call, instead

What it seems to expect is

else -> callback?.onError?.invoke(reason)

and I don't quite understand why. Shouldn't the fact that callback is not null be sufficient to derive that there must be a non-null onError function?

To add insult to injury, if I write

else -> callback?.let{it.onError(reason)}

then it accepts that but not before warning me that I should

remove redundant .let call


Solution

  • The callback?.onError() syntax would be correct if you were calling a function called onError on the callback object with the safe call operator. However, in this case, you are first reading a property of callback, and then calling another function on what the property returned.

    So instead of the expression consisting of just one step, and two pieces:

    callback   ?.onError()
    

    What you actually have is an expression of three parts, with two operators in a row:

    callback   ?.onError   ()
    

    The last step here, (), is a call to the invoke operator of the object that onError returns:

    callback   ?.onError   .invoke()
    

    However, since the onError property is read with a safe call operator, this object might be null. In this case, you can't use invoke in its operator form (same goes for any other operator, by the way), so you have to explicitly write it out, with another safe call added:

    callback   ?.onError   ?.invoke()
    

    As for the intention action telling you that you can remove the redundant let: that's a bug, and should be reported on the issue tracker.