[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