Reflection: add KTypeProjection and KType.arguments
#KT-8998 In Progress
This commit is contained in:
+34
@@ -0,0 +1,34 @@
|
|||||||
|
// WITH_REFLECT
|
||||||
|
// FULL_JDK
|
||||||
|
|
||||||
|
import java.lang.reflect.ParameterizedType
|
||||||
|
import kotlin.reflect.jvm.javaType
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class Bar
|
||||||
|
fun arrayOfInvBar(): Array<Bar> = null!!
|
||||||
|
fun arrayOfInBar(): Array<in Bar> = null!!
|
||||||
|
fun arrayOfOutBar(): Array<out Bar> = null!!
|
||||||
|
|
||||||
|
fun arrayOfInvList(): Array<List<String>> = null!!
|
||||||
|
fun arrayOfInList(): Array<in List<String>> = null!!
|
||||||
|
fun arrayOfOutList(): Array<out List<String>> = null!!
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
// NB: in "Array<in X>", Java type of X is always Any::class.java because this is the JVM signature generated by the compiler
|
||||||
|
|
||||||
|
assertEquals(Bar::class.java, ::arrayOfInvBar.returnType.arguments.single().type!!.javaType)
|
||||||
|
assertEquals(Any::class.java, ::arrayOfInBar.returnType.arguments.single().type!!.javaType)
|
||||||
|
assertEquals(Bar::class.java, ::arrayOfOutBar.returnType.arguments.single().type!!.javaType)
|
||||||
|
|
||||||
|
val invList = ::arrayOfInvList.returnType.arguments.single().type!!.javaType
|
||||||
|
assertTrue(invList is ParameterizedType && invList.rawType == List::class.java, invList.toString())
|
||||||
|
|
||||||
|
assertEquals(Any::class.java, ::arrayOfInList.returnType.arguments.single().type!!.javaType)
|
||||||
|
|
||||||
|
val outList = ::arrayOfOutList.returnType.arguments.single().type!!.javaType
|
||||||
|
assertTrue(outList is ParameterizedType && outList.rawType == List::class.java, outList.toString())
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
// WITH_REFLECT
|
||||||
|
|
||||||
|
import kotlin.reflect.jvm.javaType
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
fun listOfStrings(): List<String> = null!!
|
||||||
|
|
||||||
|
class Foo<T>
|
||||||
|
class Bar
|
||||||
|
fun fooOfInvBar(): Foo<Bar> = null!!
|
||||||
|
fun fooOfInBar(): Foo<in Bar> = null!!
|
||||||
|
fun fooOfOutBar(): Foo<out Bar> = null!!
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
assertEquals(String::class.java, ::listOfStrings.returnType.arguments.single().type!!.javaType)
|
||||||
|
|
||||||
|
assertEquals(Bar::class.java, ::fooOfInvBar.returnType.arguments.single().type!!.javaType)
|
||||||
|
assertEquals(Bar::class.java, ::fooOfInBar.returnType.arguments.single().type!!.javaType)
|
||||||
|
assertEquals(Bar::class.java, ::fooOfOutBar.returnType.arguments.single().type!!.javaType)
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// WITH_REFLECT
|
||||||
|
// FILE: J.java
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface J {
|
||||||
|
List foo();
|
||||||
|
}
|
||||||
|
|
||||||
|
// FILE: K.kt
|
||||||
|
|
||||||
|
import kotlin.reflect.jvm.javaType
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
assertEquals(Any::class.java, J::foo.returnType.arguments.single().type!!.javaType)
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// WITH_REFLECT
|
||||||
|
|
||||||
|
import kotlin.reflect.KTypeProjection
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
fun string(): String = null!!
|
||||||
|
|
||||||
|
class Fourple<A, B, C, D>
|
||||||
|
fun projections(): Fourple<String, in String, out String, *> = null!!
|
||||||
|
|
||||||
|
fun array(): Array<out Number> = null!!
|
||||||
|
|
||||||
|
fun list(): List<String> = null!!
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
val string = ::string.returnType
|
||||||
|
assertEquals(listOf(), string.arguments)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
listOf(
|
||||||
|
KTypeProjection.Invariant(string),
|
||||||
|
KTypeProjection.In(string),
|
||||||
|
KTypeProjection.Out(string),
|
||||||
|
KTypeProjection.Star
|
||||||
|
),
|
||||||
|
::projections.returnType.arguments
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
listOf(string, string, string, null),
|
||||||
|
::projections.returnType.arguments.map(KTypeProjection::type)
|
||||||
|
)
|
||||||
|
|
||||||
|
val outNumber = ::array.returnType.arguments.single()
|
||||||
|
assertTrue(outNumber is KTypeProjection.Out)
|
||||||
|
assertEquals(Number::class, outNumber.type?.classifier)
|
||||||
|
|
||||||
|
// There should be no use-site projection, despite the fact that the corresponding parameter has 'out' variance
|
||||||
|
assertTrue(::list.returnType.arguments.single() is KTypeProjection.Invariant)
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
@@ -12431,6 +12431,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("genericArrayElementType.kt")
|
||||||
|
public void testGenericArrayElementType() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/mapping/types/genericArrayElementType.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("memberFunctions.kt")
|
@TestMetadata("memberFunctions.kt")
|
||||||
public void testMemberFunctions() throws Exception {
|
public void testMemberFunctions() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/mapping/types/memberFunctions.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/mapping/types/memberFunctions.kt");
|
||||||
@@ -12443,6 +12449,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("parameterizedTypeArgument.kt")
|
||||||
|
public void testParameterizedTypeArgument() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/mapping/types/parameterizedTypeArgument.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("parameterizedTypes.kt")
|
@TestMetadata("parameterizedTypes.kt")
|
||||||
public void testParameterizedTypes() throws Exception {
|
public void testParameterizedTypes() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/mapping/types/parameterizedTypes.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/mapping/types/parameterizedTypes.kt");
|
||||||
@@ -12455,6 +12467,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("rawTypeArgument.kt")
|
||||||
|
public void testRawTypeArgument() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/mapping/types/rawTypeArgument.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("topLevelFunctions.kt")
|
@TestMetadata("topLevelFunctions.kt")
|
||||||
public void testTopLevelFunctions() throws Exception {
|
public void testTopLevelFunctions() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/mapping/types/topLevelFunctions.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/mapping/types/topLevelFunctions.kt");
|
||||||
@@ -12968,6 +12986,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
|||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/platformTypeToString.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/platformTypeToString.kt");
|
||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("typeArguments.kt")
|
||||||
|
public void testTypeArguments() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/typeArguments.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ public interface KType {
|
|||||||
*/
|
*/
|
||||||
public val classifier: KClassifier?
|
public val classifier: KClassifier?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type arguments passed for the parameters of the classifier in this type.
|
||||||
|
* For example, in the type `Array<out Number>` the only type argument is `out Number`.
|
||||||
|
*/
|
||||||
|
public val arguments: List<KTypeProjection>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `true` if this type was marked nullable in the source code.
|
* `true` if this type was marked nullable in the source code.
|
||||||
*
|
*
|
||||||
@@ -42,9 +48,54 @@ public interface KType {
|
|||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* fun <T> foo(t: T) {
|
* fun <T> foo(t: T) {
|
||||||
* // isMarkedNullable == false for t's type, but t can be null here
|
* // isMarkedNullable == false for t's type, but t can be null here when T = "Any?"
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
public val isMarkedNullable: Boolean
|
public val isMarkedNullable: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a type projection. Type projection is usually the argument to another type in a type usage.
|
||||||
|
* For example, in the type `Array<out Number>`, `out Number` is the covariant projection of the type represented by the class `Number`.
|
||||||
|
*
|
||||||
|
* Type projection is either the star projection, or an entity consisting of a specific type plus optional variance.
|
||||||
|
*
|
||||||
|
* See the [Kotlin language documentation](http://kotlinlang.org/docs/reference/generics.html#type-projections)
|
||||||
|
* for more information.
|
||||||
|
*/
|
||||||
|
public sealed class KTypeProjection {
|
||||||
|
/**
|
||||||
|
* The type specified in the projection, or `null` if this is a star projection.
|
||||||
|
*/
|
||||||
|
public abstract val type: KType?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invariant projection of a type. Invariant projection is just the type itself, without any use-site variance modifiers applied to it.
|
||||||
|
* For example, in the type `Set<String>`, `String` is an invariant projection of the type represented by the class `String`.
|
||||||
|
*/
|
||||||
|
public data class Invariant(override val type: KType) : KTypeProjection()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contravariant projection of a type, denoted by the `in` modifier applied to a type.
|
||||||
|
* For example, in the type `MutableList<in Number>`, `in Number` is a contravariant projection of the type of class `Number`.
|
||||||
|
*/
|
||||||
|
public data class In(override val type: KType) : KTypeProjection()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Covariant projection of a type, denoted by the `out` modifier applied to a type.
|
||||||
|
* For example, in the type `Array<out Number>`, `out Number` is a covariant projection of the type of class `Number`.
|
||||||
|
*/
|
||||||
|
public data class Out(override val type: KType) : KTypeProjection()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Star projection, denoted by the `*` character.
|
||||||
|
* For example, in the type `KClass<*>`, `*` is the star projection.
|
||||||
|
* See the [Kotlin language documentation](http://kotlinlang.org/docs/reference/generics.html#star-projections)
|
||||||
|
* for more information.
|
||||||
|
*/
|
||||||
|
public object Star : KTypeProjection() {
|
||||||
|
override val type: KType?
|
||||||
|
get() = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,11 +23,13 @@ import org.jetbrains.kotlin.load.java.structure.reflect.createArrayType
|
|||||||
import org.jetbrains.kotlin.load.java.structure.reflect.primitiveByWrapper
|
import org.jetbrains.kotlin.load.java.structure.reflect.primitiveByWrapper
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
import org.jetbrains.kotlin.types.TypeUtils
|
import org.jetbrains.kotlin.types.TypeUtils
|
||||||
|
import org.jetbrains.kotlin.types.Variance
|
||||||
|
import java.lang.reflect.GenericArrayType
|
||||||
|
import java.lang.reflect.ParameterizedType
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
import kotlin.reflect.KClass
|
import java.lang.reflect.WildcardType
|
||||||
import kotlin.reflect.KClassifier
|
import kotlin.LazyThreadSafetyMode.PUBLICATION
|
||||||
import kotlin.reflect.KType
|
import kotlin.reflect.*
|
||||||
import kotlin.reflect.KTypeParameter
|
|
||||||
|
|
||||||
internal class KTypeImpl(
|
internal class KTypeImpl(
|
||||||
val type: KotlinType,
|
val type: KotlinType,
|
||||||
@@ -73,6 +75,53 @@ internal class KTypeImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val arguments: List<KTypeProjection>
|
||||||
|
get() {
|
||||||
|
val typeArguments = type.arguments
|
||||||
|
if (typeArguments.isEmpty()) return emptyList()
|
||||||
|
|
||||||
|
// Lazy because it's not needed to compute javaType right away, only inside the lazy value for each argument,
|
||||||
|
// and also because sometimes (e.g. in case of star projections), this won't be needed at all.
|
||||||
|
// Note that this instance is created before the loop because ParameterizedType#actualTypeArguments clones the array
|
||||||
|
val javaTypeArguments by lazy(PUBLICATION) {
|
||||||
|
(javaType as ParameterizedType).actualTypeArguments
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeArguments.mapIndexed { i, typeProjection ->
|
||||||
|
if (typeProjection.isStarProjection) {
|
||||||
|
KTypeProjection.Star
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val type = KTypeImpl(typeProjection.type) {
|
||||||
|
val javaType = javaType
|
||||||
|
when (javaType) {
|
||||||
|
is Class<*> -> {
|
||||||
|
// It's either an array or a raw type.
|
||||||
|
// TODO: return upper bound of the corresponding parameter for a raw type?
|
||||||
|
if (javaType.isArray) javaType.componentType else Any::class.java
|
||||||
|
}
|
||||||
|
is GenericArrayType -> {
|
||||||
|
if (i != 0) throw KotlinReflectionInternalError("Array type has been queried for a non-0th argument: $this")
|
||||||
|
javaType.genericComponentType
|
||||||
|
}
|
||||||
|
is ParameterizedType -> {
|
||||||
|
val argument = javaTypeArguments[i]
|
||||||
|
// In "Foo<out Bar>", the JVM type of the first type argument should be "Bar", not "? extends Bar"
|
||||||
|
if (argument !is WildcardType) argument
|
||||||
|
else argument.lowerBounds.firstOrNull() ?: argument.upperBounds.first()
|
||||||
|
}
|
||||||
|
else -> throw KotlinReflectionInternalError("Non-generic type has been queried for arguments: $this")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
when (typeProjection.projectionKind) {
|
||||||
|
Variance.INVARIANT -> KTypeProjection.Invariant(type)
|
||||||
|
Variance.IN_VARIANCE -> KTypeProjection.In(type)
|
||||||
|
Variance.OUT_VARIANCE -> KTypeProjection.Out(type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override val isMarkedNullable: Boolean
|
override val isMarkedNullable: Boolean
|
||||||
get() = type.isMarkedNullable
|
get() = type.isMarkedNullable
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user