[FE 1.0] Prohibit implicit Java actualization in K1

^KT-58545 Fixed
Review: https://jetbrains.team/p/kt/reviews/10561

It's not yet supported in K2 KT-59213

Related tests:
- ApiTest.testStdlib
- RuntimePublicAPITest.kotlinStdlibRuntimeMerged
- KotlinProjectViewTestGenerated.test_Arrays (in Kotlin plugin)
This commit is contained in:
Nikita Bobko
2023-06-02 18:06:11 +02:00
parent d0452d569f
commit f4ba5aaf9a
18 changed files with 268 additions and 5 deletions
@@ -878,6 +878,24 @@ public class FirOldFrontendMPPDiagnosticsWithLightTreeTestGenerated extends Abst
runTest("compiler/testData/diagnostics/tests/multiplatform/java/flexibleTypes.kt");
}
@Test
@TestMetadata("implicitJavaActualizationAllowed.kt")
public void testImplicitJavaActualizationAllowed() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/java/implicitJavaActualizationAllowed.kt");
}
@Test
@TestMetadata("implicitJavaActualizationDisallowed.kt")
public void testImplicitJavaActualizationDisallowed() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/java/implicitJavaActualizationDisallowed.kt");
}
@Test
@TestMetadata("implicitJavaActualization_multipleActuals.kt")
public void testImplicitJavaActualization_multipleActuals() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/java/implicitJavaActualization_multipleActuals.kt");
}
@Test
@TestMetadata("inheritedJavaMembers.kt")
public void testInheritedJavaMembers() throws Exception {
@@ -878,6 +878,24 @@ public class FirOldFrontendMPPDiagnosticsWithPsiTestGenerated extends AbstractFi
runTest("compiler/testData/diagnostics/tests/multiplatform/java/flexibleTypes.kt");
}
@Test
@TestMetadata("implicitJavaActualizationAllowed.kt")
public void testImplicitJavaActualizationAllowed() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/java/implicitJavaActualizationAllowed.kt");
}
@Test
@TestMetadata("implicitJavaActualizationDisallowed.kt")
public void testImplicitJavaActualizationDisallowed() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/java/implicitJavaActualizationDisallowed.kt");
}
@Test
@TestMetadata("implicitJavaActualization_multipleActuals.kt")
public void testImplicitJavaActualization_multipleActuals() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/java/implicitJavaActualization_multipleActuals.kt");
}
@Test
@TestMetadata("inheritedJavaMembers.kt")
public void testInheritedJavaMembers() throws Exception {
@@ -817,6 +817,8 @@ public interface Errors {
DiagnosticFactory3<KtNamedDeclaration, MemberDescriptor, ModuleDescriptor,
Map<Incompatible<MemberDescriptor>, Collection<MemberDescriptor>>> NO_ACTUAL_FOR_EXPECT =
DiagnosticFactory3.create(ERROR, INCOMPATIBLE_DECLARATION);
DiagnosticFactory2<KtNamedDeclaration, MemberDescriptor, ModuleDescriptor> IMPLICIT_JVM_ACTUALIZATION =
DiagnosticFactory2.create(ERROR, INCOMPATIBLE_DECLARATION);
DiagnosticFactory2<KtNamedDeclaration, MemberDescriptor,
Map<Incompatible<MemberDescriptor>, Collection<MemberDescriptor>>> ACTUAL_WITHOUT_EXPECT =
DiagnosticFactory2.create(ERROR, INCOMPATIBLE_DECLARATION);
@@ -374,6 +374,10 @@ public class DefaultErrorMessages {
MAP.put(NO_ACTUAL_FOR_EXPECT, "Expected {0} has no actual declaration in module {1}{2}", DECLARATION_NAME_WITH_KIND,
MODULE_WITH_PLATFORM, adaptGenerics1(PlatformIncompatibilityDiagnosticRenderer.TEXT));
MAP.put(IMPLICIT_JVM_ACTUALIZATION, "Expected {0} is implicitly actualized by Java declaration in module {1}. " +
"Please migrate to explicit ''actual typealias''. See: https://youtrack.jetbrains.com/issue/KT-58545",
DECLARATION_NAME_WITH_KIND,
MODULE_WITH_PLATFORM);
MAP.put(ACTUAL_WITHOUT_EXPECT, "{0} has no corresponding expected declaration{1}", CAPITALIZED_DECLARATION_NAME_WITH_KIND_AND_PLATFORM,
adaptGenerics1(PlatformIncompatibilityDiagnosticRenderer.TEXT));
MAP.put(AMBIGUOUS_ACTUALS, "{0} has several compatible actual declarations in modules {1}", CAPITALIZED_DECLARATION_NAME_WITH_KIND_AND_PLATFORM, CommonRenderers.commaSeparated(
@@ -23,6 +23,8 @@ import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.incremental.components.ExpectActualTracker
import org.jetbrains.kotlin.mpp.MppJavaImplicitActualizatorMarker
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier
import org.jetbrains.kotlin.resolve.*
@@ -40,6 +42,8 @@ import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.utils.addToStdlib.cast
import java.io.File
private val implicitlyActualizedAnnotationFqn = FqName("kotlin.jvm.ImplicitlyActualizedByJvmDeclaration")
class ExpectedActualDeclarationChecker(
val moduleStructureOracle: ModuleStructureOracle,
val argumentExtractors: Iterable<ActualAnnotationArgumentExtractor>
@@ -64,7 +68,7 @@ class ExpectedActualDeclarationChecker(
if (descriptor.isExpect) {
checkExpectedDeclarationHasProperActuals(
declaration, descriptor, context.trace,
checkActualModifier, context.expectActualTracker
checkActualModifier, context
)
checkOptInAnnotation(declaration, descriptor, descriptor, context.trace)
}
@@ -93,7 +97,7 @@ class ExpectedActualDeclarationChecker(
descriptor: MemberDescriptor,
trace: BindingTrace,
checkActualModifier: Boolean,
expectActualTracker: ExpectActualTracker
context: DeclarationCheckerContext
) {
val allActualizationPaths = moduleStructureOracle.findAllReversedDependsOnPaths(descriptor.module)
val allLeafModules = allActualizationPaths.map { it.nodes.last() }.toSet()
@@ -102,15 +106,41 @@ class ExpectedActualDeclarationChecker(
val actuals = ExpectedActualResolver.findActualForExpected(descriptor, leafModule) ?: return@forEach
checkExpectedDeclarationHasAtLeastOneActual(
reportOn, descriptor, actuals, trace, leafModule, checkActualModifier, expectActualTracker
reportOn, descriptor, actuals, trace, leafModule, checkActualModifier, context.expectActualTracker
)
checkImplicitJavaActualization(reportOn, descriptor, actuals, leafModule, context)
checkExpectedDeclarationHasAtMostOneActual(
reportOn, descriptor, actuals, allActualizationPaths, trace
)
}
}
private fun checkImplicitJavaActualization(
expectPsi: KtNamedDeclaration,
expect: MemberDescriptor,
actuals: ActualsMap,
module: ModuleDescriptor,
context: DeclarationCheckerContext
) {
val actualMembers = actuals
.filter { (compatibility, _) -> compatibility.isCompatibleOrWeakCompatible() }
.flatMap { (_, members) -> members }
.takeIf(List<MemberDescriptor>::isNotEmpty)
?: return
if (actualMembers.any {
it is MppJavaImplicitActualizatorMarker &&
with(OptInUsageChecker) {
!expectPsi.isDeclarationAnnotatedWith(implicitlyActualizedAnnotationFqn, context.trace.bindingContext)
}
}
) {
context.trace.report(Errors.IMPLICIT_JVM_ACTUALIZATION.on(expectPsi, expect, module))
}
}
private fun checkExpectedDeclarationHasAtMostOneActual(
reportOn: KtNamedDeclaration,
expectDescriptor: MemberDescriptor,
@@ -343,7 +343,7 @@ class OptInUsageChecker(project: Project) : CallChecker {
}
}
private fun PsiElement.isDeclarationAnnotatedWith(annotationFqName: FqName, bindingContext: BindingContext): Boolean {
internal fun PsiElement.isDeclarationAnnotatedWith(annotationFqName: FqName, bindingContext: BindingContext): Boolean {
if (this !is KtDeclaration) return false
val descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, this)
@@ -0,0 +1,20 @@
// WITH_STDLIB
// MODULE: m1-common
// FILE: common.kt
import kotlin.jvm.ImplicitlyActualizedByJvmDeclaration
<!NO_ACTUAL_FOR_EXPECT{JVM}!>@OptIn(ExperimentalMultiplatform::class)
@ImplicitlyActualizedByJvmDeclaration
expect class <!PACKAGE_OR_CLASSIFIER_REDECLARATION!>Foo<!>() {
fun foo()
}<!>
// MODULE: m2-jvm()()(m1-common)
// FILE: Foo.java
public class Foo {
public void foo() {
}
}
@@ -0,0 +1,20 @@
// WITH_STDLIB
// MODULE: m1-common
// FILE: common.kt
import kotlin.jvm.ImplicitlyActualizedByJvmDeclaration
@OptIn(ExperimentalMultiplatform::class)
@ImplicitlyActualizedByJvmDeclaration
expect class Foo() {
fun foo()
}
// MODULE: m2-jvm()()(m1-common)
// FILE: Foo.java
public class Foo {
public void foo() {
}
}
@@ -0,0 +1,14 @@
// MODULE: m1-common
// FILE: common.kt
<!NO_ACTUAL_FOR_EXPECT{JVM}!>expect class <!PACKAGE_OR_CLASSIFIER_REDECLARATION!>Foo<!>() {
fun foo()
}<!>
// MODULE: m2-jvm()()(m1-common)
// FILE: Foo.java
public class Foo {
public void foo() {
}
}
@@ -0,0 +1,14 @@
// MODULE: m1-common
// FILE: common.kt
expect class <!IMPLICIT_JVM_ACTUALIZATION{JVM}!>Foo<!>() {
fun foo()
}
// MODULE: m2-jvm()()(m1-common)
// FILE: Foo.java
public class Foo {
public void foo() {
}
}
@@ -0,0 +1,20 @@
// MODULE: m1-common
// FILE: common.kt
<!INCOMPATIBLE_MATCHING{JVM}!>expect class <!PACKAGE_OR_CLASSIFIER_REDECLARATION!>Foo<!>(i: Int) {
fun foo()
}<!>
// MODULE: m2-jvm()()(m1-common)
// FILE: Foo.java
public class Foo {
public Foo(int i) {}
public void foo() {}
}
// FILE: jvm.kt
class <!PACKAGE_OR_CLASSIFIER_REDECLARATION!>Foo<!><T>(t: T) {
fun foo() {}
}
@@ -0,0 +1,20 @@
// MODULE: m1-common
// FILE: common.kt
expect class <!IMPLICIT_JVM_ACTUALIZATION{JVM}!>Foo<!>(i: Int) {
fun <!AMBIGUOUS_ACTUALS{JVM}!>foo<!>()
}
// MODULE: m2-jvm()()(m1-common)
// FILE: Foo.java
public class Foo {
public Foo(int i) {}
public void foo() {}
}
// FILE: jvm.kt
class Foo<T>(t: T) {
fun foo() {}
}
@@ -23441,6 +23441,24 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
runTest("compiler/testData/diagnostics/tests/multiplatform/java/flexibleTypes.kt");
}
@Test
@TestMetadata("implicitJavaActualizationAllowed.kt")
public void testImplicitJavaActualizationAllowed() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/java/implicitJavaActualizationAllowed.kt");
}
@Test
@TestMetadata("implicitJavaActualizationDisallowed.kt")
public void testImplicitJavaActualizationDisallowed() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/java/implicitJavaActualizationDisallowed.kt");
}
@Test
@TestMetadata("implicitJavaActualization_multipleActuals.kt")
public void testImplicitJavaActualization_multipleActuals() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/java/implicitJavaActualization_multipleActuals.kt");
}
@Test
@TestMetadata("inheritedJavaMembers.kt")
public void testInheritedJavaMembers() throws Exception {
@@ -17,7 +17,8 @@
package org.jetbrains.kotlin.load.java.descriptors;
import org.jetbrains.kotlin.descriptors.ClassDescriptor;
import org.jetbrains.kotlin.mpp.MppJavaImplicitActualizatorMarker;
public interface JavaClassDescriptor extends ClassDescriptor {
public interface JavaClassDescriptor extends ClassDescriptor, MppJavaImplicitActualizatorMarker {
boolean isRecord();
}
@@ -0,0 +1,14 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.mpp
/**
* The marker interface marks all Java descriptors that can implicitly actualize expect declarations.
*
* The marker interface is needed for being able to access information about Java specific descriptors from platform-agnostic
* `:compiler:frontend` module
*/
interface MppJavaImplicitActualizatorMarker
@@ -51,6 +51,30 @@ public expect annotation class JvmName(val name: String)
@OptionalExpectation
public expect annotation class JvmMultifileClass()
/**
* This annotation marks Kotlin `expect` declarations that are implicitly actualized by Java.
*
* ## Safety Risks
*
* Implicit actualization bypasses safety features, potentially leading to errors or unexpected behavior. If you use this annotation, some
* of the expect-actual invariants are not checked.
*
* Use this annotation only as a last resort. The annotation might stop working in future Kotlin versions without prior notice.
*
* If you use this annotation, consider describing your use cases in [KT-58545](https://youtrack.jetbrains.com/issue/KT-58545) comments.
*
* ## Migration
*
* Rewrite the code using explicit `actual typealias`. Unfortunately, it requires you to move your expect declarations into another
* package. Refer to [KT-58545](https://youtrack.jetbrains.com/issue/KT-58545) for more detailed migration example.
*/
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS)
@ExperimentalMultiplatform
@MustBeDocumented
@SinceKotlin("1.9")
@OptionalExpectation
public expect annotation class ImplicitlyActualizedByJvmDeclaration()
/**
* Instructs the Kotlin compiler not to generate getters/setters for this property and expose it as a field.
@@ -98,6 +98,29 @@ public actual annotation class JvmSynthetic
@Retention(AnnotationRetention.SOURCE)
public annotation class Throws(vararg val exceptionClasses: KClass<out Throwable>)
/**
* This annotation marks Kotlin `expect` declarations that are implicitly actualized by Java.
*
* # Safety Risks
*
* Implicit actualization bypasses safety features, potentially leading to errors or unexpected behavior. If you use this annotation, some
* of the expect-actual invariants are not checked.
*
* Use this annotation only as a last resort. The annotation might stop working in future Kotlin versions without prior notice.
*
* If you use this annotation, consider describing your use cases in [KT-58545](https://youtrack.jetbrains.com/issue/KT-58545) comments.
*
* # Migration
*
* Rewrite the code using explicit `actual typealias`. Unfortunately, it requires you to move your expect declarations into another
* package. Refer to [KT-58545](https://youtrack.jetbrains.com/issue/KT-58545) for more detailed migration example.
*/
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS)
@ExperimentalMultiplatform
@MustBeDocumented
@SinceKotlin("1.9")
public actual annotation class ImplicitlyActualizedByJvmDeclaration
/**
* Instructs the Kotlin compiler not to generate getters/setters for this property and expose it as a field.
@@ -3393,6 +3393,9 @@ public abstract interface annotation class kotlin/js/ExperimentalJsFileName : ja
public abstract interface annotation class kotlin/js/ExperimentalJsReflectionCreateInstance : java/lang/annotation/Annotation {
}
public abstract interface annotation class kotlin/jvm/ImplicitlyActualizedByJvmDeclaration : java/lang/annotation/Annotation {
}
public final class kotlin/jvm/JvmClassMappingKt {
public static final fun getAnnotationClass (Ljava/lang/annotation/Annotation;)Lkotlin/reflect/KClass;
public static final fun getJavaClass (Ljava/lang/Object;)Ljava/lang/Class;