Fir2Ir, JVM IR: support flexible Array types loaded from Java

We don't have true flexible types in the IR, but we approximate it with
internal type annotations, such as FlexibleNullability,
FlexibleMutability, RawType. These annotations are then handled
specially in JvmIrTypeSystemContext, which can construct a fake flexible
type so that type checker on IR types would behave exactly as on
frontend types.

As shown in KT-63441, one instance of flexible types where flexibility
was lost during conversion to IR is Java array/vararg types. It's
necessary to support it so that IR fake overrides could be constructed
correctly, because IR fake override checker requires parameter types to
be equal. So this change introduces another internal type annotation,
FlexibleArrayElementVariance, which is only applicable to types with
classifier kotlin/Array, and which signifies that the annotated type
`Array<X>` should rather be seen as `Array<X>..Array<out X>`.

 #KT-63441 Fixed
 #KT-63446 Fixed
This commit is contained in:
Alexander Udalov
2023-11-24 16:34:49 +01:00
committed by Space Team
parent e98902ce74
commit ee8d42532b
35 changed files with 571 additions and 48 deletions
@@ -0,0 +1,37 @@
// TARGET_BACKEND: JVM
// FILE: J.java
interface J {
String foo(String[] a);
}
// FILE: 1.kt
class Inv : J {
override fun foo(a: Array<String>): String = a[0]
}
class Out : J {
override fun foo(a: Array<out String>): String = a[0]
}
class Vararg : J {
override fun foo(vararg a: String): String = a[0]
}
class InvNullableElement : J {
override fun foo(a: Array<String?>): String = a[0] ?: "Fail"
}
class InvNullableArray : J {
override fun foo(a: Array<String>?): String = a?.get(0) ?: "Fail"
}
fun box(): String {
if (Inv().foo(arrayOf("OK")) != "OK") return "Fail Inv"
if (Out().foo(arrayOf("OK")) != "OK") return "Fail Out"
if (Vararg().foo("OK") != "OK") return "Fail Vararg"
if (InvNullableElement().foo(arrayOf("OK")) != "OK") return "Fail InvNullableElement"
if (InvNullableArray().foo(arrayOf("OK")) != "OK") return "Fail InvNullableArray"
return "OK"
}
@@ -0,0 +1,41 @@
// TARGET_BACKEND: JVM
// FILE: J.java
import java.util.List;
// This is a bit more elaborate version of overrideWithArrayParameterType.kt, which also checks interaction of:
// flexible nullability, flexible mutability, raw types, varargs.
interface J {
List<String>[] foo(List... a);
}
// FILE: 1.kt
class C1 : J {
override fun foo(vararg a: MutableList<Any?>?): Array<out MutableList<String>>? = null
}
class C2 : J {
override fun foo(vararg a: List<Any?>): Array<MutableList<out String>>? = null
}
class C3 : J {
override fun foo(vararg a: List<*>): Array<List<String>?>? = null
}
class C4 : J {
override fun foo(vararg a: MutableList<out Any?>): Array<out List<String?>>? = null
}
class C5 : J {
override fun foo(a: Array<List<Any?>>): Array<MutableList<String>>? = null
}
class C6 : J {
override fun foo(a: Array<out MutableList<Any?>?>): Array<out MutableList<String>>? = null
}
fun box(): String {
C1().foo()
C2().foo()
C3().foo()
C4().foo()
C5().foo(emptyArray())
C6().foo(emptyArray())
return "OK"
}
@@ -0,0 +1,34 @@
// TARGET_BACKEND: JVM
// FILE: J.java
import org.jetbrains.annotations.NotNull;
interface J {
String foo(@NotNull String[] a);
}
// FILE: 1.kt
class Inv : J {
override fun foo(a: Array<String>): String = a[0]
}
class Out : J {
override fun foo(a: Array<out String>): String = a[0]
}
class Vararg : J {
override fun foo(vararg a: String): String = a[0]
}
class InvNullableElement : J {
override fun foo(a: Array<String?>): String = a[0] ?: "Fail"
}
fun box(): String {
if (Inv().foo(arrayOf("OK")) != "OK") return "Fail Inv"
if (Out().foo(arrayOf("OK")) != "OK") return "Fail Out"
if (Vararg().foo("OK") != "OK") return "Fail Vararg"
if (InvNullableElement().foo(arrayOf("OK")) != "OK") return "Fail InvNullableElement"
return "OK"
}
@@ -0,0 +1,21 @@
// TARGET_BACKEND: JVM
// FILE: J.java
import java.util.List;
public interface J<T> {
String foo(T t, List<T[]> list);
}
// FILE: 1.kt
class A
class C : J<A> {
override fun foo(a: A, list: List<Array<A>>?): String = "OK"
}
fun box(): String {
val c: J<A> = C()
return c.foo(A(), null)
}
@@ -0,0 +1,27 @@
// TARGET_BACKEND: JVM
// FILE: J.java
interface J {
String foo(String... a);
}
// FILE: 1.kt
class Inv : J {
override fun foo(a: Array<String>): String = a[0]
}
class Out : J {
override fun foo(a: Array<out String>): String = a[0]
}
class Vararg : J {
override fun foo(vararg a: String): String = a[0]
}
fun box(): String {
if (Inv().foo(arrayOf("OK")) != "OK") return "Fail Inv"
if (Out().foo(arrayOf("OK")) != "OK") return "Fail Out"
if (Vararg().foo("OK") != "OK") return "Fail Vararg"
return "OK"
}