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?

Bohemen90
  • 131
  • 3

2 Answers2

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() ?: ""
Andrei Tanana
  • 7,932
  • 1
  • 27
  • 36
  • As suggested by @Andrei Tanana, your `runCatching` invocation in `capitalize2` is an invocation of `public inline fun ` T.runCatching(block: T.() -> R) : Result` and type parameters are inferred as `runCatching`: `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... – Pietro Martinelli Sep 17 '19 at 12:22
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.

Pietro Martinelli
  • 1,776
  • 14
  • 16