[FIR] Report more occurrences of NON_LOCAL_RETURN_NOT_ALLOWED
When an inline lambda is invoked or passed to another inline function, make sure non-local returns are allowed by all surrounding declarations. Otherwise, NON_LOCAL_RETURN_NOT_ALLOWED must be reported. ^KT-59884 Fixed ^KT-55319 Fixed
This commit is contained in:
+68
-8
@@ -21,13 +21,10 @@ import org.jetbrains.kotlin.fir.analysis.checkers.inlineCheckerExtension
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.isInlineOnly
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.unsubstitutedScope
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||
import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration
|
||||
import org.jetbrains.kotlin.fir.declarations.FirFunction
|
||||
import org.jetbrains.kotlin.fir.declarations.FirPropertyAccessor
|
||||
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.*
|
||||
import org.jetbrains.kotlin.fir.expressions.*
|
||||
import org.jetbrains.kotlin.fir.references.FirSuperReference
|
||||
import org.jetbrains.kotlin.fir.references.*
|
||||
import org.jetbrains.kotlin.fir.resolve.toSymbol
|
||||
import org.jetbrains.kotlin.fir.resolve.transformers.publishedApiEffectiveVisibility
|
||||
import org.jetbrains.kotlin.fir.scopes.getDirectOverriddenMembers
|
||||
@@ -145,8 +142,10 @@ object FirInlineDeclarationChecker : FirFunctionChecker() {
|
||||
valueParameter.isNoinline -> {
|
||||
FirErrors.USAGE_IS_NOT_INLINABLE
|
||||
}
|
||||
valueParameter.isCrossinline && !valueParameterOfOriginalInlineFunction.isCrossinline
|
||||
-> FirErrors.NON_LOCAL_RETURN_NOT_ALLOWED
|
||||
!valueParameterOfOriginalInlineFunction.isCrossinline &&
|
||||
(valueParameter.isCrossinline || !isNonLocalReturnAllowed(context, inlineFunction)) -> {
|
||||
FirErrors.NON_LOCAL_RETURN_NOT_ALLOWED
|
||||
}
|
||||
else -> continue
|
||||
}
|
||||
else -> FirErrors.USAGE_IS_NOT_INLINABLE
|
||||
@@ -164,7 +163,7 @@ object FirInlineDeclarationChecker : FirFunctionChecker() {
|
||||
reporter: DiagnosticReporter,
|
||||
) {
|
||||
if (receiverExpression == null) return
|
||||
val receiverSymbol = receiverExpression.toResolvedCallableSymbol() ?: return
|
||||
val receiverSymbol = receiverExpression.toResolvedCallableSymbol() as? FirValueParameterSymbol ?: return
|
||||
if (receiverSymbol in inlinableParameters) {
|
||||
if (!isInvokeOrInlineExtension(targetSymbol)) {
|
||||
reporter.reportOn(
|
||||
@@ -173,6 +172,13 @@ object FirInlineDeclarationChecker : FirFunctionChecker() {
|
||||
receiverSymbol,
|
||||
context
|
||||
)
|
||||
} else if (!receiverSymbol.isCrossinline && !isNonLocalReturnAllowed(context, inlineFunction)) {
|
||||
reporter.reportOn(
|
||||
receiverExpression.source ?: qualifiedAccessExpression.source,
|
||||
FirErrors.NON_LOCAL_RETURN_NOT_ALLOWED,
|
||||
receiverSymbol,
|
||||
context,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -474,6 +480,60 @@ object FirInlineDeclarationChecker : FirFunctionChecker() {
|
||||
reporter.reportOn(declaration.source, FirErrors.OVERRIDE_BY_INLINE, context)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isNonLocalReturnAllowed(context: CheckerContext, inlineFunction: FirFunction): Boolean {
|
||||
val declarations = context.containingDeclarations
|
||||
val inlineFunctionIndex = declarations.indexOf(inlineFunction)
|
||||
if (inlineFunctionIndex == -1) return true
|
||||
|
||||
for (i in (inlineFunctionIndex + 1) until declarations.size) {
|
||||
val declaration = declarations[i]
|
||||
|
||||
// Only consider containers which can change locality.
|
||||
if (declaration !is FirFunction && declaration !is FirClass) continue
|
||||
|
||||
// Anonymous functions are allowed if they are an argument to an inline function call,
|
||||
// and the associated anonymous function parameter allows non-local returns. Everything
|
||||
// else changes locality, and must not be allowed.
|
||||
val anonymousFunction = declaration as? FirAnonymousFunction ?: return false
|
||||
val (call, parameter) = extractCallAndParameter(context, anonymousFunction) ?: return false
|
||||
val callable = call.toResolvedCallableSymbol() as? FirFunctionSymbol<*> ?: return false
|
||||
if (!callable.isInline && !callable.isArrayLambdaConstructor()) return false
|
||||
if (parameter.isNoinline || parameter.isCrossinline) return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun extractCallAndParameter(
|
||||
context: CheckerContext,
|
||||
anonymousFunction: FirAnonymousFunction,
|
||||
): Pair<FirFunctionCall, FirValueParameter>? {
|
||||
for (call in context.callsOrAssignments) {
|
||||
if (call is FirFunctionCall) {
|
||||
val mapping = call.resolvedArgumentMapping ?: continue
|
||||
for ((argument, parameter) in mapping) {
|
||||
if ((argument.unwrapArgument() as? FirAnonymousFunctionExpression)?.anonymousFunction === anonymousFunction) {
|
||||
return call to parameter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the symbol is the constructor of one of 9 array classes (`Array<T>`,
|
||||
* `IntArray`, `FloatArray`, ...) which takes the size and an initializer lambda as parameters.
|
||||
* Such constructors are marked as `inline` but they are not loaded as such because the `inline`
|
||||
* flag is not stored for constructors in the binary metadata. Therefore, we pretend that they
|
||||
* are inline.
|
||||
*/
|
||||
private fun FirFunctionSymbol<*>.isArrayLambdaConstructor(): Boolean {
|
||||
return this is FirConstructorSymbol &&
|
||||
valueParameterSymbols.size == 2 &&
|
||||
resolvedReturnType.isArrayOrPrimitiveArray
|
||||
}
|
||||
}
|
||||
|
||||
fun createInlineFunctionBodyContext(function: FirFunction, session: FirSession): FirInlineDeclarationChecker.InlineFunctionBodyContext {
|
||||
|
||||
+3
-3
@@ -20,11 +20,11 @@ inline fun default0(lambda: () -> String, dlambda: () -> String = { noInlineFun
|
||||
lambda() + dlambda()
|
||||
}
|
||||
|
||||
inline fun default1_0(lambda: () -> String, dlambda: () -> String = { <!NOT_SUPPORTED_INLINE_PARAMETER_IN_INLINE_PARAMETER_DEFAULT_VALUE!>lambda<!>() }) {
|
||||
inline fun default1_0(lambda: () -> String, dlambda: () -> String = { <!NON_LOCAL_RETURN_NOT_ALLOWED, NOT_SUPPORTED_INLINE_PARAMETER_IN_INLINE_PARAMETER_DEFAULT_VALUE!>lambda<!>() }) {
|
||||
lambda() + dlambda()
|
||||
}
|
||||
|
||||
inline fun default1_1(lambda: () -> String, noinline dlambda: () -> String = { lambda() }) {
|
||||
inline fun default1_1(lambda: () -> String, noinline dlambda: () -> String = { <!NON_LOCAL_RETURN_NOT_ALLOWED!>lambda<!>() }) {
|
||||
lambda() + dlambda()
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ inline fun default1_3(noinline lambda: () -> String, noinline dlambda: () -> Str
|
||||
}
|
||||
|
||||
|
||||
inline fun default2_1(lambda: () -> String, noinline dlambda: () -> String = { inlineFun(lambda) }) {
|
||||
inline fun default2_1(lambda: () -> String, noinline dlambda: () -> String = { inlineFun(<!NON_LOCAL_RETURN_NOT_ALLOWED!>lambda<!>) }) {
|
||||
lambda() + dlambda()
|
||||
}
|
||||
|
||||
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_VARIABLE -NOTHING_TO_INLINE
|
||||
|
||||
inline fun <R> inlineFunOnlyLocal(crossinline p: () -> R) {
|
||||
val s = object {
|
||||
|
||||
val z = p()
|
||||
|
||||
fun a() {
|
||||
p()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <R> inlineFun(p: () -> R) {
|
||||
val s = object {
|
||||
|
||||
val z = p()
|
||||
|
||||
fun a() {
|
||||
p()
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_VARIABLE -NOTHING_TO_INLINE
|
||||
|
||||
inline fun <R> inlineFunOnlyLocal(crossinline p: () -> R) {
|
||||
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_VARIABLE -NOTHING_TO_INLINE
|
||||
|
||||
inline fun <R> inlineFunOnlyLocal(crossinline p: () -> R) {
|
||||
val s = object {
|
||||
|
||||
val z = p();
|
||||
|
||||
init {
|
||||
doCall {
|
||||
p()
|
||||
}
|
||||
}
|
||||
|
||||
fun a() {
|
||||
doCall {
|
||||
p()
|
||||
}
|
||||
|
||||
p()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <R> inlineFun(p: () -> R) {
|
||||
val s = object {
|
||||
|
||||
val z = p()
|
||||
|
||||
init {
|
||||
doCall {
|
||||
p()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun a() {
|
||||
doCall {
|
||||
p()
|
||||
}
|
||||
|
||||
p()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <R> doCall(p: () -> R) {
|
||||
p()
|
||||
}
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_VARIABLE -NOTHING_TO_INLINE
|
||||
|
||||
inline fun <R> inlineFunOnlyLocal(crossinline p: () -> R) {
|
||||
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
|
||||
inline fun testSameCaptured(lambdaWithResultCaptured: () -> Unit) : String {
|
||||
doWork({lambdaWithResultCaptured()})
|
||||
return "OK"
|
||||
}
|
||||
|
||||
inline fun <R> doWork(crossinline job: ()-> R) : R {
|
||||
return job()
|
||||
}
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
|
||||
inline fun testSameCaptured(lambdaWithResultCaptured: () -> Unit) : String {
|
||||
doWork({<!NON_LOCAL_RETURN_NOT_ALLOWED!>lambdaWithResultCaptured<!>()})
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_VARIABLE -NOTHING_TO_INLINE
|
||||
|
||||
inline fun <R> inlineFunOnlyLocal(crossinline p: () -> R) {
|
||||
<!NOT_YET_SUPPORTED_IN_INLINE!>fun<!> a() {
|
||||
val z = p()
|
||||
}
|
||||
a()
|
||||
}
|
||||
|
||||
inline fun <R> inlineFun(p: () -> R) {
|
||||
<!NOT_YET_SUPPORTED_IN_INLINE!>fun<!> a() {
|
||||
p()
|
||||
}
|
||||
a()
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_VARIABLE -NOTHING_TO_INLINE
|
||||
|
||||
inline fun <R> inlineFunOnlyLocal(crossinline p: () -> R) {
|
||||
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_VARIABLE -NOTHING_TO_INLINE
|
||||
|
||||
inline fun <R> inlineFunOnlyLocal(crossinline p: () -> R) {
|
||||
{
|
||||
p()
|
||||
}()
|
||||
}
|
||||
|
||||
inline fun <R> inlineFun(p: () -> R) {
|
||||
{
|
||||
p()
|
||||
}()
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_VARIABLE -NOTHING_TO_INLINE
|
||||
|
||||
inline fun <R> inlineFunOnlyLocal(crossinline p: () -> R) {
|
||||
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_VARIABLE -NOTHING_TO_INLINE
|
||||
|
||||
inline fun <R> inlineFunOnlyLocal(crossinline p: () -> R) {
|
||||
<!NOT_YET_SUPPORTED_IN_INLINE!>class<!> A {
|
||||
|
||||
val z = p()
|
||||
|
||||
fun a() {
|
||||
p()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <R> inlineFun(p: () -> R) {
|
||||
<!NOT_YET_SUPPORTED_IN_INLINE!>class<!> A {
|
||||
|
||||
val z = p()
|
||||
|
||||
fun a() {
|
||||
p()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_VARIABLE -NOTHING_TO_INLINE
|
||||
|
||||
inline fun <R> inlineFunOnlyLocal(crossinline p: () -> R) {
|
||||
|
||||
+2
-2
@@ -18,10 +18,10 @@ inline fun test(c: () -> Unit) {
|
||||
c()
|
||||
val o = object : Runnable {
|
||||
override fun run() {
|
||||
c()
|
||||
<!NON_LOCAL_RETURN_NOT_ALLOWED!>c<!>()
|
||||
}
|
||||
}
|
||||
val l = { c() }
|
||||
val l = { <!NON_LOCAL_RETURN_NOT_ALLOWED!>c<!>() }
|
||||
<!USAGE_IS_NOT_INLINABLE!>c<!>.startCoroutine(EmptyContinuation)
|
||||
}
|
||||
|
||||
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
// !LANGUAGE: +ForbidExtensionCallsOnInlineFunctionalParameters
|
||||
// !DIAGNOSTICS: -UNUSED_VARIABLE -NOTHING_TO_INLINE -UNUSED_PARAMETER
|
||||
// SKIP_TXT
|
||||
// WITH_COROUTINES
|
||||
import kotlin.coroutines.*
|
||||
import kotlin.coroutines.intrinsics.*
|
||||
import helpers.*
|
||||
|
||||
interface SuspendRunnable {
|
||||
suspend fun run()
|
||||
}
|
||||
|
||||
// Function is NOT suspend
|
||||
// parameter is inline
|
||||
// parameter is suspend
|
||||
// Block is NOT allowed to be called inside the body of owner inline function
|
||||
// Block is NOT allowed to be called from nested classes/lambdas (as common crossinlines)
|
||||
// It is NOT possible to call startCoroutine on the parameter
|
||||
// suspend calls possible inside lambda matching to the parameter
|
||||
|
||||
inline fun test(<!INLINE_SUSPEND_FUNCTION_TYPE_UNSUPPORTED!>c: suspend () -> Unit<!>) {
|
||||
<!ILLEGAL_SUSPEND_FUNCTION_CALL!>c<!>()
|
||||
val o = object: SuspendRunnable {
|
||||
override suspend fun run() {
|
||||
c()
|
||||
}
|
||||
}
|
||||
val l: suspend () -> Unit = { c() }
|
||||
<!USAGE_IS_NOT_INLINABLE!>c<!>.startCoroutine(EmptyContinuation)
|
||||
}
|
||||
|
||||
fun builder(c: suspend () -> Unit) {}
|
||||
|
||||
suspend fun calculate() = "OK"
|
||||
|
||||
fun box() {
|
||||
test {
|
||||
calculate()
|
||||
}
|
||||
}
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !LANGUAGE: +ForbidExtensionCallsOnInlineFunctionalParameters
|
||||
// !DIAGNOSTICS: -UNUSED_VARIABLE -NOTHING_TO_INLINE -UNUSED_PARAMETER
|
||||
// SKIP_TXT
|
||||
|
||||
+2
-2
@@ -16,10 +16,10 @@ suspend inline fun test(c: () -> Unit) {
|
||||
c()
|
||||
val o = object: Runnable {
|
||||
override fun run() {
|
||||
c()
|
||||
<!NON_LOCAL_RETURN_NOT_ALLOWED!>c<!>()
|
||||
}
|
||||
}
|
||||
val l = { c() }
|
||||
val l = { <!NON_LOCAL_RETURN_NOT_ALLOWED!>c<!>() }
|
||||
<!USAGE_IS_NOT_INLINABLE!>c<!>.startCoroutine(EmptyContinuation)
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -21,10 +21,10 @@ suspend inline fun test(c: suspend () -> Unit) {
|
||||
c()
|
||||
val o = object: SuspendRunnable {
|
||||
override suspend fun run() {
|
||||
c()
|
||||
<!NON_LOCAL_RETURN_NOT_ALLOWED!>c<!>()
|
||||
}
|
||||
}
|
||||
val l: suspend () -> Unit = { c() }
|
||||
val l: suspend () -> Unit = { <!NON_LOCAL_RETURN_NOT_ALLOWED!>c<!>() }
|
||||
<!USAGE_IS_NOT_INLINABLE!>c<!>.startCoroutine(EmptyContinuation)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user