Kotlin extension function - compiler cannot infer that nullable is not null
Asked Answered
A

2

8

Let's say I have a simple class Foo with a nullable String?

data class Foo(
    val bar: String?
)

and I create a simple function capitalize

fun captitalize(foo: Foo) = when {
    foo.bar != null -> runCatching { foo.bar.capitalize() }
    else -> ""
}

which works fine, because the compiler infers that foo.bar cannot be null eventhough it's type is nullable. But then I decide to write the same function as an extension of Foo

fun Foo.captitalize2() = when {
    bar != null -> runCatching { bar.capitalize() }
    else -> ""
}

and all of a sudden the compiler is no longer able to infer that bar is not null, and IntelliJ tells me that "only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable reciever of type String?"

Can anyone explain why?

Axes answered 17/9, 2019 at 7:41 Comment(0)
S
4

I think it's because in the first case you are calling this function:

public inline fun <R> runCatching(block: () -> R): Result<R> {
    return try {
        Result.success(block())
    } catch (e: Throwable) {
        Result.failure(e)
    }
}

but in the second case you are calling function with receiver:

public inline fun <T, R> T.runCatching(block: T.() -> R): Result<R> {
    return try {
        Result.success(block())
    } catch (e: Throwable) {
        Result.failure(e)
    }
}

For me, it looks like an issue in the Kotlin compiler because if you inline code of this function by yourself it will work fine:

fun Foo.captitalize2() = when {
    bar != null -> try {
        Result.success(bar.capitalize())
    } catch (e: Throwable) {
        Result.failure<String>(e)
    }
    else -> ""
}

btw, if I were you I would like to write my capitalize2 function like this :)

fun Foo.captitalize2() = bar?.capitalize() ?: ""
Slaver answered 17/9, 2019 at 9:11 Comment(1)
As suggested by @Andrei Tanana, your runCatching invocation in capitalize2 is an invocation of public inline fun <T, R> T.runCatching(block: T.() -> R) : Result<R>` and type parameters are inferred as runCatching<Foo, String>: T is Foo for the compiler, not Foo having not null property bar, so you obtain the compilation error... When you inline the function there is no type inference between when and bar.capitalize(), so the compiler is happy and you have no errors...Rancell
R
1

So, finally I found an alternative approach that allows us to use runCatching without having the problem you shows. As in my comment to the answer of @Andrei Tanana, in your code type parameters of fun <T, R> T.runCatching(block: () -> R) : Result<R> are inferred as <Foo, String> and the compiler can't use the information that this.bar is not null.

If you rewrite the capitalize2 function as follows

fun Foo.capitalize2(): Serializable = when {
    bar != null -> bar.runCatching { capitalize() }
    else -> ""
}

T is inferred as String (thanks of the bar != null case of the when expression) and the compiler does not complain about this.capitalize() invocation in the block passed to runCatching.

I hope this can help you, both as an approach than allows you to solve the problem and as explanation of the problem itself.

Rancell answered 17/9, 2019 at 12:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.