kotlin 为什么特定的返回lambda类型会导致歧义

ttp71kqs  于 2023-04-21  发布在  Kotlin
关注(0)|答案(1)|浏览(145)

在下面的例子中,我不明白这个函数有什么不明确的地方。

fun <P1, R> test(init : () -> (P1) -> R) {

}

fun <P1, P2, R> test(init : () -> (P1, P2) -> R) {

}

fun main() {
    test {
        fun (p1 : Long) {

        }
    }
}

由于某种原因,它不能确定调用哪个测试,即使返回的匿名函数明确地接受一个参数。

wfsdck30

wfsdck301#

这是因为JVM。¯_()_/¯
让我们先看看这个例子:

fun <P1, R> test(init: () -> (P1) -> R) {
}

fun <P1, P2, R> test(init: () -> (P1, P2) -> R) {
}

fun main() {
    val test1 = test<String, Unit> {
        {
            println("test: $it")
        }
    }

    val test2 = test<String, String, Unit> {
        { p1: String, p2: String ->
            println("test: $p1-$p2")
        }
    }
    
}

代码会编译吗?令人惊讶的是,即使声明了类型,编译也会失败,并显示消息:

Kotlin: Platform declaration clash: The following declarations have the same JVM signature (test(Lkotlin/jvm/functions/Function0;)V):
    fun <P1, R> test(init: () -> (P1) -> R): Unit defined in com.example.springsandbox
    fun <P1, P2, R> test(init: () -> (P1, P2) -> R): Unit defined in com.example.springsandbox

这条消息告诉我们很多可能存在问题的地方。我们可以通过更改JVM签名轻松修复它:

@JvmName("test1")
fun <P1, R> test(init: () -> (P1) -> R) {
}

@JvmName("test2")
fun <P1, P2, R> test(init: () -> (P1, P2) -> R) {

}

现在,我们的代码可以编译了,但是我们仍然需要在调用test函数时添加类型。所以让我们检查一下下面发生了什么。

public final class com/example/springsandbox/NotepadKt {

  // access flags 0x19
  // signature <P1:Ljava/lang/Object;R:Ljava/lang/Object;>(Lkotlin/jvm/functions/Function0<+Lkotlin/jvm/functions/Function1<-TP1;+TR;>;>;)V
  // declaration: void test1<P1, R>(kotlin.jvm.functions.Function0<? extends kotlin.jvm.functions.Function1<? super P1, ? extends R>>)
  public final static test1(Lkotlin/jvm/functions/Function0;)V
  @Lkotlin/jvm/JvmName;(name="test1") // invisible
    // annotable parameter count: 1 (visible)
    // annotable parameter count: 1 (invisible)
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
   L0
    ALOAD 0
    LDC "init"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 6 L2
    RETURN
   L2
    LOCALVARIABLE init Lkotlin/jvm/functions/Function0; L0 L3 0
    MAXSTACK = 2
    MAXLOCALS = 1

  // access flags 0x19
  // signature <P1:Ljava/lang/Object;P2:Ljava/lang/Object;R:Ljava/lang/Object;>(Lkotlin/jvm/functions/Function0<+Lkotlin/jvm/functions/Function2<-TP1;-TP2;+TR;>;>;)V
  // declaration: void test2<P1, P2, R>(kotlin.jvm.functions.Function0<? extends kotlin.jvm.functions.Function2<? super P1, ? super P2, ? extends R>>)
  public final static test2(Lkotlin/jvm/functions/Function0;)V
  @Lkotlin/jvm/JvmName;(name="test2") // invisible
    // annotable parameter count: 1 (visible)
    // annotable parameter count: 1 (invisible)
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
   L0
    ALOAD 0
    LDC "init"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 11 L1
    RETURN
   L2
    LOCALVARIABLE init Lkotlin/jvm/functions/Function0; L0 L2 0
    MAXSTACK = 2
    MAXLOCALS = 1

  // compiled from: notepad.kt
}

字节码中的Test-fun访问器如下所示:

L0
    LINENUMBER 14 L0
    GETSTATIC com/example/springsandbox/NotepadKt$main$test1$1.INSTANCE : Lcom/example/springsandbox/NotepadKt$main$test1$1;
    CHECKCAST kotlin/jvm/functions/Function0
    INVOKESTATIC com/example/springsandbox/NotepadKt.test1 (Lkotlin/jvm/functions/Function0;)V
    GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit;
    ASTORE 0
   L1
    LINENUMBER 20 L1
    GETSTATIC com/example/springsandbox/NotepadKt$main$test2$1.INSTANCE : Lcom/example/springsandbox/NotepadKt$main$test2$1;
    CHECKCAST kotlin/jvm/functions/Function0
    INVOKESTATIC com/example/springsandbox/NotepadKt.test2 (Lkotlin/jvm/functions/Function0;)V
    GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit;
    ASTORE 1

两个测试函数具有(几乎)相同的签名:Function0(唯一的区别是函数名,已经被@JvmName annotation改变了)。泛型类型不在这里,因为JVM已经删除了它;)
我们也可以反编译工作代码来查看冲突:

public final class NotepadKt {
   @JvmName(
      name = "test1"
   )
   public static final void test1(@NotNull Function0 init) {
      Intrinsics.checkNotNullParameter(init, "init");
   }

   @JvmName(
      name = "test2"
   )
   public static final void test2(@NotNull Function0 init) {
      Intrinsics.checkNotNullParameter(init, "init");
   }

   public static final void main() {
      test1((Function0)null.INSTANCE);
      Unit test1 = Unit.INSTANCE;
      test2((Function0)null.INSTANCE);
      Unit test2 = Unit.INSTANCE;
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}

这清楚地告诉我们哪里有问题-两个声明的函数都采用相同的参数,所以我们需要帮助Kotlin编译器选择哪一个。
参考:https://docs.oracle.com/javase/tutorial/java/generics/erasure.html
所以,我不确定这是否可以被认为是一个bug,或者更像是JVM的限制(或者,事实上,如果我们考虑类型继承(我指的是T: () -> Any, S: () -> () -> Any; S is a subtype of T),答案可能不是那么明显)。

相关问题