From 7897bb6adbb30947dc1b9d7803c57c09d5addef1 Mon Sep 17 00:00:00 2001 From: Dmitriy Novozhilov Date: Thu, 19 Nov 2020 17:48:25 +0300 Subject: [PATCH] [FE] Support sealed classes and interfaces from java KT-43551 KT-41215 --- .../javaSealedClassExhaustiveness.kt | 44 +++++++++++++ .../javaSealedClassExhaustiveness.txt | 41 ++++++++++++ .../javaSealedInterfaceExhaustiveness.kt | 64 +++++++++++++++++++ .../javaSealedInterfaceExhaustiveness.txt | 60 +++++++++++++++++ .../DiagnosticsWithJdk15TestGenerated.java | 33 ++++++++++ .../jetbrains/kotlin/descriptors/Modality.kt | 13 ++-- .../descriptors/LazyJavaClassDescriptor.kt | 11 +++- .../descriptors/LazyJavaClassMemberScope.kt | 2 +- .../java/lazy/descriptors/LazyJavaScope.kt | 2 +- 9 files changed, 260 insertions(+), 10 deletions(-) create mode 100644 compiler/testData/diagnostics/testsWithJava15/sealedClasses/javaSealedClassExhaustiveness.kt create mode 100644 compiler/testData/diagnostics/testsWithJava15/sealedClasses/javaSealedClassExhaustiveness.txt create mode 100644 compiler/testData/diagnostics/testsWithJava15/sealedClasses/javaSealedInterfaceExhaustiveness.kt create mode 100644 compiler/testData/diagnostics/testsWithJava15/sealedClasses/javaSealedInterfaceExhaustiveness.txt diff --git a/compiler/testData/diagnostics/testsWithJava15/sealedClasses/javaSealedClassExhaustiveness.kt b/compiler/testData/diagnostics/testsWithJava15/sealedClasses/javaSealedClassExhaustiveness.kt new file mode 100644 index 00000000000..0ae9771eb1f --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJava15/sealedClasses/javaSealedClassExhaustiveness.kt @@ -0,0 +1,44 @@ +// !DIAGNOSTICS: -UNUSED_VARIABLE +// ISSUE: KT-41215, KT-43551 + +// FILE: Base.java +public sealed class Base permits A, B {} + +// FILE: A.java +public final class A extends Base {} + +// FILE: B.java +public sealed class B extends Base permits B.C, B.D { + public static final class C implements B {} + + public static non-sealed class D extends B {} +} + +// FILE: main.kt +fun test_ok_1(base: Base) { + val x = when (base) { + is A -> 1 + is B -> 2 + } +} + +fun test_ok_2(base: Base) { + val x = when (base) { + is A -> 1 + is B.C -> 2 + is B.D -> 3 + } +} + +fun test_error_1(base: Base) { + val x = when (base) { + is A -> 1 + } +} + +fun test_error_2(base: Base) { + val x = when (base) { + is A -> 1 + is B.C -> 2 + } +} diff --git a/compiler/testData/diagnostics/testsWithJava15/sealedClasses/javaSealedClassExhaustiveness.txt b/compiler/testData/diagnostics/testsWithJava15/sealedClasses/javaSealedClassExhaustiveness.txt new file mode 100644 index 00000000000..8d90f4c6fcf --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJava15/sealedClasses/javaSealedClassExhaustiveness.txt @@ -0,0 +1,41 @@ +package + +public fun test_error_1(/*0*/ base: Base): kotlin.Unit +public fun test_error_2(/*0*/ base: Base): kotlin.Unit +public fun test_ok_1(/*0*/ base: Base): kotlin.Unit +public fun test_ok_2(/*0*/ base: Base): kotlin.Unit + +public final class A : Base { + public constructor A() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public sealed class B : Base { + public constructor B() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public final class C : B { + public constructor C() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public open class D : B { + public constructor D() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } +} + +public sealed class Base { + public constructor Base() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/testsWithJava15/sealedClasses/javaSealedInterfaceExhaustiveness.kt b/compiler/testData/diagnostics/testsWithJava15/sealedClasses/javaSealedInterfaceExhaustiveness.kt new file mode 100644 index 00000000000..3419f18de62 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJava15/sealedClasses/javaSealedInterfaceExhaustiveness.kt @@ -0,0 +1,64 @@ +// !DIAGNOSTICS: -UNUSED_VARIABLE +// ISSUE: KT-41215, KT-43551 + +// FILE: Base.java +public sealed interface Base permits A, B, E {} + +// FILE: A.java +public non-sealed interface A extends Base {} + +// FILE: B.java +public sealed interface B extends Base permits B.C, B.D { + public static final class C implements B {} + + public static non-sealed interface D extends B {} +} + +// FILE: E.java +public enum E implements Base { + First, Second +} + +// FILE: main.kt +fun test_ok_1(base: Base) { + val x = when (base) { + is A -> 1 + is B -> 2 + is E -> 3 + } +} + +fun test_ok_2(base: Base) { + val x = when (base) { + is A -> 1 + is B.C -> 2 + is B.D -> 3 + E.First -> 4 + E.Second -> 5 + } +} + +fun test_error_1(base: Base) { + val x = when (base) { + is A -> 1 + is B -> 2 + } +} + +fun test_error_2(base: Base) { + val x = when (base) { + is A -> 1 + is B.C -> 2 + is B.D -> 3 + E.Second -> 5 + } +} + +fun test_error_3(base: Base) { + val x = when (base) { + is A -> 1 + is B.C -> 2 + E.First -> 4 + E.Second -> 5 + } +} diff --git a/compiler/testData/diagnostics/testsWithJava15/sealedClasses/javaSealedInterfaceExhaustiveness.txt b/compiler/testData/diagnostics/testsWithJava15/sealedClasses/javaSealedInterfaceExhaustiveness.txt new file mode 100644 index 00000000000..6b88bec8277 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJava15/sealedClasses/javaSealedInterfaceExhaustiveness.txt @@ -0,0 +1,60 @@ +package + +public fun test_error_1(/*0*/ base: Base): kotlin.Unit +public fun test_error_2(/*0*/ base: Base): kotlin.Unit +public fun test_error_3(/*0*/ base: Base): kotlin.Unit +public fun test_ok_1(/*0*/ base: Base): kotlin.Unit +public fun test_ok_2(/*0*/ base: Base): kotlin.Unit + +public interface A : Base { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public sealed interface B : Base { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public final class C : B { + public constructor C() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public interface D : B { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } +} + +public sealed interface Base { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final enum class E : kotlin.Enum, Base { + enum entry First + + enum entry Second + + public constructor E() + public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: E!): kotlin.Int + @kotlin.Deprecated(level = DeprecationLevel.WARNING, message = "This member is not fully supported by Kotlin compiler, so it may be absent or have different signature in next major version", replaceWith = kotlin.ReplaceWith(expression = "", imports = {})) public final override /*1*/ /*fake_override*/ fun describeConstable(): java.util.Optional!>! + public final override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + protected/*protected and package*/ final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun finalize(): kotlin.Unit + public final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun getDeclaringClass(): java.lang.Class! + public final override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): E + public final /*synthesized*/ fun values(): kotlin.Array +} diff --git a/compiler/tests-gen/org/jetbrains/kotlin/checkers/DiagnosticsWithJdk15TestGenerated.java b/compiler/tests-gen/org/jetbrains/kotlin/checkers/DiagnosticsWithJdk15TestGenerated.java index 8d5993043e9..1a4c5ea600f 100644 --- a/compiler/tests-gen/org/jetbrains/kotlin/checkers/DiagnosticsWithJdk15TestGenerated.java +++ b/compiler/tests-gen/org/jetbrains/kotlin/checkers/DiagnosticsWithJdk15TestGenerated.java @@ -70,4 +70,37 @@ public class DiagnosticsWithJdk15TestGenerated extends AbstractDiagnosticsWithJd runTest("compiler/testData/diagnostics/testsWithJava15/jvmRecord/supertypesCheck.kt"); } } + + @TestMetadata("compiler/testData/diagnostics/testsWithJava15/sealedClasses") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class SealedClasses extends AbstractDiagnosticsWithJdk15Test { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInSealedClasses() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/testsWithJava15/sealedClasses"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @TestMetadata("javaSealedClassExhaustiveness.kt") + public void testJavaSealedClassExhaustiveness() throws Exception { + runTest("compiler/testData/diagnostics/testsWithJava15/sealedClasses/javaSealedClassExhaustiveness.kt"); + } + + @TestMetadata("javaSealedInterfaceExhaustiveness.kt") + public void testJavaSealedInterfaceExhaustiveness() throws Exception { + runTest("compiler/testData/diagnostics/testsWithJava15/sealedClasses/javaSealedInterfaceExhaustiveness.kt"); + } + + @TestMetadata("kotlinInheritsJavaClass.kt") + public void testKotlinInheritsJavaClass() throws Exception { + runTest("compiler/testData/diagnostics/testsWithJava15/sealedClasses/kotlinInheritsJavaClass.kt"); + } + + @TestMetadata("kotlinInheritsJavaInterface.kt") + public void testKotlinInheritsJavaInterface() throws Exception { + runTest("compiler/testData/diagnostics/testsWithJava15/sealedClasses/kotlinInheritsJavaInterface.kt"); + } + } } diff --git a/core/compiler.common/src/org/jetbrains/kotlin/descriptors/Modality.kt b/core/compiler.common/src/org/jetbrains/kotlin/descriptors/Modality.kt index 0b91487dfa4..eb5c8bbbd3f 100644 --- a/core/compiler.common/src/org/jetbrains/kotlin/descriptors/Modality.kt +++ b/core/compiler.common/src/org/jetbrains/kotlin/descriptors/Modality.kt @@ -15,12 +15,13 @@ enum class Modality { ABSTRACT; companion object { - - // NB: never returns SEALED - fun convertFromFlags(abstract: Boolean, open: Boolean): Modality { - if (abstract) return ABSTRACT - if (open) return OPEN - return FINAL + fun convertFromFlags(sealed: Boolean, abstract: Boolean, open: Boolean): Modality { + return when { + sealed -> SEALED + abstract -> ABSTRACT + open -> OPEN + else -> FINAL + } } } } diff --git a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaClassDescriptor.kt b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaClassDescriptor.kt index d352a0a4a47..42688fbb51f 100644 --- a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaClassDescriptor.kt +++ b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaClassDescriptor.kt @@ -76,7 +76,7 @@ class LazyJavaClassDescriptor( private val modality = if (jClass.isAnnotationType || jClass.isEnum) Modality.FINAL - else Modality.convertFromFlags(jClass.isAbstract || jClass.isInterface, !jClass.isFinal) + else Modality.convertFromFlags(jClass.isSealed, jClass.isAbstract || jClass.isInterface, !jClass.isFinal) private val visibility = jClass.visibility private val isInner = jClass.outerClass != null && !jClass.isStatic @@ -180,7 +180,14 @@ class LazyJavaClassDescriptor( fun wasScopeContentRequested() = getUnsubstitutedMemberScope().wasContentRequested() || staticScope.wasContentRequested() - override fun getSealedSubclasses(): Collection = emptyList() + override fun getSealedSubclasses(): Collection = if (modality == Modality.SEALED) { + val attributes = TypeUsage.COMMON.toAttributes() + jClass.permittedTypes.mapNotNull { + c.typeResolver.transformJavaType(it, attributes).constructor.declarationDescriptor as? ClassDescriptor + } + } else { + emptyList() + } override fun toString() = "Lazy Java class ${this.fqNameUnsafe}" diff --git a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaClassMemberScope.kt b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaClassMemberScope.kt index 7a0399d71b1..4cef902b5ee 100644 --- a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaClassMemberScope.kt +++ b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaClassMemberScope.kt @@ -515,7 +515,7 @@ class LazyJavaClassMemberScope( returnType, // Those functions are generated as open in bytecode // Actually, it should not be important because the class is final anyway, but leaving them open is convenient for consistency - Modality.convertFromFlags(abstract = false, open = true), + Modality.convertFromFlags(sealed = false, abstract = false, open = true), DescriptorVisibilities.PUBLIC, null, ) diff --git a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaScope.kt b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaScope.kt index 8642044363f..e38a6004ec2 100644 --- a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaScope.kt +++ b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaScope.kt @@ -181,7 +181,7 @@ abstract class LazyJavaScope( effectiveSignature.typeParameters, effectiveSignature.valueParameters, effectiveSignature.returnType, - Modality.convertFromFlags(method.isAbstract, !method.isFinal), + Modality.convertFromFlags(sealed = false, method.isAbstract, !method.isFinal), method.visibility.toDescriptorVisibility(), if (effectiveSignature.receiverType != null) mapOf(JavaMethodDescriptor.ORIGINAL_VALUE_PARAMETER_FOR_EXTENSION_RECEIVER to valueParameters.descriptors.first())