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 c84ae801dba..4eb27cecbe9 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 @@ -2091,6 +2091,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti runTest("compiler/testData/diagnostics/tests/backingField/kt782packageLevel.kt"); } + @Test + @TestMetadata("LocalDeclarations.kt") + public void testLocalDeclarations() throws Exception { + runTest("compiler/testData/diagnostics/tests/backingField/LocalDeclarations.kt"); + } + @Test @TestMetadata("SetterWithExplicitType.kt") public void testSetterWithExplicitType() throws Exception { diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt index 0286380d239..52ab0d5610e 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt @@ -427,6 +427,12 @@ object DIAGNOSTICS_LIST : DiagnosticList() { val VAL_REASSIGNMENT by error { parameter("variable") } + val VAL_REASSIGNMENT_VIA_BACKING_FIELD by warning { + parameter("variable") + } + val VAL_REASSIGNMENT_VIA_BACKING_FIELD_ERROR by error { + parameter("variable") + } val WRONG_INVOCATION_KIND by warning { parameter>("declaration") parameter("requiredRange") diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index 05d5fa6a1a4..1109af04a32 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -275,6 +275,8 @@ object FirErrors { // Control flow diagnostics val UNINITIALIZED_VARIABLE by error1() val VAL_REASSIGNMENT by error1() + val VAL_REASSIGNMENT_VIA_BACKING_FIELD by warning1() + val VAL_REASSIGNMENT_VIA_BACKING_FIELD_ERROR by error1() val WRONG_INVOCATION_KIND by warning3, EventOccurrencesRange, EventOccurrencesRange>() val LEAKED_IN_PLACE_LAMBDA by error1>() val WRONG_IMPLIES_CONDITION by warning0() diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirDeclarationCheckerUtils.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirDeclarationCheckerUtils.kt index d1b0855e155..055cdf1e549 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirDeclarationCheckerUtils.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirDeclarationCheckerUtils.kt @@ -5,9 +5,11 @@ package org.jetbrains.kotlin.fir.analysis.checkers.declaration +import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.FirSourceElement import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.modality @@ -16,7 +18,11 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor +import org.jetbrains.kotlin.fir.expressions.FirVariableAssignment +import org.jetbrains.kotlin.fir.languageVersionSettings +import org.jetbrains.kotlin.fir.references.FirBackingFieldReference import org.jetbrains.kotlin.fir.types.FirImplicitTypeRef +import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid import org.jetbrains.kotlin.lexer.KtTokens internal fun isInsideExpectClass(containingClass: FirRegularClass, context: CheckerContext): Boolean { @@ -168,8 +174,36 @@ internal fun checkPropertyAccessors( reporter: DiagnosticReporter, context: CheckerContext ) { - property.setter?.source?.let { - if (property.isVal) { + if (property.isVal) { + if (property.getter != null) { + var reassignment: FirBackingFieldReference? = null + // TODO: consider replacing this with normal VariableAssignmentChecker and reporting all 'field = ...' in getter (not just 1st) + // This way is a bit hacky and does not handle diagnostic suppression normally + val visitor = object : FirVisitorVoid() { + override fun visitElement(element: FirElement) { + element.acceptChildren(this) + } + + override fun visitVariableAssignment(variableAssignment: FirVariableAssignment) { + // Report the first violation (to match FE 1.0 behavior) + if (reassignment != null) return + + val backingFieldReference = variableAssignment.lValue as? FirBackingFieldReference + if (backingFieldReference?.resolvedSymbol?.fir == property) { + reassignment = backingFieldReference + } + } + } + property.getter?.body?.accept(visitor, null) + reassignment?.source?.let { + if (context.session.languageVersionSettings.supportsFeature(LanguageFeature.RestrictionOfValReassignmentViaBackingField)) { + reporter.reportOn(it, FirErrors.VAL_REASSIGNMENT_VIA_BACKING_FIELD_ERROR, property.symbol, context) + } else { + reporter.reportOn(it, FirErrors.VAL_REASSIGNMENT_VIA_BACKING_FIELD, property.symbol, context) + } + } + } + property.setter?.source?.let { reporter.reportOn(it, FirErrors.VAL_WITH_SETTER, context) } } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt index 6fa003ced18..5daa93faf2e 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt @@ -204,6 +204,8 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.USELESS_VARARG_ON import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VALUE_CLASS_CANNOT_BE_CLONEABLE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VALUE_PARAMETER_WITH_NO_TYPE_ANNOTATION import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VAL_REASSIGNMENT +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VAL_REASSIGNMENT_VIA_BACKING_FIELD +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VAL_REASSIGNMENT_VIA_BACKING_FIELD_ERROR import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VAL_WITH_SETTER import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VARARG_OUTSIDE_PARENTHESES import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VARIABLE_EXPECTED @@ -594,6 +596,8 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension { // Control flow diagnostics map.put(UNINITIALIZED_VARIABLE, "{0} must be initialized before access", PROPERTY_NAME) map.put(VAL_REASSIGNMENT, "Val cannot be reassigned", PROPERTY_NAME) + map.put(VAL_REASSIGNMENT_VIA_BACKING_FIELD, "Reassignment of read-only property via backing field is deprecated", PROPERTY_NAME) + map.put(VAL_REASSIGNMENT_VIA_BACKING_FIELD_ERROR, "Reassignment of read-only property via backing field", PROPERTY_NAME) map.put( WRONG_INVOCATION_KIND, "{2} wrong invocation kind: given {3} case, but {4} case is possible", diff --git a/compiler/testData/diagnostics/tests/backingField/FieldReassignment_after.fir.kt b/compiler/testData/diagnostics/tests/backingField/FieldReassignment_after.fir.kt deleted file mode 100644 index a082948fe44..00000000000 --- a/compiler/testData/diagnostics/tests/backingField/FieldReassignment_after.fir.kt +++ /dev/null @@ -1,7 +0,0 @@ -// !LANGUAGE: +RestrictionOfValReassignmentViaBackingField - -val my: Int = 1 - get() { - field++ - return field - } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/backingField/FieldReassignment_after.kt b/compiler/testData/diagnostics/tests/backingField/FieldReassignment_after.kt index 598dcce13e7..2d9e99f3223 100644 --- a/compiler/testData/diagnostics/tests/backingField/FieldReassignment_after.kt +++ b/compiler/testData/diagnostics/tests/backingField/FieldReassignment_after.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !LANGUAGE: +RestrictionOfValReassignmentViaBackingField val my: Int = 1 diff --git a/compiler/testData/diagnostics/tests/backingField/FieldReassignment_before.fir.kt b/compiler/testData/diagnostics/tests/backingField/FieldReassignment_before.fir.kt deleted file mode 100644 index 660a0ee1d2f..00000000000 --- a/compiler/testData/diagnostics/tests/backingField/FieldReassignment_before.fir.kt +++ /dev/null @@ -1,7 +0,0 @@ -// !LANGUAGE: -RestrictionOfValReassignmentViaBackingField - -val my: Int = 1 - get() { - field++ - return field - } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/backingField/FieldReassignment_before.kt b/compiler/testData/diagnostics/tests/backingField/FieldReassignment_before.kt index 2ac370e074f..139250c506c 100644 --- a/compiler/testData/diagnostics/tests/backingField/FieldReassignment_before.kt +++ b/compiler/testData/diagnostics/tests/backingField/FieldReassignment_before.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !LANGUAGE: -RestrictionOfValReassignmentViaBackingField val my: Int = 1 diff --git a/compiler/testData/diagnostics/tests/backingField/LocalDeclarations.kt b/compiler/testData/diagnostics/tests/backingField/LocalDeclarations.kt new file mode 100644 index 00000000000..5c202ecb43c --- /dev/null +++ b/compiler/testData/diagnostics/tests/backingField/LocalDeclarations.kt @@ -0,0 +1,35 @@ +// FIR_IDENTICAL +// !LANGUAGE: +RestrictionOfValReassignmentViaBackingField + +class Outer { + val i: Int = 1 + get() { + class Inner { + var i: Int = 2 + get() { + field++ + return field + } + val j: Int = 3 + get() { + field = 42 + return field + } + + fun innerMember() { + field++ + } + } + return field + } + + val j: Int = 4 + get() { + fun local() { + field++ + field++ + } + local() + return field + } +} diff --git a/compiler/testData/diagnostics/tests/backingField/LocalDeclarations.txt b/compiler/testData/diagnostics/tests/backingField/LocalDeclarations.txt new file mode 100644 index 00000000000..3870658b67d --- /dev/null +++ b/compiler/testData/diagnostics/tests/backingField/LocalDeclarations.txt @@ -0,0 +1,10 @@ +package + +public final class Outer { + public constructor Outer() + public final val i: kotlin.Int = 1 + public final val j: kotlin.Int = 4 + 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/controlFlowAnalysis/backingFieldInsideGetter_after.fir.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/backingFieldInsideGetter_after.fir.kt deleted file mode 100644 index 27b22820416..00000000000 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/backingFieldInsideGetter_after.fir.kt +++ /dev/null @@ -1,23 +0,0 @@ -// !LANGUAGE: +RestrictionOfValReassignmentViaBackingField - -package a - -import java.util.HashSet - -val a: MutableSet? = null - get() { - if (a == null) { - field = HashSet() - } - return a - } - -class R { - val b: String? = null - get() { - if (b == null) { - field = "b" - } - return b - } -} diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/backingFieldInsideGetter_after.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/backingFieldInsideGetter_after.kt index 1c021d93eda..3e102581d96 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/backingFieldInsideGetter_after.kt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/backingFieldInsideGetter_after.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !LANGUAGE: +RestrictionOfValReassignmentViaBackingField package a diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/backingFieldInsideGetter_before.fir.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/backingFieldInsideGetter_before.fir.kt deleted file mode 100644 index f2d5f28e3aa..00000000000 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/backingFieldInsideGetter_before.fir.kt +++ /dev/null @@ -1,23 +0,0 @@ -// !LANGUAGE: -RestrictionOfValReassignmentViaBackingField - -package a - -import java.util.HashSet - -val a: MutableSet? = null - get() { - if (a == null) { - field = HashSet() - } - return a - } - -class R { - val b: String? = null - get() { - if (b == null) { - field = "b" - } - return b - } -} diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/backingFieldInsideGetter_before.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/backingFieldInsideGetter_before.kt index eba3f97535f..acbcd5a6394 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/backingFieldInsideGetter_before.kt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/backingFieldInsideGetter_before.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !LANGUAGE: -RestrictionOfValReassignmentViaBackingField package a 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 c6fa5fbffdc..98674b54f49 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 @@ -2097,6 +2097,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/tests/backingField/kt782packageLevel.kt"); } + @Test + @TestMetadata("LocalDeclarations.kt") + public void testLocalDeclarations() throws Exception { + runTest("compiler/testData/diagnostics/tests/backingField/LocalDeclarations.kt"); + } + @Test @TestMetadata("SetterWithExplicitType.kt") public void testSetterWithExplicitType() throws Exception { diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt index 6bcf07d82f3..4e8b148ec0e 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -1229,6 +1229,20 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirErrors.VAL_REASSIGNMENT_VIA_BACKING_FIELD) { firDiagnostic -> + ValReassignmentViaBackingFieldImpl( + firSymbolBuilder.buildVariableSymbol(firDiagnostic.a.fir as FirProperty), + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } + add(FirErrors.VAL_REASSIGNMENT_VIA_BACKING_FIELD_ERROR) { firDiagnostic -> + ValReassignmentViaBackingFieldErrorImpl( + firSymbolBuilder.buildVariableSymbol(firDiagnostic.a.fir as FirProperty), + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } add(FirErrors.WRONG_INVOCATION_KIND) { firDiagnostic -> WrongInvocationKindImpl( firSymbolBuilder.buildSymbol(firDiagnostic.a.fir as FirDeclaration), diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt index 220916a8b4e..97aa8bf0812 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt @@ -862,6 +862,16 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { abstract val variable: KtVariableSymbol } + abstract class ValReassignmentViaBackingField : KtFirDiagnostic() { + override val diagnosticClass get() = ValReassignmentViaBackingField::class + abstract val variable: KtVariableSymbol + } + + abstract class ValReassignmentViaBackingFieldError : KtFirDiagnostic() { + override val diagnosticClass get() = ValReassignmentViaBackingFieldError::class + abstract val variable: KtVariableSymbol + } + abstract class WrongInvocationKind : KtFirDiagnostic() { override val diagnosticClass get() = WrongInvocationKind::class abstract val declaration: KtSymbol diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index 079a2cb8ab9..db92907416d 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -1403,6 +1403,22 @@ internal class ValReassignmentImpl( override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) } +internal class ValReassignmentViaBackingFieldImpl( + override val variable: KtVariableSymbol, + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.ValReassignmentViaBackingField(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + +internal class ValReassignmentViaBackingFieldErrorImpl( + override val variable: KtVariableSymbol, + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.ValReassignmentViaBackingFieldError(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + internal class WrongInvocationKindImpl( override val declaration: KtSymbol, override val requiredRange: EventOccurrencesRange,