From ccb74b64778c8f6527f93ef77e6ab7c441406304 Mon Sep 17 00:00:00 2001 From: Dmitriy Novozhilov Date: Tue, 15 Feb 2022 12:45:44 +0300 Subject: [PATCH] [FIR] Fix computing compatibility of expect and actual modality --- ...CompilerTestFE10TestdataTestGenerated.java | 12 ++++++ ...irOldFrontendDiagnosticsTestGenerated.java | 12 ++++++ ...DiagnosticsWithLightTreeTestGenerated.java | 12 ++++++ .../declaration/FirExpectActualResolver.kt | 42 ++++++++++++++---- .../expectObjectWithAbstractMember.kt | 18 ++++++++ .../expectObjectWithAbstractMember.txt | 43 +++++++++++++++++++ .../equalsOverrideInActualInterface.kt | 8 ++++ .../equalsOverrideInActualInterface.txt | 18 ++++++++ .../test/runners/DiagnosticTestGenerated.java | 12 ++++++ .../org/jetbrains/kotlin/utils/addToStdlib.kt | 3 ++ 10 files changed, 172 insertions(+), 8 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/multiplatform/expectObjectWithAbstractMember.kt create mode 100644 compiler/testData/diagnostics/tests/multiplatform/expectObjectWithAbstractMember.txt create mode 100644 compiler/testData/diagnostics/tests/multiplatform/headerClass/equalsOverrideInActualInterface.kt create mode 100644 compiler/testData/diagnostics/tests/multiplatform/headerClass/equalsOverrideInActualInterface.txt diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java index 8eba5aa99d0..d85f0b3a140 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java @@ -20000,6 +20000,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag runTest("compiler/testData/diagnostics/tests/multiplatform/expectInterfaceApplicability.kt"); } + @Test + @TestMetadata("expectObjectWithAbstractMember.kt") + public void testExpectObjectWithAbstractMember() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/expectObjectWithAbstractMember.kt"); + } + @Test @TestMetadata("headerFunInNonHeaderClass.kt") public void testHeaderFunInNonHeaderClass() throws Exception { @@ -20311,6 +20317,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag runTest("compiler/testData/diagnostics/tests/multiplatform/headerClass/dontOverrideMethodsFromInterfaceInCommonCode.kt"); } + @Test + @TestMetadata("equalsOverrideInActualInterface.kt") + public void testEqualsOverrideInActualInterface() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/headerClass/equalsOverrideInActualInterface.kt"); + } + @Test @TestMetadata("expectClassWithExplicitAbstractMember.kt") public void testExpectClassWithExplicitAbstractMember() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java index da3ba586654..66c610ab385 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java @@ -20000,6 +20000,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti runTest("compiler/testData/diagnostics/tests/multiplatform/expectInterfaceApplicability.kt"); } + @Test + @TestMetadata("expectObjectWithAbstractMember.kt") + public void testExpectObjectWithAbstractMember() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/expectObjectWithAbstractMember.kt"); + } + @Test @TestMetadata("headerFunInNonHeaderClass.kt") public void testHeaderFunInNonHeaderClass() throws Exception { @@ -20311,6 +20317,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti runTest("compiler/testData/diagnostics/tests/multiplatform/headerClass/dontOverrideMethodsFromInterfaceInCommonCode.kt"); } + @Test + @TestMetadata("equalsOverrideInActualInterface.kt") + public void testEqualsOverrideInActualInterface() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/headerClass/equalsOverrideInActualInterface.kt"); + } + @Test @TestMetadata("expectClassWithExplicitAbstractMember.kt") public void testExpectClassWithExplicitAbstractMember() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java index efacdc1e45b..c80b7050b72 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java @@ -20000,6 +20000,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac runTest("compiler/testData/diagnostics/tests/multiplatform/expectInterfaceApplicability.kt"); } + @Test + @TestMetadata("expectObjectWithAbstractMember.kt") + public void testExpectObjectWithAbstractMember() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/expectObjectWithAbstractMember.kt"); + } + @Test @TestMetadata("headerFunInNonHeaderClass.kt") public void testHeaderFunInNonHeaderClass() throws Exception { @@ -20311,6 +20317,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac runTest("compiler/testData/diagnostics/tests/multiplatform/headerClass/dontOverrideMethodsFromInterfaceInCommonCode.kt"); } + @Test + @TestMetadata("equalsOverrideInActualInterface.kt") + public void testEqualsOverrideInActualInterface() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/headerClass/equalsOverrideInActualInterface.kt"); + } + @Test @TestMetadata("expectClassWithExplicitAbstractMember.kt") public void testExpectClassWithExplicitAbstractMember() throws Exception { diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirExpectActualResolver.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirExpectActualResolver.kt index 5720182c4b6..0d0bfb72768 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirExpectActualResolver.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirExpectActualResolver.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.analysis.checkers.declaration import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.descriptors.Modality.* import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.analysis.checkers.collectEnumEntries @@ -31,7 +32,10 @@ import org.jetbrains.kotlin.types.AbstractTypeChecker import org.jetbrains.kotlin.types.model.TypeConstructorMarker import org.jetbrains.kotlin.utils.SmartList import org.jetbrains.kotlin.utils.addIfNotNull +import org.jetbrains.kotlin.utils.addToStdlib.enumMapOf +import org.jetbrains.kotlin.utils.addToStdlib.enumSetOf import org.jetbrains.kotlin.utils.keysToMap +import java.util.* object FirExpectActualResolver { @OptIn(ExperimentalStdlibApi::class) @@ -330,11 +334,13 @@ object FirExpectActualResolver { !areCompatibleModalities( expectDeclaration.modality, actualDeclaration.modality, - (expectDeclaration.dispatchReceiverType?.toSymbol(expectSession) as? FirRegularClassSymbol)?.modality, + expectContainingClass?.modality, actualContainingClass?.modality ) ) { - return ExpectActualCompatibility.Incompatible.Modality + if (expectDeclaration.dispatchReceiverType?.isAny != true) { + return ExpectActualCompatibility.Incompatible.Modality + } } if (!areDeclarationsWithCompatibleVisibilities(expectDeclaration.resolvedStatus, actualDeclaration.resolvedStatus)) { @@ -460,19 +466,39 @@ object FirExpectActualResolver { expectContainingClassModality: Modality? = null, actualContainingClassModality: Modality? = null ): Boolean { - val result = (expectModality == actualModality) || - (expectModality == Modality.FINAL && (actualModality == Modality.OPEN || actualModality == Modality.ABSTRACT)) - if (result) return true - if (expectContainingClassModality == null || actualContainingClassModality == null) return result - return expectModality == expectContainingClassModality && actualModality == actualContainingClassModality + val expectEffectiveModality = effectiveModality(expectModality, expectContainingClassModality) + val actualEffectiveModality = effectiveModality(actualModality, actualContainingClassModality) + + val result = actualEffectiveModality in compatibleModalityMap.getValue(expectEffectiveModality) + return result } + /* + * If containing class is final then all declarations in it effectively final + */ + private fun effectiveModality(declarationModality: Modality?, containingClassModality: Modality?): Modality? { + return when (containingClassModality) { + FINAL -> FINAL + else -> declarationModality + } + } + + /* + * Key is expect modality, value is a set of compatible actual modalities + */ + private val compatibleModalityMap: EnumMap> = enumMapOf( + ABSTRACT to enumSetOf(ABSTRACT, OPEN), + OPEN to enumSetOf(OPEN), + FINAL to enumSetOf(ABSTRACT, OPEN, FINAL), + SEALED to enumSetOf(SEALED), + ) + private fun areDeclarationsWithCompatibleVisibilities( expectStatus: FirDeclarationStatus, actualStatus: FirDeclarationStatus ): Boolean { val compare = Visibilities.compare(expectStatus.visibility, actualStatus.visibility) - return if (expectStatus.modality != Modality.FINAL) { + return if (expectStatus.modality != FINAL) { // For overridable declarations visibility should match precisely, see KT-19664 compare == 0 } else { diff --git a/compiler/testData/diagnostics/tests/multiplatform/expectObjectWithAbstractMember.kt b/compiler/testData/diagnostics/tests/multiplatform/expectObjectWithAbstractMember.kt new file mode 100644 index 00000000000..b538efc0209 --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/expectObjectWithAbstractMember.kt @@ -0,0 +1,18 @@ +// FIR_IDENTICAL +// MODULE: m1-common +// FILE: common.kt + +interface Base { + fun foo() +} + +expect object Implementation : Base + +// MODULE: m1-jvm()()(m1-common) +// FILE: jvm.kt + +abstract class RealImplementation : Base { + override fun foo() {} +} + +actual object Implementation : RealImplementation(), Base diff --git a/compiler/testData/diagnostics/tests/multiplatform/expectObjectWithAbstractMember.txt b/compiler/testData/diagnostics/tests/multiplatform/expectObjectWithAbstractMember.txt new file mode 100644 index 00000000000..ce5cb5a3e23 --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/expectObjectWithAbstractMember.txt @@ -0,0 +1,43 @@ +// -- Module: -- +package + +public interface Base { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public abstract fun foo(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public expect object Implementation : Base { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun foo(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +// -- Module: -- +package + +public interface Base { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public abstract fun foo(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public actual object Implementation : RealImplementation, Base { + private constructor Implementation() + public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*2*/ /*fake_override*/ fun foo(): kotlin.Unit + public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String +} + +public abstract class RealImplementation : Base { + public constructor RealImplementation() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ fun foo(): kotlin.Unit + 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/tests/multiplatform/headerClass/equalsOverrideInActualInterface.kt b/compiler/testData/diagnostics/tests/multiplatform/headerClass/equalsOverrideInActualInterface.kt new file mode 100644 index 00000000000..c9595e3fb5e --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/headerClass/equalsOverrideInActualInterface.kt @@ -0,0 +1,8 @@ +// FIR_IDENTICAL +// MODULE: m1-common +expect interface Base + +// MODULE: m1-jvm()()(m1-common) +actual interface Base { + override fun equals(other: Any?): Boolean +} diff --git a/compiler/testData/diagnostics/tests/multiplatform/headerClass/equalsOverrideInActualInterface.txt b/compiler/testData/diagnostics/tests/multiplatform/headerClass/equalsOverrideInActualInterface.txt new file mode 100644 index 00000000000..c9448252166 --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/headerClass/equalsOverrideInActualInterface.txt @@ -0,0 +1,18 @@ +// -- Module: -- +package + +public expect 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 +} + +// -- Module: -- +package + +public actual interface Base { + public abstract override /*1*/ 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/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java index 25c2d472be0..fbcc66f7f9e 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java @@ -20006,6 +20006,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/tests/multiplatform/expectInterfaceApplicability.kt"); } + @Test + @TestMetadata("expectObjectWithAbstractMember.kt") + public void testExpectObjectWithAbstractMember() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/expectObjectWithAbstractMember.kt"); + } + @Test @TestMetadata("headerFunInNonHeaderClass.kt") public void testHeaderFunInNonHeaderClass() throws Exception { @@ -20317,6 +20323,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/tests/multiplatform/headerClass/dontOverrideMethodsFromInterfaceInCommonCode.kt"); } + @Test + @TestMetadata("equalsOverrideInActualInterface.kt") + public void testEqualsOverrideInActualInterface() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/headerClass/equalsOverrideInActualInterface.kt"); + } + @Test @TestMetadata("expectClassWithExplicitAbstractMember.kt") public void testExpectClassWithExplicitAbstractMember() throws Exception { diff --git a/core/util.runtime/src/org/jetbrains/kotlin/utils/addToStdlib.kt b/core/util.runtime/src/org/jetbrains/kotlin/utils/addToStdlib.kt index 7c7542ca87e..7f11f596933 100644 --- a/core/util.runtime/src/org/jetbrains/kotlin/utils/addToStdlib.kt +++ b/core/util.runtime/src/org/jetbrains/kotlin/utils/addToStdlib.kt @@ -256,3 +256,6 @@ inline fun List.flatGroupBy( } fun MutableList.popLast(): E = removeAt(lastIndex) + +fun , V> enumMapOf(vararg pairs: Pair): EnumMap = EnumMap(mapOf(*pairs)) +fun > enumSetOf(element: T, vararg elements: T): EnumSet = EnumSet.of(element, *elements)