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 c568ec409b9..a9a1642b9be 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 @@ -1190,6 +1190,7 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") { val ILLEGAL_SUSPEND_PROPERTY_ACCESS by error(PositioningStrategy.REFERENCED_NAME_BY_QUALIFIED) { parameter("suspendCallable") } + val NON_LOCAL_SUSPENSION_POINT by error(PositioningStrategy.REFERENCED_NAME_BY_QUALIFIED) } } 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 125eabb787a..2894a6ed76f 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 @@ -610,5 +610,6 @@ object FirErrors { // Suspend errors val ILLEGAL_SUSPEND_FUNCTION_CALL by error1>(SourceElementPositioningStrategies.REFERENCED_NAME_BY_QUALIFIED) val ILLEGAL_SUSPEND_PROPERTY_ACCESS by error1>(SourceElementPositioningStrategies.REFERENCED_NAME_BY_QUALIFIED) + val NON_LOCAL_SUSPENSION_POINT by error0(SourceElementPositioningStrategies.REFERENCED_NAME_BY_QUALIFIED) } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirSuspendCallChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirSuspendCallChecker.kt index 6cfdad22396..2cccb4d96f4 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirSuspendCallChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirSuspendCallChecker.kt @@ -22,6 +22,7 @@ import org.jetbrains.kotlin.fir.types.coneType import org.jetbrains.kotlin.resolve.calls.checkers.COROUTINE_CONTEXT_1_2_20_FQ_NAME import org.jetbrains.kotlin.resolve.calls.checkers.COROUTINE_CONTEXT_1_2_30_FQ_NAME import org.jetbrains.kotlin.resolve.calls.checkers.COROUTINE_CONTEXT_1_3_FQ_NAME +import org.jetbrains.kotlin.utils.addToStdlib.lastIsInstanceOrNull object FirSuspendCallChecker : FirQualifiedAccessExpressionChecker() { private val SUSPEND_PROPERTIES_FQ_NAMES = setOf( @@ -48,6 +49,10 @@ object FirSuspendCallChecker : FirQualifiedAccessExpressionChecker() { else -> { } } + } else { + if (!checkNonLocalReturnUsage(enclosingSuspendFunction, context)) { + reporter.reportOn(expression.source, FirErrors.NON_LOCAL_SUSPENSION_POINT, context) + } } } @@ -60,4 +65,13 @@ object FirSuspendCallChecker : FirQualifiedAccessExpressionChecker() { } } as? FirFunction } + + private fun checkNonLocalReturnUsage(enclosingSuspendFunction: FirFunction, context: CheckerContext): Boolean { + val containingFunction = context.containingDeclarations.lastIsInstanceOrNull() ?: return false + return if (containingFunction is FirAnonymousFunction && enclosingSuspendFunction !== containingFunction) { + containingFunction.inlineStatus.returnAllowed + } else { + enclosingSuspendFunction === containingFunction + } + } } 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 cdb8be756d6..79912f6c9fc 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 @@ -261,6 +261,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_EXHAUSTIVE_WH import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_FINAL_MEMBER_IN_FINAL_CLASS import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_FINAL_MEMBER_IN_OBJECT import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_LOCAL_RETURN_NOT_ALLOWED +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_LOCAL_SUSPENSION_POINT import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_MEMBER_FUNCTION_NO_BODY import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_PRIVATE_CONSTRUCTOR_IN_ENUM import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_PRIVATE_OR_PROTECTED_CONSTRUCTOR_IN_SEALED @@ -1490,6 +1491,7 @@ class FirDefaultErrorMessages { "Suspend property ''{0}'' should be accessed only from a coroutine or suspend function", SYMBOL ) + map.put(NON_LOCAL_SUSPENSION_POINT, "Suspension functions can be called only within coroutine body") // Extended checkers group map.put(REDUNDANT_VISIBILITY_MODIFIER, "Redundant visibility modifier") diff --git a/compiler/testData/diagnostics/tests/inference/recursiveCalls/kt23531.fir.kt b/compiler/testData/diagnostics/tests/inference/recursiveCalls/kt23531.fir.kt index cce62531e2a..9bf287c0699 100644 --- a/compiler/testData/diagnostics/tests/inference/recursiveCalls/kt23531.fir.kt +++ b/compiler/testData/diagnostics/tests/inference/recursiveCalls/kt23531.fir.kt @@ -14,5 +14,5 @@ suspend fun insideJob3() = doTheJob3() fun doTheJob0() = simpleAsync0 { insideJob0() } fun doTheJob1() = simpleAsync1 { insideJob1() } -suspend fun doTheJob2() = simpleAsync2 { insideJob2() } +suspend fun doTheJob2() = simpleAsync2 { insideJob2() } suspend fun doTheJob3() = simpleAsync3 { insideJob3() } diff --git a/compiler/testData/diagnostics/tests/inference/returningLambdaInSuspendContext.fir.kt b/compiler/testData/diagnostics/tests/inference/returningLambdaInSuspendContext.fir.kt deleted file mode 100644 index 017adc2125d..00000000000 --- a/compiler/testData/diagnostics/tests/inference/returningLambdaInSuspendContext.fir.kt +++ /dev/null @@ -1,39 +0,0 @@ -// !DIAGNOSTICS: -UNUSED_PARAMETER -UNCHECKED_CAST -UNUSED_LAMBDA_EXPRESSION - -class Foo { - suspend operator fun invoke(body: suspend (Int) -> T) = null as T - suspend fun bar(body: suspend (Int) -> T) = null as T -} - -fun runBlocking(block: suspend () -> T) = null as T - -public inline fun T.run(block: T.() -> R) = null as R - -fun main() { - val retry = Foo() - - runBlocking { - retry { 1 } // OK only after fix - } - runBlocking { - retry { 1 } // OK - 1 - } - runBlocking { - retry.bar { 1 } // OK - } - runBlocking { - { retry { 1 } } // should be error - } - runBlocking { - 10.run { retry { 1 } } // should be OK - } - runBlocking { - 10.run { retry { 1 } } // should be OK - 1 - } - runBlocking { - { retry { 1 } } // should be error - 1 - } -} diff --git a/compiler/testData/diagnostics/tests/inference/returningLambdaInSuspendContext.kt b/compiler/testData/diagnostics/tests/inference/returningLambdaInSuspendContext.kt index 927e84a1cd5..8d50528a41e 100644 --- a/compiler/testData/diagnostics/tests/inference/returningLambdaInSuspendContext.kt +++ b/compiler/testData/diagnostics/tests/inference/returningLambdaInSuspendContext.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !DIAGNOSTICS: -UNUSED_PARAMETER -UNCHECKED_CAST -UNUSED_LAMBDA_EXPRESSION class Foo { diff --git a/compiler/testData/diagnostics/testsWithStdLib/coroutines/inlineCrossinline/inlineSuspendOfCrossinlineOrdinary.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/coroutines/inlineCrossinline/inlineSuspendOfCrossinlineOrdinary.fir.kt index db3279d5780..e013695fd00 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/coroutines/inlineCrossinline/inlineSuspendOfCrossinlineOrdinary.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/coroutines/inlineCrossinline/inlineSuspendOfCrossinlineOrdinary.fir.kt @@ -32,7 +32,7 @@ suspend fun calculate() = "OK" fun box() { builder { test { - calculate() + calculate() } } } diff --git a/compiler/testData/diagnostics/testsWithStdLib/coroutines/inlineCrossinline/inlineSuspendOfNoinlineOrdinary.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/coroutines/inlineCrossinline/inlineSuspendOfNoinlineOrdinary.fir.kt index a24f85d9853..99cf6dca719 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/coroutines/inlineCrossinline/inlineSuspendOfNoinlineOrdinary.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/coroutines/inlineCrossinline/inlineSuspendOfNoinlineOrdinary.fir.kt @@ -32,7 +32,7 @@ suspend fun calculate() = "OK" fun box() { builder { test { - calculate() + calculate() } } } diff --git a/compiler/testData/diagnostics/testsWithStdLib/coroutines/nonLocalSuspension.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/coroutines/nonLocalSuspension.fir.kt deleted file mode 100644 index b365bef4375..00000000000 --- a/compiler/testData/diagnostics/testsWithStdLib/coroutines/nonLocalSuspension.fir.kt +++ /dev/null @@ -1,81 +0,0 @@ -// !DIAGNOSTICS: -UNUSED_PARAMETER -import Host.suspendFromObject - -suspend fun suspendHere() = 1 -suspend fun another(a: T) = 1 - -object Host { - suspend fun suspendFromObject() = 1 -} - -fun builder(c: suspend () -> Unit) { } - -inline fun run(x: () -> Unit) {} - -inline fun runCross(crossinline x: () -> Unit) {} - -fun noinline(x: () -> Unit) {} - -fun foo() { - var result = 1 - builder { - suspendHere() - suspendFromObject() - - another("") - another(1) - - result += suspendHere() - result += suspendFromObject() - - run { - result += suspendHere() - result += suspendFromObject() - - run { - suspendHere() - suspendFromObject() - } - } - - runCross { - result += suspendHere() - result += suspendFromObject() - runCross { - suspendHere() - suspendFromObject() - } - } - - noinline { - result += suspendHere() - result += suspendFromObject() - noinline { - suspendHere() - suspendFromObject() - } - } - - class A { - fun bar() { - suspendHere() - suspendFromObject() - } - } - - object : Any() { - fun baz() { - suspendHere() - suspendFromObject() - } - } - - builder { - suspendHere() - suspendFromObject() - - another(1) - another("") - } - } -} diff --git a/compiler/testData/diagnostics/testsWithStdLib/coroutines/nonLocalSuspension.kt b/compiler/testData/diagnostics/testsWithStdLib/coroutines/nonLocalSuspension.kt index 82d02532f2f..1c8dc26f9de 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/coroutines/nonLocalSuspension.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/coroutines/nonLocalSuspension.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !DIAGNOSTICS: -UNUSED_PARAMETER import Host.suspendFromObject diff --git a/compiler/testData/diagnostics/testsWithStdLib/coroutines/suspesionInDefaultValue.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/coroutines/suspesionInDefaultValue.fir.kt index 0a6c51c86b9..89d0a0ced04 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/coroutines/suspesionInDefaultValue.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/coroutines/suspesionInDefaultValue.fir.kt @@ -6,10 +6,10 @@ suspend fun foo() = 1 suspend fun bar( x: Int = 2 + foo(), y: suspend () -> Int = { foo() }, - z: () -> Int = { foo() }, + z: () -> Int = { foo() }, w: Int = myInline { foo() }, v: Any? = object { - fun x() = foo() + fun x() = foo() suspend fun y() = foo() } ) {} 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 3cb6fe50028..7ca6bc08cbc 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 @@ -3156,6 +3156,12 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirErrors.NON_LOCAL_SUSPENSION_POINT) { firDiagnostic -> + NonLocalSuspensionPointImpl( + firDiagnostic as FirPsiDiagnostic, + token, + ) + } add(FirJvmErrors.CONFLICTING_JVM_DECLARATIONS) { firDiagnostic -> ConflictingJvmDeclarationsImpl( firDiagnostic as FirPsiDiagnostic, 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 cba039992fa..9dd2cd2461f 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 @@ -2207,6 +2207,10 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { abstract val suspendCallable: KtSymbol } + abstract class NonLocalSuspensionPoint : KtFirDiagnostic() { + override val diagnosticClass get() = NonLocalSuspensionPoint::class + } + abstract class ConflictingJvmDeclarations : KtFirDiagnostic() { override val diagnosticClass get() = ConflictingJvmDeclarations::class } 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 b2ef58b3d1b..94a976fd2ba 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 @@ -3561,6 +3561,13 @@ internal class IllegalSuspendPropertyAccessImpl( override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic) } +internal class NonLocalSuspensionPointImpl( + firDiagnostic: FirPsiDiagnostic, + override val token: ValidityToken, +) : KtFirDiagnostic.NonLocalSuspensionPoint(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic) +} + internal class ConflictingJvmDeclarationsImpl( firDiagnostic: FirPsiDiagnostic, override val token: ValidityToken,