diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonExpressionCheckers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonExpressionCheckers.kt index b9a5a4b49c4..95fe39a9aa2 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonExpressionCheckers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonExpressionCheckers.kt @@ -106,6 +106,7 @@ object CommonExpressionCheckers : ExpressionCheckers() { override val loopExpressionCheckers: Set get() = setOf( FirLoopConditionChecker, + FirForLoopStatementAssignmentChecker ) override val loopJumpCheckers: Set @@ -128,7 +129,7 @@ object CommonExpressionCheckers : ExpressionCheckers() { get() = setOf( FirForLoopChecker, FirConflictsExpressionChecker, - FirSingleNamedFunctionChecker + FirSingleNamedFunctionChecker, ) override val checkNotNullCallCheckers: Set diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirForLoopStatementAssignmentChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirForLoopStatementAssignmentChecker.kt new file mode 100644 index 00000000000..280a144b870 --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirForLoopStatementAssignmentChecker.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2010-2024 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.fir.analysis.checkers.expression + +import org.jetbrains.kotlin.KtFakeSourceElementKind +import org.jetbrains.kotlin.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.diagnostics.reportOn +import org.jetbrains.kotlin.fir.analysis.checkers.MppCheckerKind +import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors +import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin +import org.jetbrains.kotlin.fir.declarations.FirProperty +import org.jetbrains.kotlin.fir.declarations.utils.FirScriptCustomizationKind +import org.jetbrains.kotlin.fir.expressions.FirBlock +import org.jetbrains.kotlin.fir.expressions.FirErrorExpression +import org.jetbrains.kotlin.fir.expressions.FirLoop +import org.jetbrains.kotlin.fir.expressions.FirReturnExpression + +object FirForLoopStatementAssignmentChecker : FirLoopExpressionChecker(MppCheckerKind.Common) { + override fun check(expression: FirLoop, context: CheckerContext, reporter: DiagnosticReporter) { + // Checks the pattern for desugared for loop. + val parent = if (context.containingElements.size >= 2) context.containingElements[context.containingElements.size - 2] else return + if (parent.source?.kind != KtFakeSourceElementKind.DesugaredForLoop) return + + val grandParent = if (context.containingElements.size >= 3) + context.containingElements[context.containingElements.size - 3] + else + return + + if (// It is used as a statement + grandParent is FirBlock + // It is used as a single statement in the method body + || (grandParent is FirReturnExpression && grandParent.source?.kind == KtFakeSourceElementKind.ImplicitReturn.FromLastStatement) + // It is used in a kotlin script as a last statement + || (grandParent is FirProperty && (grandParent.origin as? FirDeclarationOrigin.ScriptCustomization)?.kind == FirScriptCustomizationKind.RESULT_PROPERTY) + // There was a fail before (for example, using two labels before the for loop) + || (grandParent is FirErrorExpression) + ) + return + + reporter.reportOn(expression.source, FirErrors.EXPRESSION_EXPECTED, context) + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/controlStructures/ForbidStatementAsDirectFunctionBody.fir.kt b/compiler/testData/diagnostics/tests/controlStructures/ForbidStatementAsDirectFunctionBody.fir.kt deleted file mode 100644 index 08721244579..00000000000 --- a/compiler/testData/diagnostics/tests/controlStructures/ForbidStatementAsDirectFunctionBody.fir.kt +++ /dev/null @@ -1,11 +0,0 @@ - -fun foo1() = while (b()) {} - -fun foo2() = for (i in 10) {} - -fun foo3() = when (b()) { - true -> 1 - else -> 0 -} - -fun b(): Boolean = true \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/controlStructures/ForbidStatementAsDirectFunctionBody.kt b/compiler/testData/diagnostics/tests/controlStructures/ForbidStatementAsDirectFunctionBody.kt index c3a4ed842b0..e9320e5655e 100644 --- a/compiler/testData/diagnostics/tests/controlStructures/ForbidStatementAsDirectFunctionBody.kt +++ b/compiler/testData/diagnostics/tests/controlStructures/ForbidStatementAsDirectFunctionBody.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL fun foo1() = while (b()) {} fun foo2() = for (i in 10) {} diff --git a/compiler/testData/diagnostics/tests/controlStructures/kt770.kt351.kt735_StatementType.fir.kt b/compiler/testData/diagnostics/tests/controlStructures/kt770.kt351.kt735_StatementType.fir.kt index 7fa77626645..4c0f27478dd 100644 --- a/compiler/testData/diagnostics/tests/controlStructures/kt770.kt351.kt735_StatementType.fir.kt +++ b/compiler/testData/diagnostics/tests/controlStructures/kt770.kt351.kt735_StatementType.fir.kt @@ -149,7 +149,7 @@ fun bar(a: Unit) {} fun testStatementInExpressionContext() { var z = 34 val a1: Unit = z = 334 - val f = for (i in 1..10) {} + val f = for (i in 1..10) {} if (true) return z = 34 return while (true) {} } diff --git a/compiler/tests-spec/testData/diagnostics/linked/statements/assignments/p-1/neg/2.1.fir.kt b/compiler/tests-spec/testData/diagnostics/linked/statements/assignments/p-1/neg/2.1.fir.kt index f9820e76106..047d8adaf98 100644 --- a/compiler/tests-spec/testData/diagnostics/linked/statements/assignments/p-1/neg/2.1.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/linked/statements/assignments/p-1/neg/2.1.fir.kt @@ -16,8 +16,8 @@ * NOTE: right-hand side of an assignment must be expression */ fun case1() { - val x = for () { } - val y = for (x in 1..2) { } + val x = for () { } + val y = for (x in 1..2) { } val a = while () { } val b = while (false) { } @@ -29,8 +29,8 @@ fun case1() { * NOTE: right-hand side of an assignment must be expression */ fun case2() { - var x = for () { } - var y = for (x in 1..2) { } + var x = for () { } + var y = for (x in 1..2) { } var a = while () { } var b = while (false) { } @@ -49,8 +49,8 @@ fun case3() { var b :Any? var c :Any? - x = for () { } - y = for (x in 1..2) { } + x = for () { } + y = for (x in 1..2) { } a = while () { } b = while (false) { } @@ -62,7 +62,7 @@ fun case3() { * NOTE: left-hand side of an assignment must be expression */ fun case4() { - for (x in 1..2) {} = TODO(); + for (x in 1..2) {} = TODO(); while (false) { } = TODO() }