Reflection: add KType.classifier

#KT-8998 In Progress
This commit is contained in:
Alexander Udalov
2016-07-12 16:09:29 +03:00
parent 2ab0f489b5
commit c1dd831e65
8 changed files with 284 additions and 1 deletions
@@ -0,0 +1,25 @@
// WITH_REFLECT
import kotlin.test.assertEquals
class Outer<O> {
class Nested
inner class Inner
}
fun outer(): Outer<String> = null!!
fun nested(): Outer.Nested = null!!
fun inner(): Outer<Int>.Inner = null!!
fun array(): Array<String> = null!!
fun box(): String {
assertEquals(Outer::class, ::outer.returnType.classifier)
assertEquals(Outer.Nested::class, ::nested.returnType.classifier)
assertEquals(Outer.Inner::class, ::inner.returnType.classifier)
assertEquals(Array<String>::class, ::array.returnType.classifier)
return "OK"
}
@@ -0,0 +1,23 @@
// WITH_REFLECT
import kotlin.reflect.KTypeParameter
import kotlin.test.*
class A<U> {
fun <T> foo(): T = null!!
fun bar(): Array<U>? = null!!
}
fun box(): String {
val t = A::class.members.single { it.name == "foo" }.returnType
assertFalse(t.isMarkedNullable)
val tc = t.classifier
if (tc !is KTypeParameter) fail(tc.toString())
assertEquals("T", tc.name)
val u = A::class.members.single { it.name == "bar" }.returnType
assertTrue(u.isMarkedNullable)
assertEquals(Array<Any>::class, u.classifier)
return "OK"
}
@@ -0,0 +1,112 @@
// WITH_REFLECT
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.test.assertEquals
fun primitives(
p01: Boolean,
p02: Byte,
p03: Char,
p04: Double,
p05: Float,
p06: Int,
p07: Long,
p08: Short
) {}
fun nullablePrimitives(
p01: Boolean?,
p02: Byte?,
p03: Char?,
p04: Double?,
p05: Float?,
p06: Int?,
p07: Long?,
p08: Short?
) {}
fun primitiveArrays(
p01: BooleanArray,
p02: ByteArray,
p03: CharArray,
p04: DoubleArray,
p05: FloatArray,
p06: IntArray,
p07: LongArray,
p08: ShortArray
) {}
fun others(
p1: Array<*>,
p2: Array<String>,
p3: Array<Array<Int?>?>,
p4: List<*>,
p5: List<String>?,
p6: Map.Entry<Int, Double>,
p7: Unit?,
p8: String,
p9: Nothing
) {}
inline fun <reified T : Any> wrapper(): KClass<T> = T::class
fun check(f: KFunction<*>, vararg expected: KClass<*>) {
val actual = f.parameters.map { it.type.classifier as KClass<*> }
for ((e, a) in expected.toList().zip(actual)) {
assertEquals(e, a, "$e (${e.java}) != $a (${a.java})")
}
}
fun box(): String {
check(
::primitives,
Boolean::class,
Byte::class,
Char::class,
Double::class,
Float::class,
Int::class,
Long::class,
Short::class
)
check(
::nullablePrimitives,
wrapper<Boolean>(),
wrapper<Byte>(),
wrapper<Char>(),
wrapper<Double>(),
wrapper<Float>(),
wrapper<Int>(),
wrapper<Long>(),
wrapper<Short>()
)
check(
::primitiveArrays,
BooleanArray::class,
ByteArray::class,
CharArray::class,
DoubleArray::class,
FloatArray::class,
IntArray::class,
LongArray::class,
ShortArray::class
)
check(
::others,
Array<Any>::class,
Array<String>::class,
Array<Array<Int?>?>::class,
List::class,
List::class,
Map.Entry::class,
Unit::class,
String::class,
Nothing::class
)
return "OK"
}
@@ -0,0 +1,37 @@
// WITH_REFLECT
// FILE: J.java
import java.util.List;
public class J {
public static String string() {
return "";
}
public static List<Object> list() {
return null;
}
public static int primitiveInt() {
return 0;
}
public static Integer wrapperInt() {
return 0;
}
}
// FILE: K.kt
import kotlin.reflect.KClass
import kotlin.test.assertEquals
fun box(): String {
assertEquals(String::class, J::string.returnType.classifier)
assertEquals(List::class, J::list.returnType.classifier)
assertEquals(Int::class.javaPrimitiveType!!, (J::primitiveInt.returnType.classifier as KClass<*>).java)
assertEquals(Int::class.javaObjectType, (J::wrapperInt.returnType.classifier as KClass<*>).java)
return "OK"
}
@@ -12933,6 +12933,30 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/reflection/types"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("classifierIsClass.kt")
public void testClassifierIsClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/classifierIsClass.kt");
doTest(fileName);
}
@TestMetadata("classifierIsTypeParameter.kt")
public void testClassifierIsTypeParameter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/classifierIsTypeParameter.kt");
doTest(fileName);
}
@TestMetadata("classifiersOfBuiltInTypes.kt")
public void testClassifiersOfBuiltInTypes() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/classifiersOfBuiltInTypes.kt");
doTest(fileName);
}
@TestMetadata("platformTypeClassifier.kt")
public void testPlatformTypeClassifier() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/platformTypeClassifier.kt");
doTest(fileName);
}
@TestMetadata("platformTypeNotEqualToKotlinType.kt")
public void testPlatformTypeNotEqualToKotlinType() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/platformTypeNotEqualToKotlinType.kt");
@@ -21,6 +21,14 @@ package kotlin.reflect
* or a type parameter of some declaration, plus nullability.
*/
public interface KType {
/**
* The declaration of the classifier used in this type.
* For example, in the type `List<String>` the classifier would be the [KClass] instance for [List].
*
* Returns `null` if this type is not denotable in Kotlin, for example if it is an intersection type.
*/
public val classifier: KClassifier?
/**
* `true` if this type was marked nullable in the source code.
*
@@ -27,6 +27,13 @@ val Class<*>.safeClassLoader: ClassLoader
fun Class<*>.isEnumClassOrSpecializedEnumEntryClass(): Boolean =
Enum::class.java.isAssignableFrom(this)
private val WRAPPER_TO_PRIMITIVE = listOf(
Boolean::class, Byte::class, Char::class, Double::class, Float::class, Int::class, Long::class, Short::class
).map { it.javaObjectType to it.javaPrimitiveType }.toMap()
val Class<*>.primitiveByWrapper: Class<*>?
get() = WRAPPER_TO_PRIMITIVE[this]
/**
* NOTE: does not perform a Java -> Kotlin mapping. If this is not expected, consider using KClassImpl#classId instead
*/
@@ -16,9 +16,18 @@
package kotlin.reflect.jvm.internal
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.TypeAliasDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
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 java.lang.reflect.Type
import kotlin.reflect.KClass
import kotlin.reflect.KClassifier
import kotlin.reflect.KType
import kotlin.reflect.KTypeParameter
internal class KTypeImpl(
val type: KotlinType,
@@ -26,11 +35,49 @@ internal class KTypeImpl(
) : KType {
internal val javaType: Type by ReflectProperties.lazySoft(computeJavaType)
override val classifier: KClassifier?
get() = convert(type)
private fun convert(type: KotlinType): KClassifier? {
val descriptor = type.constructor.declarationDescriptor
when (descriptor) {
is ClassDescriptor -> {
val jClass = descriptor.toJavaClass() ?: return null
if (jClass.isArray) {
// There may be no argument if it's a primitive array (such as IntArray)
val argument = type.arguments.singleOrNull()?.type ?: return KClassImpl(jClass)
val elementClassifier = convert(argument)
val elementType = when (elementClassifier) {
is KClass<*> -> elementClassifier
is KTypeParameter -> {
// For arrays of type parameters (`Array<T>`) we return the KClass representing `Array<Any>`
// since there's no other sensible option
// TODO: return `Array<erasure-of-T>`
Any::class
}
else -> TODO("Arrays of type alias classifiers are not yet supported")
}
return KClassImpl(elementType.java.createArrayType())
}
if (!TypeUtils.isNullableType(type)) {
return KClassImpl(jClass.primitiveByWrapper ?: jClass)
}
return KClassImpl(jClass)
}
is TypeParameterDescriptor -> return KTypeParameterImpl(descriptor)
is TypeAliasDescriptor -> TODO("Type alias classifiers are not yet supported")
else -> return null
}
}
override val isMarkedNullable: Boolean
get() = type.isMarkedNullable
override fun equals(other: Any?) =
other is KTypeImpl && type.equals(other.type)
other is KTypeImpl && type == other.type
override fun hashCode() =
type.hashCode()