Reflection: add KClass.{isSubclassOf,isSuperclassOf}, KType.{isSubtypeOf,isSupertypeOf}

The behavior in primitives.kt is likely to be reconsidered

 #KT-8998 Fixed
This commit is contained in:
Alexander Udalov
2016-07-26 18:29:02 +03:00
parent 4cd252d9d5
commit 3702f4a7a7
9 changed files with 308 additions and 0 deletions
@@ -0,0 +1,48 @@
// WITH_REFLECT
import kotlin.reflect.*
import kotlin.test.assertTrue
import kotlin.test.assertFalse
open class Klass
interface Interface<T>
class Bar : Interface<String>, Klass()
fun check(subclass: KClass<*>, superclass: KClass<*>, shouldBeSubclass: Boolean) {
if (shouldBeSubclass) {
assertTrue(subclass.isSubclassOf(superclass))
assertTrue(superclass.isSuperclassOf(subclass))
} else {
assertFalse(subclass.isSubclassOf(superclass))
assertFalse(superclass.isSuperclassOf(subclass))
}
}
fun box(): String {
check(Any::class, Any::class, true)
check(String::class, Any::class, true)
check(Any::class, String::class, false)
check(String::class, String::class, true)
check(Int::class, Int::class, true)
check(Int::class, Any::class, true)
check(List::class, Collection::class, true)
check(List::class, Iterable::class, true)
check(Collection::class, Iterable::class, true)
check(Set::class, List::class, false)
check(Array<String>::class, Array<Any>::class, false)
check(Array<Any>::class, Array<String>::class, false)
check(Function3::class, Function4::class, false)
check(Function4::class, Function3::class, false)
check(Bar::class, Klass::class, true)
check(Bar::class, Interface::class, true)
check(Klass::class, Bar::class, false)
check(Interface::class, Bar::class, false)
check(Klass::class, Interface::class, false)
return "OK"
}
@@ -0,0 +1,20 @@
// WITH_REFLECT
import kotlin.reflect.isSubclassOf
import kotlin.test.assertTrue
import kotlin.test.assertFalse
fun box(): String {
// KClass instances for primitive int and wrapper java.lang.Integer are different
val primitiveInt = Int::class.javaPrimitiveType!!.kotlin
val wrapperInt = Int::class.javaObjectType.kotlin
assertTrue(primitiveInt.isSubclassOf(primitiveInt))
assertTrue(wrapperInt.isSubclassOf(wrapperInt))
// Currently KClass for int != KClass for java.lang.Integer, thus they are not a subclass of each other either
// TODO: reconsider this decision
assertFalse(primitiveInt.isSubclassOf(wrapperInt))
assertFalse(wrapperInt.isSubclassOf(primitiveInt))
return "OK"
}
@@ -0,0 +1,31 @@
// WITH_REFLECT
// FILE: J.java
public interface J {
String platformString();
}
// FILE: K.kt
import kotlin.reflect.*
import kotlin.test.assertTrue
fun string(): String = null!!
fun nullableString(): String? = null!!
fun check(subCallable: KCallable<*>, superCallable: KCallable<*>) {
val subtype = subCallable.returnType
val supertype = superCallable.returnType
assertTrue(subtype.isSubtypeOf(supertype))
assertTrue(supertype.isSupertypeOf(subtype))
}
fun box(): String {
check(::string, J::platformString)
check(J::platformString, ::string)
check(::nullableString, J::platformString)
check(J::platformString, ::nullableString)
check(J::platformString, J::platformString)
return "OK"
}
@@ -0,0 +1,30 @@
// WITH_REFLECT
import kotlin.reflect.*
import kotlin.test.assertTrue
import kotlin.test.assertFalse
open class G<T>
class A : G<String>()
fun gOfString(): G<String> = null!!
fun gOfInt(): G<Int> = null!!
fun box(): String {
val gs = ::gOfString.returnType
val gi = ::gOfInt.returnType
val a = ::A.returnType
assertTrue(a.isSubtypeOf(gs))
assertTrue(gs.isSupertypeOf(a))
assertFalse(a.isSubtypeOf(gi))
assertFalse(gi.isSupertypeOf(a))
assertFalse(gs.isSubtypeOf(gi))
assertFalse(gs.isSupertypeOf(gi))
assertFalse(gi.isSubtypeOf(gs))
assertFalse(gi.isSupertypeOf(gs))
return "OK"
}
@@ -0,0 +1,64 @@
// WITH_REFLECT
import kotlin.reflect.*
import kotlin.test.assertTrue
import kotlin.test.assertFalse
fun check(subCallable: KCallable<*>, superCallable: KCallable<*>, shouldBeSubtype: Boolean) {
val subtype = subCallable.returnType
val supertype = superCallable.returnType
if (shouldBeSubtype) {
assertTrue(subtype.isSubtypeOf(supertype))
assertTrue(supertype.isSupertypeOf(subtype))
} else {
assertFalse(subtype.isSubtypeOf(supertype))
assertFalse(supertype.isSupertypeOf(subtype))
}
}
open class O
class X : O()
fun any(): Any = null!!
fun string(): String = null!!
fun nullableString(): String? = null!!
fun int(): Int = null!!
fun nothing(): Nothing = null!!
fun nullableNothing(): Nothing? = null!!
fun function2(): (Any, Any) -> Any = null!!
fun function3(): (Any, Any, Any) -> Any = null!!
fun box(): String {
check(::any, ::any, true)
check(::int, ::int, true)
check(::nothing, ::nothing, true)
check(::nullableNothing, ::nullableNothing, true)
check(::string, ::any, true)
check(::nullableString, ::any, false)
check(::int, ::any, true)
check(::O, ::any, true)
check(::X, ::any, true)
check(::nothing, ::any, true)
check(::nothing, ::string, true)
check(::nothing, ::nullableString, true)
check(::nullableNothing, ::nullableString, true)
check(::nullableNothing, ::string, false)
check(::string, ::nullableString, true)
check(::nullableString, ::string, false)
check(::X, ::O, true)
check(::O, ::X, false)
check(::int, ::string, false)
check(::string, ::int, false)
check(::any, ::string, false)
check(::any, ::nullableString, false)
check(::function2, ::function3, false)
check(::function3, ::function2, false)
return "OK"
}
@@ -0,0 +1,41 @@
// WITH_REFLECT
import kotlin.reflect.isSubtypeOf
import kotlin.test.assertTrue
import kotlin.test.assertFalse
class G<T>
fun number(): G<Number> = null!!
fun outNumber(): G<out Number> = null!!
fun inNumber(): G<in Number> = null!!
fun star(): G<*> = null!!
fun box(): String {
val n = ::number.returnType
val o = ::outNumber.returnType
val i = ::inNumber.returnType
val st = ::star.returnType
// G<Number> <: G<out Number>
assertTrue(n.isSubtypeOf(o))
assertFalse(o.isSubtypeOf(n))
// G<Number> <: G<in Number>
assertTrue(n.isSubtypeOf(i))
assertFalse(i.isSubtypeOf(n))
// G<Number> <: G<*>
assertTrue(n.isSubtypeOf(st))
assertFalse(st.isSubtypeOf(n))
// G<out Number> <: G<*>
assertTrue(o.isSubtypeOf(st))
assertFalse(st.isSubtypeOf(o))
// G<in Number> <: G<*>
assertTrue(i.isSubtypeOf(st))
assertFalse(st.isSubtypeOf(i))
return "OK"
}
@@ -12993,6 +12993,18 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
doTest(fileName);
}
@TestMetadata("isSubclassOfIsSuperclassOf.kt")
public void testIsSubclassOfIsSuperclassOf() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/supertypes/isSubclassOfIsSuperclassOf.kt");
doTest(fileName);
}
@TestMetadata("primitives.kt")
public void testPrimitives() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/supertypes/primitives.kt");
doTest(fileName);
}
@TestMetadata("simpleSupertypes.kt")
public void testSimpleSupertypes() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/supertypes/simpleSupertypes.kt");
@@ -13139,6 +13151,39 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
doTest(fileName);
}
}
@TestMetadata("compiler/testData/codegen/box/reflection/types/subtyping")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Subtyping extends AbstractBlackBoxCodegenTest {
public void testAllFilesPresentInSubtyping() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/reflection/types/subtyping"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("platformType.kt")
public void testPlatformType() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/subtyping/platformType.kt");
doTest(fileName);
}
@TestMetadata("simpleGenericTypes.kt")
public void testSimpleGenericTypes() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/subtyping/simpleGenericTypes.kt");
doTest(fileName);
}
@TestMetadata("simpleSubtypeSupertype.kt")
public void testSimpleSubtypeSupertype() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/subtyping/simpleSubtypeSupertype.kt");
doTest(fileName);
}
@TestMetadata("typeProjection.kt")
public void testTypeProjection() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/types/subtyping/typeProjection.kt");
doTest(fileName);
}
}
}
}
@@ -223,3 +223,16 @@ val KClass<*>.allSuperclasses: Collection<KClass<*>>
get() = allSupertypes.map { supertype ->
supertype.classifier as? KClass<*> ?: throw KotlinReflectionInternalError("Supertype not a class: $supertype")
}
/**
* Returns `true` if `this` class is the same or is a (possibly indirect) subclass of [base], `false` otherwise.
*/
fun KClass<*>.isSubclassOf(base: KClass<*>): Boolean =
this == base ||
DFS.ifAny(listOf(this), KClass<*>::superclasses) { it == base }
/**
* Returns `true` if `this` class is the same or is a (possibly indirect) superclass of [derived], `false` otherwise.
*/
fun KClass<*>.isSuperclassOf(derived: KClass<*>): Boolean =
derived.isSubclassOf(this)
@@ -19,6 +19,7 @@ package kotlin.reflect
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.isFlexible
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
import kotlin.reflect.jvm.internal.KTypeImpl
/**
@@ -35,3 +36,18 @@ fun KType.withNullability(nullable: Boolean): KType {
return if (!nullable) this else KTypeImpl(TypeUtils.makeNullable(kotlinType)) { javaType }
}
/**
* Returns `true` if `this` type is the same or is a subtype of [other], `false` otherwise.
*/
fun KType.isSubtypeOf(other: KType): Boolean {
return (this as KTypeImpl).type.isSubtypeOf((other as KTypeImpl).type)
}
/**
* Returns `true` if `this` type is the same or is a supertype of [other], `false` otherwise.
*/
fun KType.isSupertypeOf(other: KType): Boolean {
return other.isSubtypeOf(this)
}