Support KType.javaType in stdlib
#KT-32241 Fixed
This commit is contained in:
committed by
Alexander Udalov
parent
55595fe2c6
commit
9e37b62f62
@@ -19,6 +19,9 @@ 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.descriptors.runtime.structure.createArrayType
|
||||
import org.jetbrains.kotlin.descriptors.runtime.structure.parameterizedTypeArguments
|
||||
import org.jetbrains.kotlin.descriptors.runtime.structure.primitiveByWrapper
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import org.jetbrains.kotlin.types.Variance
|
||||
@@ -27,19 +30,16 @@ import java.lang.reflect.ParameterizedType
|
||||
import java.lang.reflect.Type
|
||||
import java.lang.reflect.WildcardType
|
||||
import kotlin.LazyThreadSafetyMode.PUBLICATION
|
||||
import kotlin.jvm.internal.KTypeBase
|
||||
import kotlin.reflect.KClassifier
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.KTypeProjection
|
||||
import org.jetbrains.kotlin.descriptors.runtime.structure.createArrayType
|
||||
import org.jetbrains.kotlin.descriptors.runtime.structure.parameterizedTypeArguments
|
||||
import org.jetbrains.kotlin.descriptors.runtime.structure.primitiveByWrapper
|
||||
import kotlin.reflect.jvm.jvmErasure
|
||||
|
||||
internal class KTypeImpl(
|
||||
val type: KotlinType,
|
||||
computeJavaType: () -> Type
|
||||
) : KType {
|
||||
internal val javaType: Type by ReflectProperties.lazySoft(computeJavaType)
|
||||
) : KTypeBase {
|
||||
override val javaType: Type by ReflectProperties.lazySoft(computeJavaType)
|
||||
|
||||
override val classifier: KClassifier? by ReflectProperties.lazySoft { convert(type) }
|
||||
|
||||
|
||||
@@ -132,6 +132,8 @@ compileTestKotlin {
|
||||
"-Xopt-in=kotlin.ExperimentalUnsignedTypes",
|
||||
"-Xopt-in=kotlin.ExperimentalStdlibApi",
|
||||
]
|
||||
// This is needed for JavaTypeTest; typeOf for non-reified type parameters doesn't work otherwise, for implementation reasons.
|
||||
freeCompilerArgs.remove("-Xno-optimized-callable-references")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package kotlin.jvm.internal
|
||||
|
||||
import java.lang.reflect.Type
|
||||
import kotlin.reflect.KType
|
||||
|
||||
@SinceKotlin("1.4")
|
||||
public interface KTypeBase : KType {
|
||||
val javaType: Type?
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package kotlin.reflect
|
||||
|
||||
import java.lang.reflect.*
|
||||
import kotlin.internal.LowPriorityInOverloadResolution
|
||||
import kotlin.jvm.internal.KTypeBase
|
||||
|
||||
/**
|
||||
* Returns a Java [Type] instance corresponding to the given Kotlin type.
|
||||
*
|
||||
* This function is experimental because not all the features are supported yet, and behavior might change in corner cases.
|
||||
* In particular, the following is not supported correctly or at all:
|
||||
* - declaration-site variance
|
||||
* - variance of types annotated with [JvmSuppressWildcards]
|
||||
* - obtaining the containing declaration of a type parameter ([TypeVariable.getGenericDeclaration])
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
@LowPriorityInOverloadResolution // To make non-experimental kotlin.reflect.full.javaType always win in overload resolution
|
||||
public val KType.javaType: Type
|
||||
get() {
|
||||
if (this is KTypeBase) {
|
||||
// Use kotlin-reflect implementation for types which are position-dependent, e.g. "Unit" in a callable's return type.
|
||||
javaType?.let { return it }
|
||||
}
|
||||
|
||||
return computeJavaType()
|
||||
}
|
||||
|
||||
@ExperimentalStdlibApi
|
||||
private fun KType.computeJavaType(forceWrapper: Boolean = false): Type {
|
||||
when (val classifier = classifier) {
|
||||
is KTypeParameter -> return TypeVariableImpl(classifier)
|
||||
is KClass<*> -> {
|
||||
val jClass = if (forceWrapper) classifier.javaObjectType else classifier.java
|
||||
val arguments = arguments
|
||||
if (arguments.isEmpty()) return jClass
|
||||
|
||||
if (jClass.isArray) {
|
||||
if (jClass.componentType.isPrimitive) return jClass
|
||||
|
||||
val (variance, elementType) = arguments.singleOrNull()
|
||||
?: throw IllegalArgumentException("kotlin.Array must have exactly one type argument: $this")
|
||||
return when (variance) {
|
||||
// Array<in ...> is always erased to Object[], and Array<*> is Object[].
|
||||
null, KVariance.IN -> jClass
|
||||
KVariance.INVARIANT, KVariance.OUT -> {
|
||||
val javaElementType = elementType!!.computeJavaType()
|
||||
if (javaElementType is Class<*>) jClass else GenericArrayTypeImpl(javaElementType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return createPossiblyInnerType(jClass, arguments)
|
||||
}
|
||||
else -> throw UnsupportedOperationException("Unsupported type classifier: $this")
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalStdlibApi
|
||||
private fun createPossiblyInnerType(jClass: Class<*>, arguments: List<KTypeProjection>): Type {
|
||||
val ownerClass = jClass.declaringClass
|
||||
?: return ParameterizedTypeImpl(jClass, null, arguments.map(KTypeProjection::javaType))
|
||||
if (Modifier.isStatic(jClass.modifiers))
|
||||
return ParameterizedTypeImpl(jClass, ownerClass, arguments.map(KTypeProjection::javaType))
|
||||
|
||||
val n = jClass.typeParameters.size
|
||||
return ParameterizedTypeImpl(
|
||||
jClass,
|
||||
createPossiblyInnerType(ownerClass, arguments.subList(n, arguments.size)),
|
||||
arguments.subList(0, n).map(KTypeProjection::javaType)
|
||||
)
|
||||
}
|
||||
|
||||
@ExperimentalStdlibApi
|
||||
private val KTypeProjection.javaType: Type
|
||||
get() {
|
||||
val variance = variance ?: return WildcardTypeImpl.STAR
|
||||
val type = type!!
|
||||
// TODO: JvmSuppressWildcards
|
||||
return when (variance) {
|
||||
KVariance.INVARIANT -> {
|
||||
// TODO: declaration-site variance
|
||||
type.computeJavaType(forceWrapper = true)
|
||||
}
|
||||
KVariance.IN -> WildcardTypeImpl(null, type.computeJavaType(forceWrapper = true))
|
||||
KVariance.OUT -> WildcardTypeImpl(type.computeJavaType(forceWrapper = true), null)
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalStdlibApi
|
||||
private interface TypeImpl : Type {
|
||||
// This is a copy of [Type.getTypeName] which is present on JDK 8+.
|
||||
fun getTypeName(): String
|
||||
}
|
||||
|
||||
@ExperimentalStdlibApi
|
||||
private class TypeVariableImpl(private val typeParameter: KTypeParameter) : TypeVariable<GenericDeclaration>, TypeImpl {
|
||||
override fun getName(): String = typeParameter.name
|
||||
|
||||
override fun getGenericDeclaration(): GenericDeclaration =
|
||||
TODO("getGenericDeclaration() is not yet supported for type variables created from KType: $typeParameter")
|
||||
|
||||
override fun getBounds(): Array<Type> = typeParameter.upperBounds.map { it.computeJavaType(forceWrapper = true) }.toTypedArray()
|
||||
|
||||
override fun getTypeName(): String = name
|
||||
|
||||
override fun equals(other: Any?): Boolean =
|
||||
other is TypeVariable<*> && name == other.name && genericDeclaration == other.genericDeclaration
|
||||
|
||||
override fun hashCode(): Int =
|
||||
name.hashCode() xor genericDeclaration.hashCode()
|
||||
|
||||
override fun toString(): String = getTypeName()
|
||||
}
|
||||
|
||||
@ExperimentalStdlibApi
|
||||
private class GenericArrayTypeImpl(private val elementType: Type) : GenericArrayType, TypeImpl {
|
||||
override fun getGenericComponentType(): Type = elementType
|
||||
|
||||
override fun getTypeName(): String = "${typeToString(elementType)}[]"
|
||||
|
||||
override fun equals(other: Any?): Boolean = other is GenericArrayType && genericComponentType == other.genericComponentType
|
||||
|
||||
override fun hashCode(): Int = genericComponentType.hashCode()
|
||||
|
||||
override fun toString(): String = getTypeName()
|
||||
}
|
||||
|
||||
@ExperimentalStdlibApi
|
||||
private class WildcardTypeImpl(private val upperBound: Type?, private val lowerBound: Type?) : WildcardType, TypeImpl {
|
||||
override fun getUpperBounds(): Array<Type> =
|
||||
arrayOf(upperBound ?: Any::class.java)
|
||||
|
||||
override fun getLowerBounds(): Array<Type> =
|
||||
if (lowerBound == null) emptyArray() else arrayOf(lowerBound)
|
||||
|
||||
override fun getTypeName(): String = when {
|
||||
lowerBound != null -> "? super ${typeToString(lowerBound)}"
|
||||
upperBound != null && upperBound != Any::class.java -> "? extends ${typeToString(upperBound)}"
|
||||
else -> "?"
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean =
|
||||
other is WildcardType && upperBounds.contentEquals(other.upperBounds) && lowerBounds.contentEquals(other.lowerBounds)
|
||||
|
||||
override fun hashCode(): Int =
|
||||
upperBounds.contentHashCode() xor lowerBounds.contentHashCode()
|
||||
|
||||
override fun toString(): String = getTypeName()
|
||||
|
||||
companion object {
|
||||
val STAR = WildcardTypeImpl(null, null)
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalStdlibApi
|
||||
private class ParameterizedTypeImpl(
|
||||
private val rawType: Class<*>,
|
||||
private val ownerType: Type?,
|
||||
typeArguments: List<Type>
|
||||
) : ParameterizedType, TypeImpl {
|
||||
private val typeArguments = typeArguments.toTypedArray()
|
||||
|
||||
override fun getRawType(): Type = rawType
|
||||
|
||||
override fun getOwnerType(): Type? = ownerType
|
||||
|
||||
override fun getActualTypeArguments(): Array<Type> = typeArguments
|
||||
|
||||
override fun getTypeName(): String = buildString {
|
||||
if (ownerType != null) {
|
||||
append(typeToString(ownerType))
|
||||
append("$")
|
||||
append(rawType.simpleName)
|
||||
} else {
|
||||
append(typeToString(rawType))
|
||||
}
|
||||
|
||||
if (typeArguments.isNotEmpty()) {
|
||||
typeArguments.joinTo(this, prefix = "<", postfix = ">", transform = ::typeToString)
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean =
|
||||
other is ParameterizedType && rawType == other.rawType && ownerType == other.ownerType &&
|
||||
actualTypeArguments.contentEquals(other.actualTypeArguments)
|
||||
|
||||
override fun hashCode(): Int =
|
||||
rawType.hashCode() xor ownerType.hashCode() xor actualTypeArguments.contentHashCode()
|
||||
|
||||
override fun toString(): String = getTypeName()
|
||||
}
|
||||
|
||||
// TODO: use getTypeName instead of this, when this code is migrated to JDK 8.
|
||||
private fun typeToString(type: Type): String =
|
||||
if (type is Class<*>) {
|
||||
if (type.isArray) {
|
||||
val unwrap = generateSequence(type, Class<*>::getComponentType)
|
||||
unwrap.last().name + "[]".repeat(unwrap.count())
|
||||
} else type.name
|
||||
} else type.toString()
|
||||
@@ -0,0 +1,377 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package test.reflection
|
||||
|
||||
import org.junit.Test
|
||||
import java.lang.reflect.*
|
||||
import kotlin.internal.OnlyInputTypes
|
||||
import kotlin.reflect.javaType
|
||||
import kotlin.reflect.typeOf
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotEquals
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.fail
|
||||
|
||||
@Suppress(
|
||||
"unused", "UNUSED_VARIABLE", "ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE", "RedundantInnerClassModifier", "NAME_SHADOWING",
|
||||
"FINAL_UPPER_BOUND"
|
||||
)
|
||||
class JavaTypeTest {
|
||||
@Test
|
||||
fun primitives() {
|
||||
assertEquals(Boolean::class.java, javaTypeOf<Boolean>())
|
||||
assertEquals(Char::class.java, javaTypeOf<Char>())
|
||||
assertEquals(Byte::class.java, javaTypeOf<Byte>())
|
||||
assertEquals(Short::class.java, javaTypeOf<Short>())
|
||||
assertEquals(Int::class.java, javaTypeOf<Int>())
|
||||
assertEquals(Float::class.java, javaTypeOf<Float>())
|
||||
assertEquals(Long::class.java, javaTypeOf<Long>())
|
||||
assertEquals(Double::class.java, javaTypeOf<Double>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun primitiveArrays() {
|
||||
assertEquals(BooleanArray::class.java, javaTypeOf<BooleanArray>())
|
||||
assertEquals(CharArray::class.java, javaTypeOf<CharArray>())
|
||||
assertEquals(ByteArray::class.java, javaTypeOf<ByteArray>())
|
||||
assertEquals(ShortArray::class.java, javaTypeOf<ShortArray>())
|
||||
assertEquals(IntArray::class.java, javaTypeOf<IntArray>())
|
||||
assertEquals(FloatArray::class.java, javaTypeOf<FloatArray>())
|
||||
assertEquals(LongArray::class.java, javaTypeOf<LongArray>())
|
||||
assertEquals(DoubleArray::class.java, javaTypeOf<DoubleArray>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun builtinClasses() {
|
||||
assertEquals(String::class.java, javaTypeOf<String>())
|
||||
assertEquals(Number::class.java, javaTypeOf<Number>())
|
||||
assertEquals(Unit::class.java, javaTypeOf<Unit>())
|
||||
assertEquals(Annotation::class.java, javaTypeOf<Annotation>())
|
||||
assertEquals(Cloneable::class.java, javaTypeOf<Cloneable>())
|
||||
assertEquals(String.Companion::class.java, javaTypeOf<String.Companion>())
|
||||
assertEquals(Deprecated::class.java, javaTypeOf<Deprecated>())
|
||||
|
||||
assertEquals(Void::class.java, javaTypeOf<Void>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun enumOfStar() {
|
||||
parameterized(javaTypeOf<Enum<*>>()) { type ->
|
||||
star(type.actualTypeArguments.single())
|
||||
assertEquals(null, type.ownerType)
|
||||
assertEquals(Enum::class.java, type.rawType)
|
||||
assertEquals("java.lang.Enum<?>", type.typeName)
|
||||
}
|
||||
|
||||
assertEqualsAndHashCode(javaTypeOf<Enum<*>>(), javaTypeOf<Enum<*>>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun recursiveEnumOfStar() {
|
||||
parameterized(javaTypeOf<Enum<out Enum<*>>>()) { type ->
|
||||
wildcardExtends(type.actualTypeArguments.single()) { starEnum ->
|
||||
parameterized(starEnum) { starEnumType ->
|
||||
star(starEnumType.actualTypeArguments.single())
|
||||
assertEquals(Enum::class.java, starEnumType.rawType)
|
||||
}
|
||||
}
|
||||
assertEquals(Enum::class.java, type.rawType)
|
||||
assertEquals("java.lang.Enum<? extends java.lang.Enum<?>>", type.typeName)
|
||||
}
|
||||
|
||||
assertEqualsAndHashCode(javaTypeOf<Enum<out Enum<*>>>(), javaTypeOf<Enum<out Enum<*>>>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun listOfString() {
|
||||
parameterized(javaTypeOf<List<String>>()) { type ->
|
||||
assertEquals(String::class.java, type.actualTypeArguments.single())
|
||||
assertEquals(null, type.ownerType)
|
||||
assertEquals(List::class.java, type.rawType)
|
||||
assertEquals("java.util.List<java.lang.String>", type.typeName)
|
||||
}
|
||||
parameterized(javaTypeOf<MutableList<String>>()) { type ->
|
||||
assertEquals(String::class.java, type.actualTypeArguments.single())
|
||||
assertEquals(null, type.ownerType)
|
||||
assertEquals(MutableList::class.java, type.rawType)
|
||||
assertEquals("java.util.List<java.lang.String>", type.typeName)
|
||||
}
|
||||
|
||||
assertEqualsAndHashCode(javaTypeOf<List<String>>(), javaTypeOf<List<String>>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun arrays() {
|
||||
assertEquals(Array<Any>::class.java, javaTypeOf<Array<Any>>())
|
||||
assertEquals(Array<Array<Any>>::class.java, javaTypeOf<Array<Array<Any>>>())
|
||||
assertEquals(Array<Boolean>::class.java, javaTypeOf<Array<Boolean>>())
|
||||
|
||||
assertEquals(Array<Any>::class.java, javaTypeOf<Array<in String>>())
|
||||
assertEquals(Array<Any>::class.java, javaTypeOf<Array<in Array<String>>>())
|
||||
|
||||
assertEquals(Array<Number>::class.java, javaTypeOf<Array<out Number>>())
|
||||
assertEquals(Array<Array<Number>>::class.java, javaTypeOf<Array<out Array<Number>>>())
|
||||
|
||||
assertEquals(Array<Any>::class.java, javaTypeOf<Array<*>>())
|
||||
|
||||
genericArray(javaTypeOf<Array<Set<String>>>()) { type ->
|
||||
parameterized(type.genericComponentType) { argument ->
|
||||
assertEquals(String::class.java, argument.actualTypeArguments.single())
|
||||
assertEquals(Set::class.java, argument.rawType)
|
||||
}
|
||||
assertEquals("java.util.Set<java.lang.String>[]", type.typeName)
|
||||
}
|
||||
|
||||
assertEqualsAndHashCode(javaTypeOf<Array<Set<String>>>(), javaTypeOf<Array<Set<String>>>())
|
||||
assertNotEquals(javaTypeOf<Array<Set<String>>>(), javaTypeOf<Array<Collection<String>>>())
|
||||
assertNotEquals(javaTypeOf<Array<Set<String>>>(), javaTypeOf<Array<Set<CharSequence>>>())
|
||||
assertNotEquals(javaTypeOf<Array<Set<String>>>(), javaTypeOf<List<Set<String>>>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun <U1, U2, U3 : U2> functionTypeVariables() where U2 : Number, U2 : Comparable<*> {
|
||||
val m = this::class.java.declaredMethods.single { it.name == "functionTypeVariables" }
|
||||
val tvs = m.typeParameters
|
||||
parameterized(javaTypeOf<Set<U1>>()) { type ->
|
||||
typeVariable(type.actualTypeArguments.single()) { tv ->
|
||||
assertEquals("U1", tv.name)
|
||||
assertEquals("U1", tv.typeName)
|
||||
assertEquals(listOf(Any::class.java), tv.bounds.toList())
|
||||
|
||||
// assertEquals(m, tv.genericDeclaration)
|
||||
// assertEqualsAndHashCode(tvs[0], tv)
|
||||
}
|
||||
}
|
||||
lateinit var u2: TypeVariable<*>
|
||||
parameterized(javaTypeOf<Set<U2>>()) { type ->
|
||||
typeVariable(type.actualTypeArguments.single()) { tv ->
|
||||
assertEquals("U2", tv.name)
|
||||
val bounds = tv.bounds.toList()
|
||||
assertEquals(2, bounds.size)
|
||||
assertEquals(Number::class.java, bounds[0])
|
||||
parameterized(bounds[1]) { comparable ->
|
||||
assertEquals(Comparable::class.java, comparable.rawType)
|
||||
star(comparable.actualTypeArguments.single())
|
||||
}
|
||||
u2 = tv
|
||||
|
||||
// assertEquals(m, tv.genericDeclaration)
|
||||
// assertEqualsAndHashCode(tvs[1], tv)
|
||||
}
|
||||
}
|
||||
parameterized(javaTypeOf<Set<U3>>()) { type ->
|
||||
typeVariable(type.actualTypeArguments.single()) { tv ->
|
||||
assertEquals("U3", tv.name)
|
||||
// assertEquals(listOf(u2), tv.bounds.toList())
|
||||
|
||||
// assertEquals(m, tv.genericDeclaration)
|
||||
// assertEqualsAndHashCode(tvs[2], tv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class T1<V1, V2, V3 : V2> where V2 : Number, V2 : Comparable<*> {
|
||||
fun setOfV1(): Type = javaTypeOf<Set<V1>>()
|
||||
fun setOfV2(): Type = javaTypeOf<Set<V2>>()
|
||||
fun setOfV3(): Type = javaTypeOf<Set<V3>>()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun classTypeVariables() {
|
||||
val t1 = T1<Int, Int, Int>()
|
||||
val tvs = T1::class.java.typeParameters
|
||||
parameterized(t1.setOfV1()) { type ->
|
||||
typeVariable(type.actualTypeArguments.single()) { tv ->
|
||||
assertEquals("V1", tv.name)
|
||||
assertEquals(listOf(Any::class.java), tv.bounds.toList())
|
||||
assertEquals("V1", tv.typeName)
|
||||
|
||||
// assertEquals(T1::class.java, tv.genericDeclaration)
|
||||
// assertEqualsAndHashCode(tvs[0], tv)
|
||||
}
|
||||
}
|
||||
lateinit var v2: TypeVariable<*>
|
||||
parameterized(t1.setOfV2()) { type ->
|
||||
typeVariable(type.actualTypeArguments.single()) { tv ->
|
||||
assertEquals("V2", tv.name)
|
||||
val bounds = tv.bounds.toList()
|
||||
assertEquals(2, bounds.size)
|
||||
assertEquals(Number::class.java, bounds[0])
|
||||
parameterized(bounds[1]) { comparable ->
|
||||
assertEquals(Comparable::class.java, comparable.rawType)
|
||||
star(comparable.actualTypeArguments.single())
|
||||
}
|
||||
v2 = tv
|
||||
|
||||
// assertEquals(T1::class.java, tv.genericDeclaration)
|
||||
// assertEqualsAndHashCode(tvs[1], tv)
|
||||
}
|
||||
}
|
||||
parameterized(t1.setOfV3()) { type ->
|
||||
typeVariable(type.actualTypeArguments.single()) { tv ->
|
||||
assertEquals("V3", tv.name)
|
||||
// assertEquals(listOf(v2), tv.bounds.toList())
|
||||
|
||||
// assertEquals(T1::class.java, tv.genericDeclaration)
|
||||
// assertEqualsAndHashCode(tvs[2], tv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class T2<W>
|
||||
|
||||
@Test
|
||||
fun useSiteVariance() {
|
||||
parameterized(javaTypeOf<T2<*>>()) { type ->
|
||||
star(type.actualTypeArguments.single())
|
||||
}
|
||||
parameterized(javaTypeOf<T2<out CharSequence>>()) { type ->
|
||||
wildcardExtends(type.actualTypeArguments.single()) { bound ->
|
||||
assertEquals(CharSequence::class.java, bound)
|
||||
}
|
||||
}
|
||||
parameterized(javaTypeOf<T2<in CharSequence>>()) { type ->
|
||||
wildcardSuper(type.actualTypeArguments.single()) { bound ->
|
||||
assertEquals(CharSequence::class.java, bound)
|
||||
}
|
||||
}
|
||||
|
||||
assertEqualsAndHashCode(javaTypeOf<T2<*>>(), javaTypeOf<T2<*>>())
|
||||
assertEqualsAndHashCode(javaTypeOf<T2<out CharSequence>>(), javaTypeOf<T2<out CharSequence>>())
|
||||
assertEqualsAndHashCode(javaTypeOf<T2<in CharSequence>>(), javaTypeOf<T2<in CharSequence>>())
|
||||
|
||||
assertNotEquals(javaTypeOf<T2<out CharSequence>>(), javaTypeOf<T2<out Number>>())
|
||||
assertNotEquals(javaTypeOf<T2<in CharSequence>>(), javaTypeOf<T2<in Number>>())
|
||||
}
|
||||
|
||||
private class T3<X1, X2> {
|
||||
class Nested<Y>
|
||||
|
||||
inner class Inner<Z> {
|
||||
inner class NonGeneric {
|
||||
inner class DeepInner<G1, G2>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nestedTypes() {
|
||||
val nestedGenericType = javaTypeOf<T3.Nested<IntRange>>()
|
||||
parameterized(nestedGenericType) { type ->
|
||||
assertEquals(listOf(IntRange::class.java), type.actualTypeArguments.toList())
|
||||
assertEquals(T3::class.java, type.ownerType)
|
||||
assertEquals("reflection.JavaTypeTest\$T3\$Nested<kotlin.ranges.IntRange>", type.typeName)
|
||||
}
|
||||
assertEqualsAndHashCode(javaTypeOf<T3.Nested<String>>(), javaTypeOf<T3.Nested<String>>())
|
||||
assertNotEquals(javaTypeOf<T3.Nested<String>>(), javaTypeOf<T3.Nested<Int>>())
|
||||
|
||||
|
||||
val innerGenericType = javaTypeOf<T3<String, Unit>.Inner<IntRange>>()
|
||||
parameterized(innerGenericType) { type ->
|
||||
assertEquals(listOf(IntRange::class.java), type.actualTypeArguments.toList())
|
||||
parameterized(type.ownerType) { ownerType ->
|
||||
assertEquals(listOf(String::class.java, Unit::class.java), ownerType.actualTypeArguments.toList())
|
||||
}
|
||||
assertEquals("reflection.JavaTypeTest\$T3<java.lang.String, kotlin.Unit>\$Inner<kotlin.ranges.IntRange>", type.typeName)
|
||||
}
|
||||
assertEqualsAndHashCode(javaTypeOf<T3<String, Unit>.Inner<String>>(), javaTypeOf<T3<String, Unit>.Inner<String>>())
|
||||
assertNotEquals(javaTypeOf<T3<String, Unit>.Inner<String>>(), javaTypeOf<T3<String, Unit>.Inner<Int>>())
|
||||
assertNotEquals(javaTypeOf<T3<String, Unit>.Inner<String>>(), javaTypeOf<T3<String, String>.Inner<String>>())
|
||||
|
||||
|
||||
val deepInnerType = javaTypeOf<T3<Any, Int>.Inner<Char>.NonGeneric.DeepInner<Byte, Short>>()
|
||||
parameterized(deepInnerType) { deepInnerType ->
|
||||
assertEquals(listOf(Byte::class.javaObjectType, Short::class.javaObjectType), deepInnerType.actualTypeArguments.toList())
|
||||
parameterized(deepInnerType.ownerType) { nonGenericType ->
|
||||
assertEquals(emptyList(), nonGenericType.actualTypeArguments.toList())
|
||||
parameterized(nonGenericType.ownerType) { innerType ->
|
||||
assertEquals(listOf(Char::class.javaObjectType), innerType.actualTypeArguments.toList())
|
||||
parameterized(innerType.ownerType) { t3 ->
|
||||
assertEquals(listOf(Any::class.java, Int::class.javaObjectType), t3.actualTypeArguments.toList())
|
||||
assertEquals(JavaTypeTest::class.java, t3.ownerType)
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals(
|
||||
"reflection.JavaTypeTest\$T3<java.lang.Object, java.lang.Integer>\$Inner<java.lang.Character>" +
|
||||
"\$NonGeneric\$DeepInner<java.lang.Byte, java.lang.Short>", deepInnerType.typeName
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class T4<I : Int> {
|
||||
fun setOfI(): Type = javaTypeOf<Set<I>>()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun primitiveUpperBound() {
|
||||
parameterized(T4<Int>().setOfI()) { setOfI ->
|
||||
typeVariable(setOfI.actualTypeArguments.single()) { i ->
|
||||
val bound = i.bounds.single()
|
||||
assertEquals(Int::class.javaObjectType, bound)
|
||||
assertNotEquals(Int::class.javaPrimitiveType, bound)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
private inline fun <reified T> javaTypeOf(): Type =
|
||||
typeOf<T>().javaType
|
||||
|
||||
private fun <@OnlyInputTypes T> assertEqualsAndHashCode(expected: T, actual: T, message: String? = null) {
|
||||
assertEquals(expected, actual, message)
|
||||
assertEquals(actual, expected, message)
|
||||
assertEquals(expected.hashCode(), actual.hashCode(), message)
|
||||
}
|
||||
|
||||
private inline fun <reified T : Type> checkIsType(type: Type, block: (T) -> Unit) {
|
||||
assertTrue(type is T, "Type is not ${T::class.java.simpleName}: $type (${type::class.java.name})")
|
||||
block(type)
|
||||
}
|
||||
|
||||
private val Type.typeName: String
|
||||
// Calling getTypeName() via reflection because the interface method Type.getTypeName has only appeared in JDK 8.
|
||||
get() = (this::class.java.getDeclaredMethod("getTypeName").apply { isAccessible = true })(this) as String
|
||||
|
||||
// A<X>
|
||||
private fun parameterized(type: Type, block: (ParameterizedType) -> Unit): Unit = checkIsType(type, block)
|
||||
|
||||
// ?, ? extends ..., ? super ...
|
||||
private fun wildcard(type: Type, block: (WildcardType) -> Unit): Unit = checkIsType(type, block)
|
||||
|
||||
// Array<A<X>>
|
||||
private fun genericArray(type: Type, block: (GenericArrayType) -> Unit): Unit = checkIsType(type, block)
|
||||
|
||||
// T
|
||||
private fun typeVariable(type: Type, block: (TypeVariable<*>) -> Unit): Unit = checkIsType(type, block)
|
||||
|
||||
// ? extends ...
|
||||
private fun wildcardExtends(type: Type, block: (upperBound: Type) -> Unit) {
|
||||
wildcard(type) { argument ->
|
||||
assertEquals(emptyList(), argument.lowerBounds.toList())
|
||||
val bound = argument.upperBounds.singleOrNull() ?: fail("Type is not an extends-wildcard: $type (${type::class.java.name})")
|
||||
block(bound)
|
||||
}
|
||||
}
|
||||
|
||||
// ? super ...
|
||||
private fun wildcardSuper(type: Type, block: (lowerBound: Type) -> Unit) {
|
||||
wildcard(type) { argument ->
|
||||
assertEquals(listOf(Any::class.java), argument.upperBounds.toList())
|
||||
val bound = argument.lowerBounds.singleOrNull() ?: fail("Type is not a super-wildcard: $type (${type::class.java.name})")
|
||||
block(bound)
|
||||
}
|
||||
}
|
||||
|
||||
// *
|
||||
private fun star(type: Type) {
|
||||
wildcardExtends(type) { bound ->
|
||||
assertEquals(Any::class.java, bound)
|
||||
}
|
||||
assertEquals("?", type.typeName)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user