[FIR] 6/n FirExpectActualDeclarationChecker: support implicit actualization
diff --git a/compiler/config/src/org/jetbrains/kotlin/config/AnalysisFlags.kt b/compiler/config/src/org/jetbrains/kotlin/config/AnalysisFlags.kt index 61fa806..55443e1 100644 --- a/compiler/config/src/org/jetbrains/kotlin/config/AnalysisFlags.kt +++ b/compiler/config/src/org/jetbrains/kotlin/config/AnalysisFlags.kt
@@ -19,7 +19,7 @@ * that the reason is broken design of `expect`/`actual` KT-64130). * E.g. `ABSTRACT_NOT_IMPLEMENTED` (KT-66205) * - * This flag is true in two case: + * This flag is true in two cases: * 1. Metadata compilation (`:compile*KotlinMetadata` Gradle task) * 2. IDE analysis *
diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirExpectActualDeclarationChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirExpectActualDeclarationChecker.kt index 2b01917..0f884d5 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirExpectActualDeclarationChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirExpectActualDeclarationChecker.kt
@@ -7,6 +7,7 @@ import org.jetbrains.kotlin.KtFakeSourceElementKind import org.jetbrains.kotlin.KtSourceElement +import org.jetbrains.kotlin.config.AnalysisFlags import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.diagnostics.DiagnosticReporter @@ -22,6 +23,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.utils.* +import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol import org.jetbrains.kotlin.fir.symbols.SymbolInternals import org.jetbrains.kotlin.fir.symbols.impl.* @@ -68,6 +70,18 @@ checkExpectDeclarationModifiers(declaration, context, reporter) checkOptInAnnotation(declaration, declaration.symbol, context, reporter, declaration.source) } + if (!context.session.languageVersionSettings.getFlag(AnalysisFlags.metadataCompilation) && + declaration is FirRegularClass && + declaration.isExpect && + declaration.isMarkedAsImplicitlyActualizedByJvmDeclaration(context) + ) { + val reportOn = declaration.getAnnotationByClassId(ImplicitlyActualizedByJvmDeclaration, context.session)?.source + ?: declaration.source + // This works only because FirExpectActualDeclarationChecker is MppCheckerKind.Platform => we can find and check actual for expect + // IDE doesn't support MppCheckerKind.Platform + // We can't check implicit actualization neither in metadata nor in IDE + checkExpectWithImplicitActual(declaration, context, reporter, reportOn) + } val matchingCompatibilityToMembersMap = declaration.symbol.expectForActual.orEmpty() if ((ExpectActualMatchingCompatibility.MatchedSuccessfully in matchingCompatibilityToMembersMap || declaration.hasActualModifier()) && !declaration.isLocalMember // Reduce verbosity. WRONG_MODIFIER_TARGET will be reported anyway. @@ -76,6 +90,56 @@ } } + private fun checkExpectWithImplicitActual( + expect: FirRegularClass, + context: CheckerContext, + reporter: DiagnosticReporter, + reportOn: KtSourceElement?, + ) { + // If it's null NO_ACTUAL_FOR_EXPECT will be reported by IR + val implicitActual = context.session.symbolProvider.getClassLikeSymbolByClassId(expect.classId) ?: return + // If it's expect, then expect declaration found itself, and NO_ACTUAL_FOR_EXPECT will be reported by IR + if (implicitActual.isExpect) return + val expectActualMatchingContext = + context.session.expectActualMatchingContextFactory.create( + context.session, + context.scopeSession, + allowedWritingMemberExpectForActualMapping = true + ) + val compatibility = AbstractExpectActualChecker.getClassifiersCompatibility( + expect.symbol, + implicitActual, + expectActualMatchingContext, + context.languageVersionSettings + ) + when (compatibility) { + ExpectActualCheckingCompatibility.Compatible -> {} // All good. Nothing to do + is ExpectActualCheckingCompatibility.ClassScopes -> { + reportClassScopesIncompatibility( + implicitActual, + expect.symbol, + compatibility, + reporter, + reportOn, + context, + noActualForExpectDiagFactory = FirErrors.IMPLICIT_ACTUAL_NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS + ) + } + is ExpectActualCheckingCompatibility.ClassifiersIncompatible -> { + reporter.reportOn( + reportOn, + FirErrors.IMPLICIT_ACTUAL_IS_INCOMPATIBLE_WITH_EXPECT, + implicitActual, + mapOf(compatibility to listOf(expect.symbol)), + context + ) + } + else -> error("Implicit actualization incorrect incompatibility kind: $compatibility") + } + @OptIn(SymbolInternals::class) + checkOptInAnnotation(implicitActual.fir, expect.symbol, context, reporter, reportOn) + } + private fun containsExpectOrActualModifier(declaration: FirMemberDeclaration): Boolean { return declaration.source.getModifierList()?.let { modifiers -> KtTokens.EXPECT_KEYWORD in modifiers || KtTokens.ACTUAL_KEYWORD in modifiers
diff --git a/compiler/testData/diagnostics/tests/multiplatform/implicitActualization/implicitJavaActualization_classifierIncompatibilities.fir.kt b/compiler/testData/diagnostics/tests/multiplatform/implicitActualization/implicitJavaActualization_classifierIncompatibilities.fir.kt index 4e28200..ff87a6b 100644 --- a/compiler/testData/diagnostics/tests/multiplatform/implicitActualization/implicitJavaActualization_classifierIncompatibilities.fir.kt +++ b/compiler/testData/diagnostics/tests/multiplatform/implicitActualization/implicitJavaActualization_classifierIncompatibilities.fir.kt
@@ -8,19 +8,19 @@ interface I -<!EXPECT_ACTUAL_INCOMPATIBILITY{JVM}!>@ImplicitlyActualizedByJvmDeclaration expect class A<!> -<!EXPECT_ACTUAL_INCOMPATIBILITY{JVM}!>@ImplicitlyActualizedByJvmDeclaration expect value class B(val x: Int)<!> +<!EXPECT_ACTUAL_INCOMPATIBILITY{JVM}!><!IMPLICIT_ACTUAL_IS_INCOMPATIBLE_WITH_EXPECT!>@ImplicitlyActualizedByJvmDeclaration<!> expect class A<!> +<!EXPECT_ACTUAL_INCOMPATIBILITY{JVM}!><!IMPLICIT_ACTUAL_IS_INCOMPATIBLE_WITH_EXPECT!>@ImplicitlyActualizedByJvmDeclaration<!> expect value class B(val x: Int)<!> @ImplicitlyActualizedByJvmDeclaration expect fun interface C1 { fun foo() } -@ImplicitlyActualizedByJvmDeclaration expect fun interface C2 { fun foo() } +<!IMPLICIT_ACTUAL_IS_INCOMPATIBLE_WITH_EXPECT!>@ImplicitlyActualizedByJvmDeclaration<!> expect fun interface C2 { fun foo() } @ImplicitlyActualizedByJvmDeclaration expect class D1 : I -<!EXPECT_ACTUAL_INCOMPATIBILITY{JVM}!>@ImplicitlyActualizedByJvmDeclaration expect class D2 : I<!> +<!EXPECT_ACTUAL_INCOMPATIBILITY{JVM}!><!IMPLICIT_ACTUAL_IS_INCOMPATIBLE_WITH_EXPECT!>@ImplicitlyActualizedByJvmDeclaration<!> expect class D2 : I<!> @ImplicitlyActualizedByJvmDeclaration expect enum class E1 { ONE, TWO } -<!EXPECT_ACTUAL_INCOMPATIBILITY{JVM}!>@ImplicitlyActualizedByJvmDeclaration expect enum class E2 { ONE, <!NO_ACTUAL_FOR_EXPECT{JVM}!>TWO<!> }<!> -<!EXPECT_ACTUAL_INCOMPATIBILITY{JVM}!>@ImplicitlyActualizedByJvmDeclaration expect class Outer { +<!EXPECT_ACTUAL_INCOMPATIBILITY{JVM}!><!IMPLICIT_ACTUAL_IS_INCOMPATIBLE_WITH_EXPECT!>@ImplicitlyActualizedByJvmDeclaration<!> expect enum class E2 { ONE, <!NO_ACTUAL_FOR_EXPECT{JVM}!>TWO<!> }<!> +<!EXPECT_ACTUAL_INCOMPATIBILITY{JVM}!><!IMPLICIT_ACTUAL_NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS!>@ImplicitlyActualizedByJvmDeclaration<!> expect class Outer { class F1 inner class F2 - <!EXPECT_ACTUAL_INCOMPATIBILITY{JVM}!>inner class F3<!> - <!EXPECT_ACTUAL_INCOMPATIBILITY{JVM}!>class F4<!> + <!EXPECT_ACTUAL_INCOMPATIBILITY{JVM}, IMPLICIT_ACTUAL_IS_INCOMPATIBLE_WITH_EXPECT!>inner class F3<!> + <!EXPECT_ACTUAL_INCOMPATIBILITY{JVM}, IMPLICIT_ACTUAL_IS_INCOMPATIBLE_WITH_EXPECT!>class F4<!> }<!> // MODULE: m2-jvm()()(m1-common)
diff --git a/compiler/testData/diagnostics/tests/multiplatform/implicitActualization/implicitJavaActualization_requiresOptIn.fir.kt b/compiler/testData/diagnostics/tests/multiplatform/implicitActualization/implicitJavaActualization_requiresOptIn.fir.kt new file mode 100644 index 0000000..9067b18 --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/implicitActualization/implicitJavaActualization_requiresOptIn.fir.kt
@@ -0,0 +1,15 @@ +// WITH_STDLIB + +// MODULE: m1-common +// FILE: common.kt + +import kotlin.jvm.ImplicitlyActualizedByJvmDeclaration + +@OptIn(ExperimentalMultiplatform::class) +@ImplicitlyActualizedByJvmDeclaration +expect annotation class Foo + +// MODULE: m2-jvm()()(m1-common) +// FILE: Foo.java +@kotlin.RequiresOptIn +public @interface Foo {}
diff --git a/compiler/testData/diagnostics/tests/multiplatform/implicitActualization/implicitJavaActualization_requiresOptIn.kt b/compiler/testData/diagnostics/tests/multiplatform/implicitActualization/implicitJavaActualization_requiresOptIn.kt index 945b5ff..9067b18 100644 --- a/compiler/testData/diagnostics/tests/multiplatform/implicitActualization/implicitJavaActualization_requiresOptIn.kt +++ b/compiler/testData/diagnostics/tests/multiplatform/implicitActualization/implicitJavaActualization_requiresOptIn.kt
@@ -1,4 +1,3 @@ -// FIR_IDENTICAL // WITH_STDLIB // MODULE: m1-common
diff --git a/compiler/testData/diagnostics/tests/multiplatform/implicitActualization/implicitJavaActualization_requiresOptIn.ll.kt b/compiler/testData/diagnostics/tests/multiplatform/implicitActualization/implicitJavaActualization_requiresOptIn.ll.kt new file mode 100644 index 0000000..9067b18 --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/implicitActualization/implicitJavaActualization_requiresOptIn.ll.kt
@@ -0,0 +1,15 @@ +// WITH_STDLIB + +// MODULE: m1-common +// FILE: common.kt + +import kotlin.jvm.ImplicitlyActualizedByJvmDeclaration + +@OptIn(ExperimentalMultiplatform::class) +@ImplicitlyActualizedByJvmDeclaration +expect annotation class Foo + +// MODULE: m2-jvm()()(m1-common) +// FILE: Foo.java +@kotlin.RequiresOptIn +public @interface Foo {}
diff --git a/compiler/testData/diagnostics/tests/multiplatform/implicitActualization/implicitKotlinActualization_defaultParamsInActual.fir.kt b/compiler/testData/diagnostics/tests/multiplatform/implicitActualization/implicitKotlinActualization_defaultParamsInActual.fir.kt index 2bfa903..37ef43d 100644 --- a/compiler/testData/diagnostics/tests/multiplatform/implicitActualization/implicitKotlinActualization_defaultParamsInActual.fir.kt +++ b/compiler/testData/diagnostics/tests/multiplatform/implicitActualization/implicitKotlinActualization_defaultParamsInActual.fir.kt
@@ -6,7 +6,7 @@ import kotlin.jvm.ImplicitlyActualizedByJvmDeclaration @OptIn(ExperimentalMultiplatform::class) -@ImplicitlyActualizedByJvmDeclaration +<!IMPLICIT_ACTUAL_NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS!>@ImplicitlyActualizedByJvmDeclaration<!> expect class Foo { fun foo(a: Int) }
diff --git a/compiler/testData/diagnostics/tests/multiplatform/implicitActualization/implicitKotlinActualization_defaultParamsInActual.ll.kt b/compiler/testData/diagnostics/tests/multiplatform/implicitActualization/implicitKotlinActualization_defaultParamsInActual.ll.kt new file mode 100644 index 0000000..2bfa903 --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/implicitActualization/implicitKotlinActualization_defaultParamsInActual.ll.kt
@@ -0,0 +1,21 @@ +// WITH_STDLIB + +// MODULE: m1-common +// FILE: common.kt + +import kotlin.jvm.ImplicitlyActualizedByJvmDeclaration + +@OptIn(ExperimentalMultiplatform::class) +@ImplicitlyActualizedByJvmDeclaration +expect class Foo { + fun foo(a: Int) +} + +// MODULE: lib()()() +// FILE: lib.kt +class Foo { + fun foo(a: Int = 1) {} +} + +// MODULE: m2-jvm(lib)()(m1-common) +// FILE: jvm.kt