diff --git a/compiler/testData/codegen/box/reflection/mapping/types/genericArrayElementType.kt b/compiler/testData/codegen/box/reflection/mapping/types/genericArrayElementType.kt new file mode 100644 index 00000000000..6d12cb81cb4 --- /dev/null +++ b/compiler/testData/codegen/box/reflection/mapping/types/genericArrayElementType.kt @@ -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 = null!! +fun arrayOfInBar(): Array = null!! +fun arrayOfOutBar(): Array = null!! + +fun arrayOfInvList(): Array> = null!! +fun arrayOfInList(): Array> = null!! +fun arrayOfOutList(): Array> = null!! + +fun box(): String { + // NB: in "Array", 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" +} diff --git a/compiler/testData/codegen/box/reflection/mapping/types/parameterizedTypeArgument.kt b/compiler/testData/codegen/box/reflection/mapping/types/parameterizedTypeArgument.kt new file mode 100644 index 00000000000..e20ecb7c20d --- /dev/null +++ b/compiler/testData/codegen/box/reflection/mapping/types/parameterizedTypeArgument.kt @@ -0,0 +1,22 @@ +// WITH_REFLECT + +import kotlin.reflect.jvm.javaType +import kotlin.test.assertEquals + +fun listOfStrings(): List = null!! + +class Foo +class Bar +fun fooOfInvBar(): Foo = null!! +fun fooOfInBar(): Foo = null!! +fun fooOfOutBar(): Foo = 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" +} diff --git a/compiler/testData/codegen/box/reflection/mapping/types/rawTypeArgument.kt b/compiler/testData/codegen/box/reflection/mapping/types/rawTypeArgument.kt new file mode 100644 index 00000000000..5ed75841683 --- /dev/null +++ b/compiler/testData/codegen/box/reflection/mapping/types/rawTypeArgument.kt @@ -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" +} diff --git a/compiler/testData/codegen/box/reflection/types/typeArguments.kt b/compiler/testData/codegen/box/reflection/types/typeArguments.kt new file mode 100644 index 00000000000..53f64122dcf --- /dev/null +++ b/compiler/testData/codegen/box/reflection/types/typeArguments.kt @@ -0,0 +1,42 @@ +// WITH_REFLECT + +import kotlin.reflect.KTypeProjection +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +fun string(): String = null!! + +class Fourple +fun projections(): Fourple = null!! + +fun array(): Array = null!! + +fun list(): List = 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" +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index fbbf759c79a..e73a37bdeb2 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -12431,6 +12431,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { 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") public void testMemberFunctions() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/mapping/types/memberFunctions.kt"); @@ -12443,6 +12449,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { 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") public void testParameterizedTypes() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/mapping/types/parameterizedTypes.kt"); @@ -12455,6 +12467,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { 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") public void testTopLevelFunctions() throws Exception { 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"); doTest(fileName); } + + @TestMetadata("typeArguments.kt") + public void testTypeArguments() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/typeArguments.kt"); + doTest(fileName); + } } } diff --git a/core/builtins/src/kotlin/reflect/KType.kt b/core/builtins/src/kotlin/reflect/KType.kt index 54053464af4..cffb719056a 100644 --- a/core/builtins/src/kotlin/reflect/KType.kt +++ b/core/builtins/src/kotlin/reflect/KType.kt @@ -29,6 +29,12 @@ public interface KType { */ public val classifier: KClassifier? + /** + * Type arguments passed for the parameters of the classifier in this type. + * For example, in the type `Array` the only type argument is `out Number`. + */ + public val arguments: List + /** * `true` if this type was marked nullable in the source code. * @@ -42,9 +48,54 @@ public interface KType { * * ``` * fun 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 } + +/** + * 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` 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` 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` 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` 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 + } +} diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KTypeImpl.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KTypeImpl.kt index cdd3ef30f70..059c4d0ed28 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KTypeImpl.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KTypeImpl.kt @@ -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.types.KotlinType 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 kotlin.reflect.KClass -import kotlin.reflect.KClassifier -import kotlin.reflect.KType -import kotlin.reflect.KTypeParameter +import java.lang.reflect.WildcardType +import kotlin.LazyThreadSafetyMode.PUBLICATION +import kotlin.reflect.* internal class KTypeImpl( val type: KotlinType, @@ -73,6 +75,53 @@ internal class KTypeImpl( } } + override val arguments: List + 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", 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 get() = type.isMarkedNullable