2017-10-20 10 views
6

Nie jestem pewien, czy "ciężkie zawody" są właściwym słowem, ale tutaj jest problem, z którym się borykam. I zajęło mi trochę czasu, aby odtworzyć to, aby możliwie najmniejszej przykład, więc o to idzie:Kotlin ciężko zaprzepaszczony w górę do wywnioskowanego (na miejscu) parametru

class BaseParameterizedType<T> 

fun <U: BaseParameterizedType<*>> getSpecific(clazz: KClass<in U>) : U { 
    TODO() 
} 

fun example(arg: KClass<out BaseParameterizedType<*>>)) { 
    getSpecific(arg.innerType) 
} 

Ok, więc powyższy kod nie powiedzie się w „TODO”, ale jeśli to nie było, a jeśli funkcja zwrócona normalnie, to zdecydowanie kończy się niepowodzeniem z wyjątkiem wskaźnika pustego. I starał się dowiedzieć, co się dzieje źle, więc zwróciłem się do dekompilacji kodu Java (od kodu bajtowego Kotlin):

public static final void example(@NotNull KClass arg) { 
    Intrinsics.checkParameterIsNotNull(arg, "arg"); 
    getSpecific(arg.getInnerType()); 
    throw null; // <-- The problem 
} 

Gdybym zmienić podpis funkcję getSpecific(clz: KClass<in U>) : U do żadnej z tych form:

  1. getSpecific(clz: KClass<out U>) : U
  2. getSpecific(clz: KClass<U>) : U
  3. getSpecific(clz: KClass<in U>) : BaseParameterizedType<*>

lub nawet funkcja do example(arg: KClass<out BaseParameterizedType<*>) lub example(arg: KClass<BaseParameterizedType<*>>), wówczas generowany kod jest:

public static final void example(@NotNull KClass arg) { 
    Intrinsics.checkParameterIsNotNull(arg, "arg"); 
    getSpecific(arg.getInnerType()); 
} 

Teraz, powiedzmy, że na wezwanie miejscu, zmienić go na adres:

getSpecific(BaseParameterizedType::class) 

wtedy to też nie wygenerować klauzulę throw null. Zgaduję więc, że ma to coś wspólnego z kotliną zakładając, że ta obsada zawsze zawiedzie lub że dostępne są nieokreślone informacje, aby dokonać wnioskowania?

Tak, wiemy, że arg.innerType jest KClass<out BaseParameterizedType<*>> i używamy go w miejscu przyjmującej KClass<in BaseParameterizedType<*>>, więc dlaczego nie jest U wywnioskować do BaseParamterizedType<*>>. To jest dosłownie jedyny typ, który kiedykolwiek będzie pasował.

Jednocześnie, myślę, że wygenerowanie oświadczenia throw null jest niewiarygodnie trudne do debugowania. Stacktrace po prostu wskazywałby linię, na której znajduje się getSpecific i powodzenia, wykreślając skąd pochodził wyjątek wskaźnika pustego.

Odpowiedz

4

Jest to znany problem w odniesieniu do sprawy rogu wnioskowanie typu obsługi, gdy wywnioskować typ jest Nothing (i to jest w Twoim przypadku):

enter image description here

Wnioskowanie zachowuje się w ten sposób ze względu na próbę przymusu dla projekcji KClass<in U> i KClass<out BaseParameterizedType<*>>.

Zasadniczo out -projected typ jednocześnie oznacza in Nothing (ponieważ rzeczywisty typ argumentu może być którykolwiek z podtypów, i nic nie może być bezpiecznie przekazywane w). Tak więc, aby dopasować KClass<out BaseParameterizedType<*>> z KClass<in U> kompilator wybiera U := Nothing, co oznacza, że ​​wywołanie funkcji zwraca również Nothing.

Uwaga: a Foo<out Any> występ nie może dorównać Foo<in T> z T := Any, ponieważ rzeczywisty typ argumentu wartość przekazana do Foo<out Any> może być, na przykład, Int. Następnie, jeśli Foo<T> akceptuje w niektórych swoich funkcjach, zezwolenie na wyżej wymieniony mecz pozwoli również na przekazanie instancji Any do miejsca, gdzie Foo<Int> ich nie oczekuje. W rzeczywistości, in Nothing staje się jedynym sposobem na ich dopasowanie, ze względu na nieznaną naturę projektu -projected typu out.

Po tym, dla Nothing -returning wywołania funkcji, kompilator wstawia że throw null kodu bajtowego aby upewnić się, że realizacja nie przebiega (oceniania Nothing -typed wyraz is supposed to never finish correctly).

Zobacz zagadnienia: KT-20849, KT-18789

+0

Czy tę powierzchnię emisyjną niedawno? Ponieważ jest to część kodu, który działał idealnie dla mnie, i pękł dopiero niedawno. Przykro mi, że nie będę w stanie wskazać dokładnej wersji kotlin, w której zaczęło się łamać, ponieważ ciągle aktualizujemy wersję i jest to coś, co niestety nie jest objęte naszymi testami i jest rzadko osiągalnym fragmentem kod. Trudno jest więc ustalić, kiedy dokładnie zaczęło się łamać. –

1

Podobnie jak @hotkey wspomniano, out oznacza in Nothing i Nothing rzuci null.So zrobić kilka testów tak:

fun main(args: Array<String>) { 
    tryToReturnNothing() 
} 

fun tryToReturnNothing(): Nothing{ 
    TODO() 
} 

Generowanie ->

public static final void main(@NotNull String[] args) { 
     Intrinsics.checkParameterIsNotNull(args, "args"); 
     tryToReturnNothing(); 
     throw null; // here 
    } 

    @NotNull 
    public static final Void tryToReturnNothing() { 
     throw (Throwable)(new NotImplementedError((String)null, 1, (DefaultConstructorMarker)null)); 
    } 

Biorąc pod uwagę typ null jest Nothing?, możemy zwrócić Nothing? zamiast Nothing. Więc mogę zmienić U do U?, a następnie zniknąć klauzula throw null:

fun <U: BaseParameterizedType<*>> getSpecific(clazz: KClass<in U>) : U? { // see here: change U to U? 
    TODO() 
} 

fun example(arg: KClass<out BaseParameterizedType<*>>) { 
    getSpecific(arg) 
} 

Generowanie ->

@Nullable 
    public static final BaseParameterizedType getSpecific(@NotNull KClass clazz) { 
     Intrinsics.checkParameterIsNotNull(clazz, "clazz"); 
     throw (Throwable)(new NotImplementedError((String)null, 1, (DefaultConstructorMarker)null)); 
    } 

    public static final void example(@NotNull KClass arg) { 
     Intrinsics.checkParameterIsNotNull(arg, "arg"); 
     getSpecific(arg); 
    } 
Powiązane problemy