diff --git a/compiler/testData/codegen/box/reflection/supertypes/isSubclassOfIsSuperclassOf.kt b/compiler/testData/codegen/box/reflection/supertypes/isSubclassOfIsSuperclassOf.kt new file mode 100644 index 00000000000..0553106e8f5 --- /dev/null +++ b/compiler/testData/codegen/box/reflection/supertypes/isSubclassOfIsSuperclassOf.kt @@ -0,0 +1,48 @@ +// WITH_REFLECT + +import kotlin.reflect.* +import kotlin.test.assertTrue +import kotlin.test.assertFalse + +open class Klass +interface Interface +class Bar : Interface, 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::class, Array::class, false) + check(Array::class, Array::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" +} diff --git a/compiler/testData/codegen/box/reflection/supertypes/primitives.kt b/compiler/testData/codegen/box/reflection/supertypes/primitives.kt new file mode 100644 index 00000000000..2d54b7ae8aa --- /dev/null +++ b/compiler/testData/codegen/box/reflection/supertypes/primitives.kt @@ -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" +} diff --git a/compiler/testData/codegen/box/reflection/types/subtyping/platformType.kt b/compiler/testData/codegen/box/reflection/types/subtyping/platformType.kt new file mode 100644 index 00000000000..710576d0edf --- /dev/null +++ b/compiler/testData/codegen/box/reflection/types/subtyping/platformType.kt @@ -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" +} diff --git a/compiler/testData/codegen/box/reflection/types/subtyping/simpleGenericTypes.kt b/compiler/testData/codegen/box/reflection/types/subtyping/simpleGenericTypes.kt new file mode 100644 index 00000000000..d96d14bf85f --- /dev/null +++ b/compiler/testData/codegen/box/reflection/types/subtyping/simpleGenericTypes.kt @@ -0,0 +1,30 @@ +// WITH_REFLECT + +import kotlin.reflect.* +import kotlin.test.assertTrue +import kotlin.test.assertFalse + +open class G +class A : G() + +fun gOfString(): G = null!! +fun gOfInt(): G = 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" +} diff --git a/compiler/testData/codegen/box/reflection/types/subtyping/simpleSubtypeSupertype.kt b/compiler/testData/codegen/box/reflection/types/subtyping/simpleSubtypeSupertype.kt new file mode 100644 index 00000000000..4ce879fd66e --- /dev/null +++ b/compiler/testData/codegen/box/reflection/types/subtyping/simpleSubtypeSupertype.kt @@ -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" +} diff --git a/compiler/testData/codegen/box/reflection/types/subtyping/typeProjection.kt b/compiler/testData/codegen/box/reflection/types/subtyping/typeProjection.kt new file mode 100644 index 00000000000..12aa84a1cfb --- /dev/null +++ b/compiler/testData/codegen/box/reflection/types/subtyping/typeProjection.kt @@ -0,0 +1,41 @@ +// WITH_REFLECT + +import kotlin.reflect.isSubtypeOf +import kotlin.test.assertTrue +import kotlin.test.assertFalse + +class G + +fun number(): G = null!! +fun outNumber(): G = null!! +fun inNumber(): G = 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 <: G + assertTrue(n.isSubtypeOf(o)) + assertFalse(o.isSubtypeOf(n)) + + // G <: G + assertTrue(n.isSubtypeOf(i)) + assertFalse(i.isSubtypeOf(n)) + + // G <: G<*> + assertTrue(n.isSubtypeOf(st)) + assertFalse(st.isSubtypeOf(n)) + + // G <: G<*> + assertTrue(o.isSubtypeOf(st)) + assertFalse(st.isSubtypeOf(o)) + + // G <: G<*> + assertTrue(i.isSubtypeOf(st)) + assertFalse(st.isSubtypeOf(i)) + + return "OK" +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index ed6f54dc820..d7ec15f36d0 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -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); + } + } } } diff --git a/core/reflection.jvm/src/kotlin/reflect/KClasses.kt b/core/reflection.jvm/src/kotlin/reflect/KClasses.kt index fbac678e23e..34442668fc9 100644 --- a/core/reflection.jvm/src/kotlin/reflect/KClasses.kt +++ b/core/reflection.jvm/src/kotlin/reflect/KClasses.kt @@ -223,3 +223,16 @@ val KClass<*>.allSuperclasses: Collection> 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) diff --git a/core/reflection.jvm/src/kotlin/reflect/KTypes.kt b/core/reflection.jvm/src/kotlin/reflect/KTypes.kt index 07de73844c3..0eae7304fb7 100644 --- a/core/reflection.jvm/src/kotlin/reflect/KTypes.kt +++ b/core/reflection.jvm/src/kotlin/reflect/KTypes.kt @@ -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) +}