diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java index 2e1494c6d9a..908546eb8c0 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java @@ -213,6 +213,18 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag runTest("compiler/testData/diagnostics/tests/DeprecatedUnaryOperatorConventions.kt"); } + @Test + @TestMetadata("derivedClassPropertyShadowsBaseClassField.kt") + public void testDerivedClassPropertyShadowsBaseClassField() throws Exception { + runTest("compiler/testData/diagnostics/tests/derivedClassPropertyShadowsBaseClassField.kt"); + } + + @Test + @TestMetadata("derivedClassPropertyShadowsBaseClassField13.kt") + public void testDerivedClassPropertyShadowsBaseClassField13() throws Exception { + runTest("compiler/testData/diagnostics/tests/derivedClassPropertyShadowsBaseClassField13.kt"); + } + @Test @TestMetadata("DiamondFunction.kt") public void testDiamondFunction() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java index 37206cf733c..0b11f3c4f67 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java @@ -213,6 +213,18 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti runTest("compiler/testData/diagnostics/tests/DeprecatedUnaryOperatorConventions.kt"); } + @Test + @TestMetadata("derivedClassPropertyShadowsBaseClassField.kt") + public void testDerivedClassPropertyShadowsBaseClassField() throws Exception { + runTest("compiler/testData/diagnostics/tests/derivedClassPropertyShadowsBaseClassField.kt"); + } + + @Test + @TestMetadata("derivedClassPropertyShadowsBaseClassField13.kt") + public void testDerivedClassPropertyShadowsBaseClassField13() throws Exception { + runTest("compiler/testData/diagnostics/tests/derivedClassPropertyShadowsBaseClassField13.kt"); + } + @Test @TestMetadata("DiamondFunction.kt") public void testDiamondFunction() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java index cb779baa1d6..c99b4ddb7ef 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java @@ -213,6 +213,18 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac runTest("compiler/testData/diagnostics/tests/DeprecatedUnaryOperatorConventions.kt"); } + @Test + @TestMetadata("derivedClassPropertyShadowsBaseClassField.kt") + public void testDerivedClassPropertyShadowsBaseClassField() throws Exception { + runTest("compiler/testData/diagnostics/tests/derivedClassPropertyShadowsBaseClassField.kt"); + } + + @Test + @TestMetadata("derivedClassPropertyShadowsBaseClassField13.kt") + public void testDerivedClassPropertyShadowsBaseClassField13() throws Exception { + runTest("compiler/testData/diagnostics/tests/derivedClassPropertyShadowsBaseClassField13.kt"); + } + @Test @TestMetadata("DiamondFunction.kt") public void testDiamondFunction() throws Exception { diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmPropertyVsFieldAmbiguityCallChecker.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmPropertyVsFieldAmbiguityCallChecker.kt new file mode 100644 index 00000000000..781e03f0a2e --- /dev/null +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmPropertyVsFieldAmbiguityCallChecker.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2010-2022 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.resolve.jvm.checkers + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.load.java.lazy.descriptors.isJavaField +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker +import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm + +object JvmPropertyVsFieldAmbiguityCallChecker : CallChecker { + override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) { + if (!context.languageVersionSettings.supportsFeature(LanguageFeature.PreferJavaFieldOverload)) return + val resultingDescriptor = resolvedCall.resultingDescriptor as? PropertyDescriptor ?: return + if (!resultingDescriptor.isJavaField) return + val ownContainingClass = resultingDescriptor.containingDeclaration as? ClassDescriptor ?: return + + val field = DescriptorUtils.unwrapFakeOverride(resultingDescriptor) + val fieldClassDescriptor = field.containingDeclaration as? ClassDescriptor + if (fieldClassDescriptor === ownContainingClass) return + + // If we have visible alternative property, we leave everything as is (see KT-54393) + ownContainingClass.unsubstitutedMemberScope.getContributedVariables( + resultingDescriptor.name, NoLookupLocation.FOR_ALREADY_TRACKED + ).forEach { alternativePropertyDescriptor -> + if (alternativePropertyDescriptor !== resultingDescriptor) { + val hasLateInit = alternativePropertyDescriptor.isLateInit + if (!hasLateInit && + alternativePropertyDescriptor.getter?.isDefault != false && + alternativePropertyDescriptor.setter?.isDefault != false && + alternativePropertyDescriptor.modality == Modality.FINAL + ) return@forEach + val propertyClassDescriptor = + DescriptorUtils.unwrapFakeOverride(alternativePropertyDescriptor).containingDeclaration as? ClassDescriptor + if (fieldClassDescriptor != null && propertyClassDescriptor != null && + DescriptorUtils.isSubclass(fieldClassDescriptor, propertyClassDescriptor) + ) return@forEach + if (DescriptorVisibilities.isVisible( + /* receiver = */ resolvedCall.dispatchReceiver, + /* what = */ alternativePropertyDescriptor, + /* from = */ context.scope.ownerDescriptor, + /* useSpecialRulesForPrivateSealedConstructors = */ false + ) + ) { + val factory = when { + alternativePropertyDescriptor.getter?.isDefault == false -> + ErrorsJvm.BASE_CLASS_FIELD_SHADOWS_DERIVED_CLASS_PROPERTY + hasLateInit || alternativePropertyDescriptor.setter?.isDefault == false -> + ErrorsJvm.BACKING_FIELD_ACCESSED_DUE_TO_PROPERTY_FIELD_CONFLICT + else -> + ErrorsJvm.BASE_CLASS_FIELD_MAY_SHADOW_DERIVED_CLASS_PROPERTY + } + context.trace.report( + factory.on( + reportOn, + fieldClassDescriptor?.fqNameSafe?.asString() ?: "unknown class", + propertyClassDescriptor?.fqNameSafe?.asString() ?: "unknown class" + ) + ) + return + } + } + } + } +} \ No newline at end of file diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java index 8d63430c8fb..a8262be1742 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java @@ -237,6 +237,25 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension { MAP.put(REDUNDANT_REPEATABLE_ANNOTATION, "Please, remove the ''{0}'' annotation, as ''{1}'' is already enough", TO_STRING, TO_STRING); + + MAP.put(BASE_CLASS_FIELD_SHADOWS_DERIVED_CLASS_PROPERTY, + "Now field from base class {0} shadows the property with custom getter from derived class {1}. " + + "This behavior will be changed soon in favor of the property. " + + "Please use explicit cast to {0} if you wish to preserve current behavior. " + + "See https://youtrack.jetbrains.com/issue/KT-55017 for details", STRING, STRING); + + MAP.put(BASE_CLASS_FIELD_MAY_SHADOW_DERIVED_CLASS_PROPERTY, + "Field from base class {0} may shadow the open property from derived class {1}. " + + "This behavior will be changed soon in favor of the property. " + + "Please use explicit cast to {0} if you wish to preserve current behavior. " + + "See https://youtrack.jetbrains.com/issue/KT-55017 for details", STRING, STRING); + + MAP.put(BACKING_FIELD_ACCESSED_DUE_TO_PROPERTY_FIELD_CONFLICT, + "Property backing field in derived class {1} is accessed instead of the property itself. " + + "This happens because of field with the same name in the base class {0}. " + + "This behavior will be changed soon in favor of the property. " + + "Please use explicit cast to {0} if you wish to preserve current behavior. " + + "See https://youtrack.jetbrains.com/issue/KT-55017 for details", STRING, STRING); } @NotNull diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java index 7d3e4893765..f5493b0f158 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java @@ -214,6 +214,12 @@ public interface ErrorsJvm { DiagnosticFactory2 REDUNDANT_REPEATABLE_ANNOTATION = DiagnosticFactory2.create(WARNING); + DiagnosticFactory2 BASE_CLASS_FIELD_SHADOWS_DERIVED_CLASS_PROPERTY = DiagnosticFactory2.create(WARNING); + + DiagnosticFactory2 BASE_CLASS_FIELD_MAY_SHADOW_DERIVED_CLASS_PROPERTY = DiagnosticFactory2.create(WARNING); + + DiagnosticFactory2 BACKING_FIELD_ACCESSED_DUE_TO_PROPERTY_FIELD_CONFLICT = DiagnosticFactory2.create(WARNING); + @SuppressWarnings("UnusedDeclaration") Object _initializer = new Object() { { diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt index 5bd77efaaf0..5f476bd28ac 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt @@ -64,7 +64,8 @@ object JvmPlatformConfigurator : PlatformConfiguratorBase( EnumDeclaringClassDeprecationChecker, UpperBoundViolatedInTypealiasConstructorChecker, JvmSyntheticAssignmentChecker, - LateinitIntrinsicApplicabilityChecker(isWarningInPre19 = false) + LateinitIntrinsicApplicabilityChecker(isWarningInPre19 = false), + JvmPropertyVsFieldAmbiguityCallChecker, ), additionalTypeCheckers = listOf( diff --git a/compiler/testData/diagnostics/tests/derivedClassPropertyShadowsBaseClassField.fir.kt b/compiler/testData/diagnostics/tests/derivedClassPropertyShadowsBaseClassField.fir.kt new file mode 100644 index 00000000000..219d80c59c2 --- /dev/null +++ b/compiler/testData/diagnostics/tests/derivedClassPropertyShadowsBaseClassField.fir.kt @@ -0,0 +1,48 @@ +// WITH_STDLIB +// FILE: Base.java + +public class Base { + public String regular = "a"; + + public String withGetter = "b"; + + public String lateInit = "c"; + + public String lazyProp = "d"; + + public String withSetter = "e"; + + public String openProp = "f"; +} + +// FILE: test.kt + +open class Derived : Base() { + val regular = "aa" + + val withGetter get() = "bb" + + lateinit var lateInit: String + + val lazyProp by lazy { "dd" } + + var withSetter: String = "ee" + set(value) { + println(value) + field = value + } + + open val openProp = "ff" +} + +fun test(d: Derived) { + d.regular + d.withGetter + d.lateInit + d.lazyProp + d.withSetter = "" + d.openProp + + d::withGetter + Derived::withGetter +} diff --git a/compiler/testData/diagnostics/tests/derivedClassPropertyShadowsBaseClassField.kt b/compiler/testData/diagnostics/tests/derivedClassPropertyShadowsBaseClassField.kt new file mode 100644 index 00000000000..532a9aa82e7 --- /dev/null +++ b/compiler/testData/diagnostics/tests/derivedClassPropertyShadowsBaseClassField.kt @@ -0,0 +1,48 @@ +// WITH_STDLIB +// FILE: Base.java + +public class Base { + public String regular = "a"; + + public String withGetter = "b"; + + public String lateInit = "c"; + + public String lazyProp = "d"; + + public String withSetter = "e"; + + public String openProp = "f"; +} + +// FILE: test.kt + +open class Derived : Base() { + val regular = "aa" + + val withGetter get() = "bb" + + lateinit var lateInit: String + + val lazyProp by lazy { "dd" } + + var withSetter: String = "ee" + set(value) { + println(value) + field = value + } + + open val openProp = "ff" +} + +fun test(d: Derived) { + d.regular + d.withGetter + d.lateInit + d.lazyProp + d.withSetter = "" + d.openProp + + d::withGetter + Derived::withGetter +} diff --git a/compiler/testData/diagnostics/tests/derivedClassPropertyShadowsBaseClassField.txt b/compiler/testData/diagnostics/tests/derivedClassPropertyShadowsBaseClassField.txt new file mode 100644 index 00000000000..d4b52656560 --- /dev/null +++ b/compiler/testData/diagnostics/tests/derivedClassPropertyShadowsBaseClassField.txt @@ -0,0 +1,35 @@ +package + +public fun test(/*0*/ d: Derived): kotlin.Unit + +public open class Base { + public constructor Base() + public final var lateInit: kotlin.String! + public final var lazyProp: kotlin.String! + public final var openProp: kotlin.String! + public final var regular: kotlin.String! + public final var withGetter: kotlin.String! + public final var withSetter: kotlin.String! + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public open class Derived : Base { + public constructor Derived() + public final lateinit var lateInit: kotlin.String + public final override /*1*/ /*fake_override*/ var lateInit: kotlin.String! + public final val lazyProp: kotlin.String + public final override /*1*/ /*fake_override*/ var lazyProp: kotlin.String! + public open val openProp: kotlin.String = "ff" + public final override /*1*/ /*fake_override*/ var openProp: kotlin.String! + public final val regular: kotlin.String = "aa" + public final override /*1*/ /*fake_override*/ var regular: kotlin.String! + public final val withGetter: kotlin.String + public final override /*1*/ /*fake_override*/ var withGetter: kotlin.String! + public final var withSetter: kotlin.String + public final override /*1*/ /*fake_override*/ var withSetter: kotlin.String! + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/derivedClassPropertyShadowsBaseClassField13.kt b/compiler/testData/diagnostics/tests/derivedClassPropertyShadowsBaseClassField13.kt new file mode 100644 index 00000000000..d913e0518a6 --- /dev/null +++ b/compiler/testData/diagnostics/tests/derivedClassPropertyShadowsBaseClassField13.kt @@ -0,0 +1,42 @@ +// FIR_IDENTICAL +// !LANGUAGE: -PreferJavaFieldOverload +// WITH_STDLIB +// FILE: Base.java + +public class Base { + public String a = "a"; + + public String b = "b"; + + public String c = "c"; + + public String d = "d"; + + public String e = "e"; +} + +// FILE: test.kt + +class Derived : Base() { + val a = "aa" + + val b get() = "bb" + + lateinit var c: String + + val d by lazy { "dd" } + + var e: String = "ee" + set(value) { + println(value) + field = value + } +} + +fun test(d: Derived) { + d.a + d.b + d.c + d.d + d.e = "" +} diff --git a/compiler/testData/diagnostics/tests/derivedClassPropertyShadowsBaseClassField13.txt b/compiler/testData/diagnostics/tests/derivedClassPropertyShadowsBaseClassField13.txt new file mode 100644 index 00000000000..d2a27dbb27d --- /dev/null +++ b/compiler/testData/diagnostics/tests/derivedClassPropertyShadowsBaseClassField13.txt @@ -0,0 +1,32 @@ +package + +public fun test(/*0*/ d: Derived): kotlin.Unit + +public open class Base { + public constructor Base() + public final var a: kotlin.String! + public final var b: kotlin.String! + public final var c: kotlin.String! + public final var d: kotlin.String! + public final var e: kotlin.String! + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class Derived : Base { + public constructor Derived() + public final val a: kotlin.String = "aa" + public final override /*1*/ /*fake_override*/ var a: kotlin.String! + public final val b: kotlin.String + public final override /*1*/ /*fake_override*/ var b: kotlin.String! + public final lateinit var c: kotlin.String + public final override /*1*/ /*fake_override*/ var c: kotlin.String! + public final val d: kotlin.String + public final override /*1*/ /*fake_override*/ var d: kotlin.String! + public final var e: kotlin.String + public final override /*1*/ /*fake_override*/ var e: kotlin.String! + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/j+k/fieldOverridesNothing.fir.kt b/compiler/testData/diagnostics/tests/j+k/fieldOverridesNothing.fir.kt new file mode 100644 index 00000000000..478351238e2 --- /dev/null +++ b/compiler/testData/diagnostics/tests/j+k/fieldOverridesNothing.fir.kt @@ -0,0 +1,21 @@ +// !LANGUAGE: +PreferJavaFieldOverload + +// FILE: B.java + +public abstract class B implements A { + public int size = 1; +} + +// FILE: main.kt + +interface A { + val size: Int +} + +class C : B() { + override val size: Int get() = 1 +} + +fun foo() { + C().size +} diff --git a/compiler/testData/diagnostics/tests/j+k/fieldOverridesNothing.kt b/compiler/testData/diagnostics/tests/j+k/fieldOverridesNothing.kt index 47a7971833c..d79bf061fda 100644 --- a/compiler/testData/diagnostics/tests/j+k/fieldOverridesNothing.kt +++ b/compiler/testData/diagnostics/tests/j+k/fieldOverridesNothing.kt @@ -1,4 +1,3 @@ -// FIR_IDENTICAL // !LANGUAGE: +PreferJavaFieldOverload // FILE: B.java @@ -18,5 +17,5 @@ class C : B() { } fun foo() { - C().size -} \ No newline at end of file + C().size +} diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java index 33b35ff9bbe..50659e6753d 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java @@ -213,6 +213,18 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/tests/DeprecatedUnaryOperatorConventions.kt"); } + @Test + @TestMetadata("derivedClassPropertyShadowsBaseClassField.kt") + public void testDerivedClassPropertyShadowsBaseClassField() throws Exception { + runTest("compiler/testData/diagnostics/tests/derivedClassPropertyShadowsBaseClassField.kt"); + } + + @Test + @TestMetadata("derivedClassPropertyShadowsBaseClassField13.kt") + public void testDerivedClassPropertyShadowsBaseClassField13() throws Exception { + runTest("compiler/testData/diagnostics/tests/derivedClassPropertyShadowsBaseClassField13.kt"); + } + @Test @TestMetadata("DiamondFunction.kt") public void testDiamondFunction() throws Exception {