Я получаю ошибку неоднозначности разрешения перегрузки при безопасном вызове kotlin

У меня есть нулевая строковая переменная ab. Если я вызываю toUpperCase через оператор безопасного вызова после того, как я присвою ему нуль, kotlin дает ошибку.

fun main(args: Array<String>){
    var ab:String? = "hello"
    ab = null
    println(ab?.toUpperCase())
}

Ошибка: (6, 16)
Недостаток разрешения перегрузки:
@InlineOnly public inline fun Char.toUpperCase(): Char, определенный в kotlin.text
@InlineOnly public inline fun String.toUpperCase(): Строка, определенная в kotlin.text

В чем проблема?

Ответ 1

Как указано в этом doc о smart-casts:

x = y задает x типа y после присваивания

Линия ab = null, вероятно, умная передача ab до Nothing?. Если вы проверите ab is Nothing?, это действительно true.

var ab: String? = "hello"
ab = null
println(ab?.toUpperCase())
println(ab is Nothing?) // true

Так как Nothing? является подтипом всех типов (включая Char? и String?), это объясняет, почему вы получаете ошибку Overload resolution ambiguity. Решением этой ошибки будет то, о чем Вилл Менцель упомянул в его ответе, выбрав ab в тип String перед вызовом toUpperCase().


Примечания: Такая ошибка возникает, когда класс реализует два интерфейса, и оба интерфейса имеют функцию расширения одной и той же сигнатуры:
//interface
interface A {}
interface B {}

//extension function
fun A.x() = 0
fun B.x() = 0

//implementing class
class C : A, B {}

C().x()    //Overload resolution ambiguity
(C() as A).x()    //OK. Call A.x()
(C() as B).x()    //OK. Call B.x()

Ответ 2

Я не уверен, но это похоже на ошибку из-за умного кастинга (до Nothing?, подтипа каждого типа с нулевым именем). Это работает:

fun main(args: Array<String>) {
    var ab: String? = "hello"
    ab = makeNull()
    println(ab?.toUpperCase())
}

fun makeNull(): String? = null

Единственное различие: компилятор не знает назначение null напрямую, что, по-видимому, вызывает ошибку в вашем примере. Но все же, вероятно, вам тоже придется работать.

Ответ 3

Это действительно похоже на ошибку. Тип String? как-то теряется при назначении null, поэтому вы должны явно сказать компилятору, что он должен иметь дело с String?.

fun main(args: Array<String>){
    var ab: String? = "hello"
    ab = null
    println((ab as String?)?.toUpperCase()) // explicit cast

    // ...or

    println(ab?.let { it.toUpperCase() }) // use let
}

Ответ 4

Я считаю, что это связано с умными приведениями, используемыми Kotlin. Другими словами, Котлин может сделать вывод, что после этой строки кода:

ab = null

тип переменной ab - это просто null (это не фактический тип, который вы можете использовать в Kotlin - я просто ссылаюсь на диапазон допустимых значений), а не String? (другими словами, существует no way ab может содержать String).

Учитывая, что функция расширения toUpperString() определена только для Char и String (а не Char? или String?), нет способа выбрать между ними.

Чтобы избежать такого поведения, см. ответы, предложенные другими ребятами (например, явное приведение в String?), но это определенно выглядит как функция (и довольно полезная), а не ошибка для меня.

Ответ 5

Я декомпилировал вашу функцию, и я подумал: после того, как вы сделаете ab = null, компилятор будет smartcast его, помещая null (ACONST_NULL) в каждое возникновение ab. Тогда как null не имеет типа. вы не можете указать тип приемника toUpperCase().

Это java-эквивалентный код, сгенерированный из байтового кода kotlin:

public final void main(@NotNull String[] args) {
   Intrinsics.checkParameterIsNotNull(args, "args");
   String ab = "hello";
   ab = (String)null;
   Object var3 = null;
   System.out.println(var3);
}

Он выглядит как проблема, которая должна быть решена командой kotlin.