Reflection: add KTypeProjection and KType.arguments

#KT-8998 In Progress
This commit is contained in:
Alexander Udalov
2016-07-12 16:20:44 +03:00
parent c1dd831e65
commit f69cc01f8e
7 changed files with 246 additions and 5 deletions
@@ -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"
}
@@ -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);
}
} }
} }
+52 -1
View File
@@ -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