From 1edf3c7809601911189bf84353eac5388a8d27d7 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Tue, 12 Jul 2016 16:38:45 +0300 Subject: [PATCH] Reflection: add KClassifier.createType #KT-8998 In Progress --- .../typeToStringInnerGeneric.kt | 16 ++++ .../reflection/types/createType/equality.kt | 40 ++++++++++ .../types/createType/innerGeneric.kt | 26 +++++++ .../types/createType/simpleCreateType.kt | 18 +++++ .../types/createType/typeParameter.kt | 29 +++++++ .../createType/wrongNumberOfArguments.kt | 49 ++++++++++++ .../codegen/BlackBoxCodegenTestGenerated.java | 45 +++++++++++ .../src/kotlin/reflect/KClassifiers.kt | 78 +++++++++++++++++++ 8 files changed, 301 insertions(+) create mode 100644 compiler/testData/codegen/box/reflection/methodsFromAny/typeToStringInnerGeneric.kt create mode 100644 compiler/testData/codegen/box/reflection/types/createType/equality.kt create mode 100644 compiler/testData/codegen/box/reflection/types/createType/innerGeneric.kt create mode 100644 compiler/testData/codegen/box/reflection/types/createType/simpleCreateType.kt create mode 100644 compiler/testData/codegen/box/reflection/types/createType/typeParameter.kt create mode 100644 compiler/testData/codegen/box/reflection/types/createType/wrongNumberOfArguments.kt create mode 100644 core/reflection.jvm/src/kotlin/reflect/KClassifiers.kt diff --git a/compiler/testData/codegen/box/reflection/methodsFromAny/typeToStringInnerGeneric.kt b/compiler/testData/codegen/box/reflection/methodsFromAny/typeToStringInnerGeneric.kt new file mode 100644 index 00000000000..7117668b211 --- /dev/null +++ b/compiler/testData/codegen/box/reflection/methodsFromAny/typeToStringInnerGeneric.kt @@ -0,0 +1,16 @@ +// WITH_REFLECT + +import kotlin.test.assertEquals + +class A { + inner class B { + inner class C + } +} + +fun foo(): A.B.C = null!! + +fun box(): String { + assertEquals("A.B.C", ::foo.returnType.toString()) + return "OK" +} diff --git a/compiler/testData/codegen/box/reflection/types/createType/equality.kt b/compiler/testData/codegen/box/reflection/types/createType/equality.kt new file mode 100644 index 00000000000..eee27d0155b --- /dev/null +++ b/compiler/testData/codegen/box/reflection/types/createType/equality.kt @@ -0,0 +1,40 @@ +// WITH_REFLECT + +import kotlin.reflect.createType +import kotlin.reflect.KTypeProjection +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals + +class Foo + +fun box(): String { + assertEquals(String::class.createType(), String::class.createType()) + + assertEquals( + Foo::class.createType(listOf(KTypeProjection.Star)), + Foo::class.createType(listOf(KTypeProjection.Star)) + ) + + val i = Int::class.createType() + assertEquals( + Foo::class.createType(listOf(KTypeProjection.Invariant(i))), + Foo::class.createType(listOf(KTypeProjection.Invariant(i))) + ) + + assertNotEquals( + Foo::class.createType(listOf(KTypeProjection.In(i))), + Foo::class.createType(listOf(KTypeProjection.Out(i))) + ) + + assertNotEquals( + Foo::class.createType(listOf(KTypeProjection.Out(Any::class.createType(nullable = true)))), + Foo::class.createType(listOf(KTypeProjection.Star)) + ) + + assertNotEquals( + Foo::class.createType(listOf(KTypeProjection.Star), nullable = false), + Foo::class.createType(listOf(KTypeProjection.Star), nullable = true) + ) + + return "OK" +} diff --git a/compiler/testData/codegen/box/reflection/types/createType/innerGeneric.kt b/compiler/testData/codegen/box/reflection/types/createType/innerGeneric.kt new file mode 100644 index 00000000000..49a5975e7d3 --- /dev/null +++ b/compiler/testData/codegen/box/reflection/types/createType/innerGeneric.kt @@ -0,0 +1,26 @@ +// WITH_REFLECT + +import kotlin.reflect.createType +import kotlin.reflect.KClass +import kotlin.reflect.KTypeProjection +import kotlin.test.assertEquals + +class A { + inner class B { + inner class C + } + class D +} + +fun foo(): A.B.C = null!! + +fun box(): String { + fun KClass<*>.inv() = KTypeProjection.Invariant(this.createType()) + + val type = A.B.C::class.createType(listOf(Long::class.inv(), Double::class.inv(), Float::class.inv(), Int::class.inv())) + assertEquals("A.B.C", type.toString()) + + assertEquals("A.D", A.D::class.createType().toString()) + + return "OK" +} diff --git a/compiler/testData/codegen/box/reflection/types/createType/simpleCreateType.kt b/compiler/testData/codegen/box/reflection/types/createType/simpleCreateType.kt new file mode 100644 index 00000000000..1fbb0cfd600 --- /dev/null +++ b/compiler/testData/codegen/box/reflection/types/createType/simpleCreateType.kt @@ -0,0 +1,18 @@ +// WITH_REFLECT + +import kotlin.reflect.createType +import kotlin.reflect.KTypeProjection +import kotlin.test.assertEquals + +class Foo +class Bar + +fun box(): String { + assertEquals("Foo", Foo::class.createType().toString()) + assertEquals("Foo?", Foo::class.createType(nullable = true).toString()) + + assertEquals("Bar", Bar::class.createType(listOf(KTypeProjection.Invariant(String::class.createType()))).toString()) + assertEquals("Bar?", Bar::class.createType(listOf(KTypeProjection.Invariant(Int::class.createType())), nullable = true).toString()) + + return "OK" +} diff --git a/compiler/testData/codegen/box/reflection/types/createType/typeParameter.kt b/compiler/testData/codegen/box/reflection/types/createType/typeParameter.kt new file mode 100644 index 00000000000..b98db89bb2e --- /dev/null +++ b/compiler/testData/codegen/box/reflection/types/createType/typeParameter.kt @@ -0,0 +1,29 @@ +// WITH_REFLECT + +import kotlin.reflect.createType +import kotlin.test.assertEquals + +class Foo { + fun nonNull(): T = null!! + fun nullable(): T? = null +} + +fun box(): String { + val tp = Foo::class.typeParameters.single() + assertEquals( + Foo::class.members.single { it.name == "nonNull" }.returnType, + tp.createType() + ) + assertEquals( + Foo::class.members.single { it.name == "nullable" }.returnType, + tp.createType(nullable = true) + ) + + assertEquals(tp.createType(), tp.createType()) + assertEquals(tp.createType(nullable = true), tp.createType(nullable = true)) + + assertEquals("T", tp.createType().toString()) + assertEquals("T?", tp.createType(nullable = true).toString()) + + return "OK" +} diff --git a/compiler/testData/codegen/box/reflection/types/createType/wrongNumberOfArguments.kt b/compiler/testData/codegen/box/reflection/types/createType/wrongNumberOfArguments.kt new file mode 100644 index 00000000000..6426d5cc2f9 --- /dev/null +++ b/compiler/testData/codegen/box/reflection/types/createType/wrongNumberOfArguments.kt @@ -0,0 +1,49 @@ +// WITH_REFLECT + +import kotlin.reflect.createType +import kotlin.reflect.KClassifier +import kotlin.reflect.KTypeProjection + +fun test(classifier: KClassifier, arguments: List) { + try { + classifier.createType(arguments) + throw AssertionError("createType should have thrown IllegalArgumentException") + } + catch (e: IllegalArgumentException) { + // OK + } +} + +class Outer { + inner class Inner + class Nested +} + +fun box(): String { + val p = KTypeProjection.Star + + test(String::class, listOf(p)) + test(String::class, listOf(p, p)) + test(List::class, listOf()) + test(List::class, listOf(p, p)) + test(Map::class, listOf()) + test(Map::class, listOf(p)) + test(Map::class, listOf(p, p, p)) + test(Array::class, listOf()) + + test(Outer::class, listOf()) + test(Outer::class, listOf(p, p)) + + // Outer.Inner takes two arguments: first for O, second for I + test(Outer.Inner::class, listOf()) + test(Outer.Inner::class, listOf(p)) + test(Outer.Inner::class, listOf(p, p, p)) + + // Outer.Nested takes one argument for N + test(Outer.Nested::class, listOf()) + test(Outer.Nested::class, listOf(p, p)) + + test(Outer::class.typeParameters.single(), listOf(p)) + + return "OK" +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index d15f3d594cd..90a6afeda00 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -12584,6 +12584,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/methodsFromAny/typeToString.kt"); doTest(fileName); } + + @TestMetadata("typeToStringInnerGeneric.kt") + public void testTypeToStringInnerGeneric() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/methodsFromAny/typeToStringInnerGeneric.kt"); + doTest(fileName); + } } @TestMetadata("compiler/testData/codegen/box/reflection/multifileClasses") @@ -13049,6 +13055,45 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/useSiteVariance.kt"); doTest(fileName); } + + @TestMetadata("compiler/testData/codegen/box/reflection/types/createType") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class CreateType extends AbstractBlackBoxCodegenTest { + public void testAllFilesPresentInCreateType() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/reflection/types/createType"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("equality.kt") + public void testEquality() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/createType/equality.kt"); + doTest(fileName); + } + + @TestMetadata("innerGeneric.kt") + public void testInnerGeneric() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/createType/innerGeneric.kt"); + doTest(fileName); + } + + @TestMetadata("simpleCreateType.kt") + public void testSimpleCreateType() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/createType/simpleCreateType.kt"); + doTest(fileName); + } + + @TestMetadata("typeParameter.kt") + public void testTypeParameter() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/createType/typeParameter.kt"); + doTest(fileName); + } + + @TestMetadata("wrongNumberOfArguments.kt") + public void testWrongNumberOfArguments() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/createType/wrongNumberOfArguments.kt"); + doTest(fileName); + } + } } } diff --git a/core/reflection.jvm/src/kotlin/reflect/KClassifiers.kt b/core/reflection.jvm/src/kotlin/reflect/KClassifiers.kt new file mode 100644 index 00000000000..69e5516e591 --- /dev/null +++ b/core/reflection.jvm/src/kotlin/reflect/KClassifiers.kt @@ -0,0 +1,78 @@ +/* + * 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. + */ + +@file:JvmName("KClassifiers") +package kotlin.reflect + +import org.jetbrains.kotlin.descriptors.annotations.Annotations +import org.jetbrains.kotlin.types.* +import kotlin.reflect.jvm.internal.KClassImpl +import kotlin.reflect.jvm.internal.KClassifierImpl +import kotlin.reflect.jvm.internal.KTypeImpl +import kotlin.reflect.jvm.internal.KTypeParameterImpl + +/** + * Creates a [KType] instance with the given classifier, type arguments, nullability and annotations. + * If the number of passed type arguments is not equal to the total number of type parameters of a classifier, + * an exception is thrown. If any of the arguments does not satisfy the bounds of the corresponding type parameter, + * an exception is thrown. + * + * For classifiers representing type parameters, the type argument list must always be empty. + * For classes, the type argument list should contain arguments for the type parameters of the class. If the class is `inner`, + * the list should follow with arguments for the type parameters of its outer class, and so forth until a class is + * not `inner`, or is declared on the top level. + */ +fun KClassifier.createType( + arguments: List = emptyList(), + nullable: Boolean = false, + annotations: List = emptyList() +): KType { + val descriptor = (this as? KClassifierImpl)?.descriptor + ?: throw KotlinReflectionInternalError("Cannot create type for an unsupported classifier: $this (${this.javaClass})") + + val typeConstructor = descriptor.typeConstructor + val parameters = typeConstructor.parameters + if (parameters.size != arguments.size) { + throw IllegalArgumentException("Class declares ${parameters.size} type parameters, but ${arguments.size} were provided.") + } + + // TODO: throw exception if argument does not satisfy bounds + + val typeAnnotations = + if (annotations.isEmpty()) Annotations.EMPTY + else Annotations.EMPTY // TODO: support type annotations + + val kotlinType = createKotlinType(typeAnnotations, typeConstructor, arguments, nullable) + + return KTypeImpl(kotlinType) { + TODO("Java type is not yet supported for types created with createType (classifier = $this)") + } +} + +private fun createKotlinType( + typeAnnotations: Annotations, typeConstructor: TypeConstructor, arguments: List, nullable: Boolean +): SimpleType { + val parameters = typeConstructor.parameters + return KotlinTypeFactory.simpleType(typeAnnotations, typeConstructor, arguments.mapIndexed { index, typeProjection -> + val type = (typeProjection.type as KTypeImpl?)?.type + when (typeProjection) { + is KTypeProjection.Invariant -> TypeProjectionImpl(Variance.INVARIANT, type!!) + is KTypeProjection.In -> TypeProjectionImpl(Variance.IN_VARIANCE, type!!) + is KTypeProjection.Out -> TypeProjectionImpl(Variance.OUT_VARIANCE, type!!) + is KTypeProjection.Star -> StarProjectionImpl(parameters[index]) + } + }, nullable) +}