Reflection: add KType.classifier
#KT-8998 In Progress
This commit is contained in:
@@ -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.
|
||||
*
|
||||
|
||||
+7
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user