From 170ea4dead175af111fd6826b6dc7f40d7391dbc Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Fri, 22 Jul 2016 15:34:54 +0300 Subject: [PATCH] Reflection: add KVariance, KTypeParameter.{variance,upperBounds,equals,hashCode} --- .../typeParametersEqualsHashCode.kt | 39 ++++++++++++ .../typeParameters/declarationSiteVariance.kt | 23 ++++++++ .../reflection/typeParameters/upperBounds.kt | 59 +++++++++++++++++++ .../box/reflection/types/useSiteVariance.kt | 26 ++++++++ .../codegen/BlackBoxCodegenTestGenerated.java | 24 ++++++++ core/builtins/src/kotlin/reflect/KType.kt | 23 +++++++- .../src/kotlin/reflect/KTypeParameter.kt | 14 +++++ core/builtins/src/kotlin/reflect/KVariance.kt | 44 ++++++++++++++ .../jvm/internal/KTypeParameterImpl.kt | 23 ++++++++ 9 files changed, 272 insertions(+), 3 deletions(-) create mode 100644 compiler/testData/codegen/box/reflection/methodsFromAny/typeParametersEqualsHashCode.kt create mode 100644 compiler/testData/codegen/box/reflection/typeParameters/declarationSiteVariance.kt create mode 100644 compiler/testData/codegen/box/reflection/typeParameters/upperBounds.kt create mode 100644 compiler/testData/codegen/box/reflection/types/useSiteVariance.kt create mode 100644 core/builtins/src/kotlin/reflect/KVariance.kt diff --git a/compiler/testData/codegen/box/reflection/methodsFromAny/typeParametersEqualsHashCode.kt b/compiler/testData/codegen/box/reflection/methodsFromAny/typeParametersEqualsHashCode.kt new file mode 100644 index 00000000000..8e5b3efda5b --- /dev/null +++ b/compiler/testData/codegen/box/reflection/methodsFromAny/typeParametersEqualsHashCode.kt @@ -0,0 +1,39 @@ +// WITH_REFLECT + +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals + +class A +class B + +class Fun { + fun foo(): T = null!! +} + +class Fourple + +fun box(): String { + assertEquals(A::class.typeParameters, A::class.typeParameters) + assertEquals(A::class.typeParameters.single().hashCode(), A::class.typeParameters.single().hashCode()) + + fun getFoo() = Fun::class.members.single { it.name == "foo" } + assertEquals(getFoo().typeParameters, getFoo().typeParameters) + assertEquals(getFoo().typeParameters.single().hashCode(), getFoo().typeParameters.single().hashCode()) + + assertNotEquals(A::class.typeParameters.single(), B::class.typeParameters.single()) + + val fi = Fourple::class.typeParameters + val fj = Fourple::class.typeParameters + for (i in 0..fi.size - 1) { + for (j in 0..fj.size - 1) { + if (i == j) { + assertEquals(fi[i], fj[j]) + assertEquals(fi[i].hashCode(), fj[j].hashCode()) + } else { + assertNotEquals(fi[i], fj[j]) + } + } + } + + return "OK" +} diff --git a/compiler/testData/codegen/box/reflection/typeParameters/declarationSiteVariance.kt b/compiler/testData/codegen/box/reflection/typeParameters/declarationSiteVariance.kt new file mode 100644 index 00000000000..f04164b33bf --- /dev/null +++ b/compiler/testData/codegen/box/reflection/typeParameters/declarationSiteVariance.kt @@ -0,0 +1,23 @@ +// WITH_REFLECT + +import kotlin.reflect.KVariance +import kotlin.test.assertEquals + +class Triple { + fun foo(): T = null!! +} + +fun box(): String { + assertEquals( + listOf( + KVariance.IN, + KVariance.INVARIANT, + KVariance.OUT + ), + Triple::class.typeParameters.map { it.variance } + ) + + assertEquals(KVariance.INVARIANT, Triple::class.members.single { it.name == "foo" }.typeParameters.single().variance) + + return "OK" +} diff --git a/compiler/testData/codegen/box/reflection/typeParameters/upperBounds.kt b/compiler/testData/codegen/box/reflection/typeParameters/upperBounds.kt new file mode 100644 index 00000000000..2a636a3702d --- /dev/null +++ b/compiler/testData/codegen/box/reflection/typeParameters/upperBounds.kt @@ -0,0 +1,59 @@ +// WITH_REFLECT + +import kotlin.reflect.KTypeProjection +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class DefaultBound +class NullableAnyBound +class NotNullAnyBound +class TwoBounds where T : Comparable + +class OtherParameterBound + +class RecursiveGeneric> + +class FunctionTypeParameter { + fun foo(): Cloneable = null!! +} + +// helper functions to obtain KType instances +fun nullableAny(): Any? = null +fun notNullAny(): Any = null!! + +fun box(): String { + assertEquals(listOf(::nullableAny.returnType), DefaultBound::class.typeParameters.single().upperBounds) + assertEquals(listOf(::nullableAny.returnType), NullableAnyBound::class.typeParameters.single().upperBounds) + assertEquals(listOf(::notNullAny.returnType), NotNullAnyBound::class.typeParameters.single().upperBounds) + + TwoBounds::class.typeParameters.single().let { + val (cl, cm) = it.upperBounds + assertEquals(Cloneable::class, cl.classifier) + assertEquals(listOf(), cl.arguments) + + assertEquals(Comparable::class, cm.classifier) + val cmt = cm.arguments.single() + assertTrue(cmt is KTypeProjection.Invariant) + assertEquals(it, cmt.type!!.classifier) + } + + OtherParameterBound::class.typeParameters.let { + val (t, u) = it + assertEquals(u, t.upperBounds.single().classifier) + assertEquals(Number::class, u.upperBounds.single().classifier) + } + + FunctionTypeParameter::class.members.single { it.name == "foo" }.let { foo -> + assertEquals(foo.returnType, foo.typeParameters.single().upperBounds.single()) + } + + val recursiveGenericTypeParameter = RecursiveGeneric::class.typeParameters.single() + val recursiveGenericBound = recursiveGenericTypeParameter.upperBounds.single() + assertEquals(Enum::class, recursiveGenericBound.classifier) + recursiveGenericBound.arguments.single().let { projection -> + assertTrue(projection is KTypeProjection.Invariant) + assertEquals(recursiveGenericTypeParameter, projection.type!!.classifier) + } + + return "OK" +} diff --git a/compiler/testData/codegen/box/reflection/types/useSiteVariance.kt b/compiler/testData/codegen/box/reflection/types/useSiteVariance.kt new file mode 100644 index 00000000000..5f4a100584f --- /dev/null +++ b/compiler/testData/codegen/box/reflection/types/useSiteVariance.kt @@ -0,0 +1,26 @@ +// WITH_REFLECT + +import kotlin.reflect.KVariance +import kotlin.test.assertEquals + +class Fourple +fun foo(): Fourple = null!! + +fun listOfStrings(): List = null!! + +fun box(): String { + assertEquals( + listOf( + KVariance.INVARIANT, + KVariance.IN, + KVariance.OUT, + null + ), + ::foo.returnType.arguments.map { it.variance } + ) + + // Declaration-site variance should have no effect on the variance of the type projection: + assertEquals(KVariance.INVARIANT, ::listOfStrings.returnType.arguments.first().variance) + + return "OK" +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 61fc443ba7e..2931ad28c8f 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -12567,6 +12567,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("typeParametersEqualsHashCode.kt") + public void testTypeParametersEqualsHashCode() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/methodsFromAny/typeParametersEqualsHashCode.kt"); + doTest(fileName); + } + @TestMetadata("typeToString.kt") public void testTypeToString() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/methodsFromAny/typeToString.kt"); @@ -12951,11 +12957,23 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/reflection/typeParameters"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("declarationSiteVariance.kt") + public void testDeclarationSiteVariance() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/typeParameters/declarationSiteVariance.kt"); + doTest(fileName); + } + @TestMetadata("typeParametersAndNames.kt") public void testTypeParametersAndNames() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/typeParameters/typeParametersAndNames.kt"); doTest(fileName); } + + @TestMetadata("upperBounds.kt") + public void testUpperBounds() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/typeParameters/upperBounds.kt"); + doTest(fileName); + } } @TestMetadata("compiler/testData/codegen/box/reflection/types") @@ -13007,6 +13025,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/typeArguments.kt"); doTest(fileName); } + + @TestMetadata("useSiteVariance.kt") + public void testUseSiteVariance() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/useSiteVariance.kt"); + doTest(fileName); + } } } diff --git a/core/builtins/src/kotlin/reflect/KType.kt b/core/builtins/src/kotlin/reflect/KType.kt index cffb719056a..98cb9c07f80 100644 --- a/core/builtins/src/kotlin/reflect/KType.kt +++ b/core/builtins/src/kotlin/reflect/KType.kt @@ -70,23 +70,37 @@ public sealed class KTypeProjection { */ public abstract val type: KType? + /** + * The use-site variance specified in the projection, or `null` if this is a star projection. + */ + public abstract val variance: KVariance? + /** * 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() + public data class Invariant(override val type: KType) : KTypeProjection() { + override val variance: KVariance? + get() = KVariance.INVARIANT + } /** * 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() + public data class In(override val type: KType) : KTypeProjection() { + override val variance: KVariance? + get() = KVariance.IN + } /** * 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() + public data class Out(override val type: KType) : KTypeProjection() { + override val variance: KVariance? + get() = KVariance.OUT + } /** * Star projection, denoted by the `*` character. @@ -97,5 +111,8 @@ public sealed class KTypeProjection { public object Star : KTypeProjection() { override val type: KType? get() = null + + override val variance: KVariance? + get() = null } } diff --git a/core/builtins/src/kotlin/reflect/KTypeParameter.kt b/core/builtins/src/kotlin/reflect/KTypeParameter.kt index 08cdcfbba1f..971192ec94f 100644 --- a/core/builtins/src/kotlin/reflect/KTypeParameter.kt +++ b/core/builtins/src/kotlin/reflect/KTypeParameter.kt @@ -26,4 +26,18 @@ public interface KTypeParameter : KClassifier { * The name of this type parameter as it was declared in the source code. */ public val name: String + + /** + * Upper bounds, or generic constraints imposed on this type parameter. + * See the [Kotlin language documentation](https://kotlinlang.org/docs/reference/generics.html#upper-bounds) + * for more information. + */ + public val upperBounds: List + + /** + * Declaration-site variance of this type parameter. + * See the [Kotlin language documentation](https://kotlinlang.org/docs/reference/generics.html#declaration-site-variance) + * for more information. + */ + public val variance: KVariance } diff --git a/core/builtins/src/kotlin/reflect/KVariance.kt b/core/builtins/src/kotlin/reflect/KVariance.kt new file mode 100644 index 00000000000..48b7dd32c73 --- /dev/null +++ b/core/builtins/src/kotlin/reflect/KVariance.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kotlin.reflect + +/** + * Represents variance applied to a type parameter on the declaration site (*declaration-site variance*), + * or to a type in a projection (*use-site variance*). + * + * See the [Kotlin language documentation](https://kotlinlang.org/docs/reference/generics.html#variance) + * for more information. + * + * @see [KTypeParameter.variance] + * @see [KTypeProjection] + */ +enum class KVariance { + /** + * The affected type parameter or type is *invariant*, which means it has no variance applied to it. + */ + INVARIANT, + + /** + * The affected type parameter or type is *contravariant*. Denoted by the `in` modifier in the source code. + */ + IN, + + /** + * The affected type parameter or type is *covariant*. Denoted by the `out` modifier in the source code. + */ + OUT, +} diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KTypeParameterImpl.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KTypeParameterImpl.kt index 14d23c6b7bf..2ec1cab1a3b 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KTypeParameterImpl.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KTypeParameterImpl.kt @@ -17,9 +17,32 @@ package kotlin.reflect.jvm.internal import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor +import org.jetbrains.kotlin.types.Variance +import kotlin.reflect.KType import kotlin.reflect.KTypeParameter +import kotlin.reflect.KVariance internal class KTypeParameterImpl(override val descriptor: TypeParameterDescriptor) : KTypeParameter, KClassifierImpl { override val name: String get() = descriptor.name.asString() + + override val upperBounds: List + get() = descriptor.upperBounds.map { kotlinType -> + KTypeImpl(kotlinType) { + TODO("Java type is not yet supported for type parameters: $descriptor") + } + } + + override val variance: KVariance + get() = when (descriptor.variance) { + Variance.INVARIANT -> KVariance.INVARIANT + Variance.IN_VARIANCE -> KVariance.IN + Variance.OUT_VARIANCE -> KVariance.OUT + } + + override fun equals(other: Any?) = + other is KTypeParameterImpl && descriptor == other.descriptor + + override fun hashCode() = + descriptor.hashCode() }