FIR: introduce NON_LOCAL_SUSPENSION_POINT diagnostic
This commit is contained in:
committed by
teamcityserver
parent
8f1d07084b
commit
2397650c24
+1
@@ -1190,6 +1190,7 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
|
|||||||
val ILLEGAL_SUSPEND_PROPERTY_ACCESS by error<PsiElement>(PositioningStrategy.REFERENCED_NAME_BY_QUALIFIED) {
|
val ILLEGAL_SUSPEND_PROPERTY_ACCESS by error<PsiElement>(PositioningStrategy.REFERENCED_NAME_BY_QUALIFIED) {
|
||||||
parameter<Symbol>("suspendCallable")
|
parameter<Symbol>("suspendCallable")
|
||||||
}
|
}
|
||||||
|
val NON_LOCAL_SUSPENSION_POINT by error<PsiElement>(PositioningStrategy.REFERENCED_NAME_BY_QUALIFIED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -610,5 +610,6 @@ object FirErrors {
|
|||||||
// Suspend errors
|
// Suspend errors
|
||||||
val ILLEGAL_SUSPEND_FUNCTION_CALL by error1<PsiElement, FirBasedSymbol<*>>(SourceElementPositioningStrategies.REFERENCED_NAME_BY_QUALIFIED)
|
val ILLEGAL_SUSPEND_FUNCTION_CALL by error1<PsiElement, FirBasedSymbol<*>>(SourceElementPositioningStrategies.REFERENCED_NAME_BY_QUALIFIED)
|
||||||
val ILLEGAL_SUSPEND_PROPERTY_ACCESS by error1<PsiElement, FirBasedSymbol<*>>(SourceElementPositioningStrategies.REFERENCED_NAME_BY_QUALIFIED)
|
val ILLEGAL_SUSPEND_PROPERTY_ACCESS by error1<PsiElement, FirBasedSymbol<*>>(SourceElementPositioningStrategies.REFERENCED_NAME_BY_QUALIFIED)
|
||||||
|
val NON_LOCAL_SUSPENSION_POINT by error0<PsiElement>(SourceElementPositioningStrategies.REFERENCED_NAME_BY_QUALIFIED)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+14
@@ -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_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_2_30_FQ_NAME
|
||||||
import org.jetbrains.kotlin.resolve.calls.checkers.COROUTINE_CONTEXT_1_3_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() {
|
object FirSuspendCallChecker : FirQualifiedAccessExpressionChecker() {
|
||||||
private val SUSPEND_PROPERTIES_FQ_NAMES = setOf(
|
private val SUSPEND_PROPERTIES_FQ_NAMES = setOf(
|
||||||
@@ -48,6 +49,10 @@ object FirSuspendCallChecker : FirQualifiedAccessExpressionChecker() {
|
|||||||
else -> {
|
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
|
} as? FirFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun checkNonLocalReturnUsage(enclosingSuspendFunction: FirFunction, context: CheckerContext): Boolean {
|
||||||
|
val containingFunction = context.containingDeclarations.lastIsInstanceOrNull<FirFunction>() ?: return false
|
||||||
|
return if (containingFunction is FirAnonymousFunction && enclosingSuspendFunction !== containingFunction) {
|
||||||
|
containingFunction.inlineStatus.returnAllowed
|
||||||
|
} else {
|
||||||
|
enclosingSuspendFunction === containingFunction
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -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_FINAL_CLASS
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_FINAL_MEMBER_IN_OBJECT
|
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_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_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_CONSTRUCTOR_IN_ENUM
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_PRIVATE_OR_PROTECTED_CONSTRUCTOR_IN_SEALED
|
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",
|
"Suspend property ''{0}'' should be accessed only from a coroutine or suspend function",
|
||||||
SYMBOL
|
SYMBOL
|
||||||
)
|
)
|
||||||
|
map.put(NON_LOCAL_SUSPENSION_POINT, "Suspension functions can be called only within coroutine body")
|
||||||
|
|
||||||
// Extended checkers group
|
// Extended checkers group
|
||||||
map.put(REDUNDANT_VISIBILITY_MODIFIER, "Redundant visibility modifier")
|
map.put(REDUNDANT_VISIBILITY_MODIFIER, "Redundant visibility modifier")
|
||||||
|
|||||||
+1
-1
@@ -14,5 +14,5 @@ suspend fun insideJob3() = doTheJob3()
|
|||||||
|
|
||||||
fun doTheJob0() = simpleAsync0 { <!ARGUMENT_TYPE_MISMATCH!>insideJob0()<!> }
|
fun doTheJob0() = simpleAsync0 { <!ARGUMENT_TYPE_MISMATCH!>insideJob0()<!> }
|
||||||
fun doTheJob1() = simpleAsync1 { <!ARGUMENT_TYPE_MISMATCH!>insideJob1()<!> }
|
fun doTheJob1() = simpleAsync1 { <!ARGUMENT_TYPE_MISMATCH!>insideJob1()<!> }
|
||||||
suspend fun doTheJob2() = simpleAsync2 { <!ARGUMENT_TYPE_MISMATCH!>insideJob2()<!> }
|
suspend fun doTheJob2() = simpleAsync2 { <!ARGUMENT_TYPE_MISMATCH!><!NON_LOCAL_SUSPENSION_POINT!>insideJob2<!>()<!> }
|
||||||
suspend fun doTheJob3() = simpleAsync3 { <!ARGUMENT_TYPE_MISMATCH!>insideJob3()<!> }
|
suspend fun doTheJob3() = simpleAsync3 { <!ARGUMENT_TYPE_MISMATCH!>insideJob3()<!> }
|
||||||
|
|||||||
-39
@@ -1,39 +0,0 @@
|
|||||||
// !DIAGNOSTICS: -UNUSED_PARAMETER -UNCHECKED_CAST -UNUSED_LAMBDA_EXPRESSION
|
|
||||||
|
|
||||||
class Foo {
|
|
||||||
suspend operator fun <T> invoke(body: suspend (Int) -> T) = null as T
|
|
||||||
suspend fun <T> bar(body: suspend (Int) -> T) = null as T
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> runBlocking(block: suspend () -> T) = null as T
|
|
||||||
|
|
||||||
public inline fun <T, R> 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_IDENTICAL
|
||||||
// !DIAGNOSTICS: -UNUSED_PARAMETER -UNCHECKED_CAST -UNUSED_LAMBDA_EXPRESSION
|
// !DIAGNOSTICS: -UNUSED_PARAMETER -UNCHECKED_CAST -UNUSED_LAMBDA_EXPRESSION
|
||||||
|
|
||||||
class Foo {
|
class Foo {
|
||||||
|
|||||||
+1
-1
@@ -32,7 +32,7 @@ suspend fun calculate() = "OK"
|
|||||||
fun box() {
|
fun box() {
|
||||||
builder {
|
builder {
|
||||||
test {
|
test {
|
||||||
calculate()
|
<!NON_LOCAL_SUSPENSION_POINT!>calculate<!>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -32,7 +32,7 @@ suspend fun calculate() = "OK"
|
|||||||
fun box() {
|
fun box() {
|
||||||
builder {
|
builder {
|
||||||
test {
|
test {
|
||||||
calculate()
|
<!NON_LOCAL_SUSPENSION_POINT!>calculate<!>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-81
@@ -1,81 +0,0 @@
|
|||||||
// !DIAGNOSTICS: -UNUSED_PARAMETER
|
|
||||||
import Host.suspendFromObject
|
|
||||||
|
|
||||||
suspend fun suspendHere() = 1
|
|
||||||
suspend fun <T> another(a: T) = 1
|
|
||||||
|
|
||||||
object Host {
|
|
||||||
suspend fun suspendFromObject() = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> 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<String> {
|
|
||||||
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<Int> {
|
|
||||||
suspendHere()
|
|
||||||
suspendFromObject()
|
|
||||||
|
|
||||||
another(1)
|
|
||||||
another("")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_IDENTICAL
|
||||||
// !DIAGNOSTICS: -UNUSED_PARAMETER
|
// !DIAGNOSTICS: -UNUSED_PARAMETER
|
||||||
import Host.suspendFromObject
|
import Host.suspendFromObject
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -6,10 +6,10 @@ suspend fun foo() = 1
|
|||||||
suspend fun bar(
|
suspend fun bar(
|
||||||
x: Int = 2 + foo(),
|
x: Int = 2 + foo(),
|
||||||
y: suspend () -> Int = { foo() },
|
y: suspend () -> Int = { foo() },
|
||||||
z: () -> Int = { foo() },
|
z: () -> Int = { <!NON_LOCAL_SUSPENSION_POINT!>foo<!>() },
|
||||||
w: Int = myInline { foo() },
|
w: Int = myInline { foo() },
|
||||||
v: Any? = object {
|
v: Any? = object {
|
||||||
fun x() = foo()
|
fun x() = <!NON_LOCAL_SUSPENSION_POINT!>foo<!>()
|
||||||
suspend fun y() = foo()
|
suspend fun y() = foo()
|
||||||
}
|
}
|
||||||
) {}
|
) {}
|
||||||
|
|||||||
+6
@@ -3156,6 +3156,12 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
|
|||||||
token,
|
token,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
add(FirErrors.NON_LOCAL_SUSPENSION_POINT) { firDiagnostic ->
|
||||||
|
NonLocalSuspensionPointImpl(
|
||||||
|
firDiagnostic as FirPsiDiagnostic,
|
||||||
|
token,
|
||||||
|
)
|
||||||
|
}
|
||||||
add(FirJvmErrors.CONFLICTING_JVM_DECLARATIONS) { firDiagnostic ->
|
add(FirJvmErrors.CONFLICTING_JVM_DECLARATIONS) { firDiagnostic ->
|
||||||
ConflictingJvmDeclarationsImpl(
|
ConflictingJvmDeclarationsImpl(
|
||||||
firDiagnostic as FirPsiDiagnostic,
|
firDiagnostic as FirPsiDiagnostic,
|
||||||
|
|||||||
+4
@@ -2207,6 +2207,10 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
|
|||||||
abstract val suspendCallable: KtSymbol
|
abstract val suspendCallable: KtSymbol
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class NonLocalSuspensionPoint : KtFirDiagnostic<PsiElement>() {
|
||||||
|
override val diagnosticClass get() = NonLocalSuspensionPoint::class
|
||||||
|
}
|
||||||
|
|
||||||
abstract class ConflictingJvmDeclarations : KtFirDiagnostic<PsiElement>() {
|
abstract class ConflictingJvmDeclarations : KtFirDiagnostic<PsiElement>() {
|
||||||
override val diagnosticClass get() = ConflictingJvmDeclarations::class
|
override val diagnosticClass get() = ConflictingJvmDeclarations::class
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -3561,6 +3561,13 @@ internal class IllegalSuspendPropertyAccessImpl(
|
|||||||
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
|
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal class NonLocalSuspensionPointImpl(
|
||||||
|
firDiagnostic: FirPsiDiagnostic,
|
||||||
|
override val token: ValidityToken,
|
||||||
|
) : KtFirDiagnostic.NonLocalSuspensionPoint(), KtAbstractFirDiagnostic<PsiElement> {
|
||||||
|
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
|
||||||
|
}
|
||||||
|
|
||||||
internal class ConflictingJvmDeclarationsImpl(
|
internal class ConflictingJvmDeclarationsImpl(
|
||||||
firDiagnostic: FirPsiDiagnostic,
|
firDiagnostic: FirPsiDiagnostic,
|
||||||
override val token: ValidityToken,
|
override val token: ValidityToken,
|
||||||
|
|||||||
Reference in New Issue
Block a user