[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:
+18
@@ -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 {
|
||||
|
||||
+18
@@ -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);
|
||||
|
||||
+4
@@ -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(
|
||||
|
||||
+33
-3
@@ -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)
|
||||
|
||||
Vendored
+20
@@ -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() {
|
||||
}
|
||||
}
|
||||
+20
@@ -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() {
|
||||
}
|
||||
}
|
||||
Vendored
+14
@@ -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() {
|
||||
}
|
||||
}
|
||||
Vendored
+14
@@ -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() {
|
||||
}
|
||||
}
|
||||
+20
@@ -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() {}
|
||||
}
|
||||
Vendored
+20
@@ -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() {}
|
||||
}
|
||||
Generated
+18
@@ -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 {
|
||||
|
||||
+2
-1
@@ -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.
|
||||
|
||||
+3
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user