Kotlin难以上传到推断的(现场)参数

我不确定是否“失败”是正确的话,但这是我面临的问题。 我花了相当长的时间来重现这个最小的例子,所以在这里:

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

好的,所以上面的代码在'TODO'失败,但是如果它不在那里,并且函数正常返回,那么它肯定会失败,并产生空指针异常。 我试图弄清楚什么是错误的,所以我转向了反编译的Java代码(来自kotlin字节码):

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

如果我将getSpecific(clz: KClass<in U>) : U的函数签名更改为以下任何一种形式:

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

甚至是函数( example(arg: KClass<out BaseParameterizedType<*>)example(arg: KClass<BaseParameterizedType<*>>) ,则生成的代码是:

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

现在,让我们说在呼叫站点,我改变它:

 getSpecific(BaseParameterizedType::class) 

那么这也不会生成throw null子句。 所以,我猜这与kotlin有关,假设这个演员总是会失败,或者有不确定的信息可以进行推理。

所以,我们知道arg.innerTypeKClass<out BaseParameterizedType<*>> ,我们在一个接受KClass<in BaseParameterizedType<*>>的站点上使用它,所以为什么不推断为BaseParamterizedType<*>> 。 这实际上是唯一可以匹配的类型。

同时,我认为只是生成一个throw null语句难以调试是难以置信的。 stacktrace会指向getSpecific行,并且找出空指针异常来自哪里。

这是一个已知的问题关于类型推理的角落案件处理时,推断类型是Nothing (这是你的情况):

在这里输入图像描述

因为对KClass<in U>KClass<out BaseParameterizedType<*>>的强制尝试,推理的行为就是这样。

基本上, out -projected类型在同一时间意味着in Nothing (因为实际的类型参数可以是任何的子类型,没有什么可以安全地传入)。 因此, KClass<out BaseParameterizedType<*>>KClass<in U>匹配,编译器将选择U := Nothing ,这意味着函数调用也会返回Nothing

备注: Foo<out Any>投影不能与Foo<in T>中的Foo<in T> Foo<out Any>匹配, T := Any ,因为为Foo<out Any>传递的值的实际类型实Foo<out Any>可以是,例如Int 然后,如果Foo<T>在其某些函数中接受T ,允许前面提到的匹配也允许您将Any实例传递到Foo<Int>不指望它们的地方。 实际上, in Nothing成为匹配它们的唯一方法的情况下,由于out投射类型的未知性质。

之后,对于Nothing返回函数调用,编译器会插入那些会throw null字节码的错误,以确保执行不会继续(评估Nothing型表达式应该永远不会正确完成 )。

看到的问题: KT-20849 , KT-18789

就像@hotkey提到的那样, in NothingNothing意味着Nothing都不会抛出null。所以我做了一些这样的测试:

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

生成 – >

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

考虑到null的类型是Nothing? ,我们可以返回Nothing? 而不是Nothing 。 所以我把U换成U? ,然后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) } 

生成 – >

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