[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