Функция расширения Kotlin - компилятор не может сделать вывод, что значение NULL не равно NULL

Скажем, у меня есть простой класс Foo с обнуляемым String?

data class Foo(
    val bar: String?
)

и я создаю простую функцию capitalize

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

что отлично работает, потому что компилятор делает вывод, что foo.bar не может иметь значение NULL, хотя его тип допускает значение NULL. Но потом я решил написать такую ​​же функцию как расширение Foo

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

и внезапно компилятор больше не может сделать вывод, что bar не является нулевым, и IntelliJ сообщает мне, что «только безопасные (?.) или ненулевые утвержденные (!!.) вызовы разрешены для приемника типа, допускающего значение NULL. Нить?"

Кто-нибудь может объяснить почему?


person Bohemen90    schedule 17.09.2019    source источник


Ответы (2)


Я думаю, это потому, что в первом случае вы вызываете эту функцию:

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

но во втором случае вы вызываете функцию с приемником:

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

Для меня это похоже на проблему в компиляторе Kotlin, потому что, если вы встраиваете код этой функции самостоятельно, она будет работать нормально:

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

кстати, на вашем месте я бы написал свою capitalize2 функцию вот так :)

fun Foo.captitalize2() = bar?.capitalize() ?: ""
person Andrei Tanana    schedule 17.09.2019
comment
Как предложил @Andrei Tanana, ваш runCatching вызов в capitalize2 - это вызов public inline fun <T, R> T.runCatching (block: T. () - ›R): Result ‹R›`, а параметры типа выводятся как runCatching<Foo, String>: T is Foo для компилятор, а не Foo having not null property bar, поэтому вы получаете ошибку компиляции ... Когда вы встраиваете функцию, нет вывода типа между when и bar.capitalize(), поэтому компилятор счастлив, и у вас нет ошибок ... - person Pietro Martinelli; 17.09.2019

Итак, наконец, я нашел альтернативный подход, который позволяет нам использовать runCatching без проблем, которые вы показываете. Как и в моем комментарии к ответу @Andrei Tanana, в вашем коде параметры типа fun <T, R> T.runCatching(block: () -> R) : Result<R> выводятся как <Foo, String>, и компилятор не может использовать информацию о том, что this.bar не null.

Если вы перепишете функцию capitalize2 следующим образом

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

T выводится как String (благодаря случаю bar != null выражения when), и компилятор не жалуется на вызов this.capitalize() в блоке, переданном в runCatching.

Я надеюсь, что это поможет вам как в качестве подхода, позволяющего решить проблему, так и в качестве объяснения самой проблемы.

person Pietro Martinelli    schedule 17.09.2019