Kotlin hard-failing up-cast a un parámetro inferido (en el sitio)

No estoy seguro de si la palabra correcta es "difícil", pero este es el problema al que me estoy enfrentando. Y me ha llevado bastante time reproducir esto en el menor ejemplo posible, así que aquí va:

class BaseParameterizedType<T> fun <U: BaseParameterizedType<*>> getSpecific(clazz: KClass<in U>) : U { TODO() } fun example(arg: KClass<out BaseParameterizedType<*>>)) { getSpecific(arg.innerType) } 

Ok, entonces el código anterior falla en el 'TODO', pero si no estaba allí y si la function regresó normalmente, entonces definitivamente falla con una exception de puntero nulo. Intenté averiguar qué estaba pasando mal, así que recurrí al código Java descomstackdo (del bytecode de kotlin):

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

Si cambio la firma de function de getSpecific(clz: KClass<in U>) : U a cualquiera de estas forms:

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

o incluso la function a example(arg: KClass<out BaseParameterizedType<*>) o example(arg: KClass<BaseParameterizedType<*>>) , entonces el código generado es:

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

Ahora, digamos en el sitio de llamadas, lo cambio a:

 getSpecific(BaseParameterizedType::class) 

entonces esto tampoco genera la cláusula throw null . Entonces, supongo que esto tiene algo que ver con kotlin, suponiendo que este reparto siempre fallará o que hay información indeterminada disponible para hacer la inferencia.

Entonces, sabemos que arg.innerType es KClass<out BaseParameterizedType<*>> y lo usamos en un sitio que acepta KClass<in BaseParameterizedType<*>> , entonces ¿por qué no se infiere U a BaseParamterizedType<*>> ? Ese es literalmente el único tipo que alguna vez coincidirá.

Al mismo time, creo que generar una statement de throw null es increíblemente difícil de depurar. La stacktrace simplemente señalaría la línea donde hay getSpecific y buena suerte para averiguar de dónde vino la exception del puntero nulo.

Este es un problema conocido con respecto al event handling casos de inferencia de tipo de tipo cuando el tipo inferido es Nothing (y es en su caso):

enter image description here

La inferencia se comporta de esta manera debido a un bash de coerción para las proyecciones KClass<in U> y KClass<out BaseParameterizedType<*>> .

Básicamente, un tipo out project al mismo time significa in Nothing (porque el argumento de tipo real puede ser cualquiera de los subtypes, y no se puede pasar nada con security). Entonces, para hacer coincidir KClass<out BaseParameterizedType<*>> con KClass<in U> el comstackdor elige U := Nothing , lo que implica que la llamada de function tampoco devuelve Nothing .

Observación: una proyección de Foo<out Any> no puede coincidir con Foo<in T> con T := Any , porque el argumento de tipo real del valor pasado para Foo<out Any> puede ser, por ejemplo, Int . Entonces, si Foo<T> acepta T en algunas de sus funciones, permitir la coincidencia antes mencionada también le permitirá pasar las instancias a donde Foo<Int> no las espera. En realidad, in Nothing convierte en la única forma de unirlas, debido a la naturaleza desconocida del tipo out .

Después de eso, para una llamada a function de devolución de Nothing , el comstackdor inserta un código de byte throw null para asegurarse de que la ejecución no continúa ( se supone que la evaluación de una expresión de tipo Nothing nunca termina correctamente ).

Ver los problemas: KT-20849 , KT-18789

Tal como se mencionó @hotkey, out significa que in Nothing y Nothing arrojarán null. Por lo tanto, realizo algunas testings como esta:

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

Generar ->

  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)); } 

¿Considerando el tipo de null es Nothing? , podemos devolver Nothing? en lugar de Nothing ¿Entonces cambio U a U? , y luego la cláusula throw null desaparece:

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

Generar ->

  @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); } 
  • la demostración del reino de Kotlin no se puede ejecutar
  • ¿Cómo puedo usar la callback en Kotlin?
  • El argumento de tipo genérico no está dentro de los límites
  • ¿Es una 'propiedad' privada un 'campo'?
  • Funciones locales acceden a variables principales con el mismo nombre
  • Parsing xml kotlin android
  • El tipo Out-Projected 'ArrayList <*>' prohíbe el uso de 'public fun fun add (índice: Int, elemento: E): Unidad definida en java.util.ArrayList'
  • Usar el recurso en kotlin func - no funciona con el flask de grasa (un flask)
  • Constantes en Kotlin: ¿cuál es una forma recomendada de crearlos?
  • Kotlin: el operador '==' no se puede aplicar a '¡Editable!' y 'String' al comparar cadenas
  • Cómo implementar un reciclador de carga perezosa. Adaptador de vista como el reciclador de reino. Ver el adaptador usando kotlin.