Kotlin-通用类型参数不受尊重

ser*_*0ne 7 generics type-inference typechecking type-parameter kotlin

考虑以下示例:

import kotlin.reflect.KProperty1

infix fun <T, R> KProperty1<T, R>.test(value: R) = Unit

data class Foo(val bar: Int)

fun main() {
    Foo::bar test "Hello"
}
Run Code Online (Sandbox Code Playgroud)

给定test期望a value的类型R,为什么在这种情况下,在属性类型为的地方Int,它是否允许我传递a String

hot*_*key 8

首先,看一下接口的声明KProperty1,它是:

interface KProperty1<T, out R> : KProperty<R>, (T) -> R
Run Code Online (Sandbox Code Playgroud)

这里最重要的部分是out-projected 型参数R,它定义subptyping的之间的关系KProperty1类型的具有用于不同类型的参数R

(1)即,对于任何FooA并且B使得A : BA是的子类型B
KProperty1<Foo, A> : KProperty1<Foo, B>。这称为协方差,因为参数化类型彼此之间的关联方式与其类型参数一样。


(2)接下来,请注意,对于anyABthatA : BA可以将的实例作为参数传递给任何B-typed参数。在这方面,扩展功能的接收器参数与普通参数没有区别。


现在,最关键的部分是编译器运行的类型推断算法。类型推断的目标之一是为每个泛型调用建立静态已知的类型实参,其中忽略类型实参。

在类型推断呼叫Foo::bar test "Hello",编译器需要实际推断类型参数T,并R根据已知类型的接收器的Foo::barKProperty1<Foo, Int>)和value参数"Hello"String)。

这是通过解决约束系统在内部完成的。我们可以如下模拟此逻辑:

  • 鉴于KProperty<Foo, Int>传递为KProperty<T, R>

    • 我们必须使用T := FooT不变)
    • 我们必须使用Int或其任何超类型作为类型实参R
      • 这是因为协方差为R:给定的(1)(2)相结合,选择Int或它的一些超类型用于R有必要能够通过KProperty<Foo, Int>,其中KProperty<Foo, R>,预计
      • 这些超类型的实例是Int?NumberNumber?AnyAny?
  • 给定a String作为传递R

    • 我们必须使用String其某些超型作为R
      • 为了能够通过(2)预期的String位置,这是必需的R
      • 这些超类型的实例是String?CharSequenceCharSequence?AnyAny?

给定的两个约束R,即它应该是Int或其某些超类型,并且应该是String或其某些超类型,编译器会找到同时满足这两个条件的最不常见类型。此类型为Any

因此,推断的类型实参为T := FooR := Any,使用显式类型实参的调用为:

Foo::bar.test<Foo, Any>("Hello")
Run Code Online (Sandbox Code Playgroud)

在IntelliJ IDEA中,可以使用对非infix调用添加显式类型参数的操作来添加推断的类型。


免责声明:这并不是编译器内部的确切工作方式,但是使用这种推理方式,您可能经常会得到与编译器结果一致的结果。


也相关: