7
votes

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?

2

2 Answers

4
votes

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() ?: ""
1
votes

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.