From d66be3f82d42de8d9ab49a0c8c4eef04201d26bc Mon Sep 17 00:00:00 2001 From: pyos Date: Wed, 23 Nov 2022 16:02:11 +0100 Subject: [PATCH] FIR CFG: do not assume all arguments to inline funs are called in place Only values for non-noinline, non-crossinline, functional type parameters qualify. --- ...nosisCompilerFirTestdataTestGenerated.java | 6 + ...TouchedTilContractsPhaseTestGenerated.java | 5 + .../cfg/inlineLambdasAreInplace.fir.txt | 55 +++++++++ .../resolve/cfg/inlineLambdasAreInplace.kt | 38 ++++++ .../testData/resolve/cfg/postponedLambdas.dot | 2 - .../resolve/cfg/postponedLambdas.fir.txt | 2 +- .../lambdaWithoutExpectedType.fir.txt | 4 +- .../delegates/delegateTypeMismatch.fir.txt | 2 +- .../inference/plusAssignWithLambdaInRhs.dot | 6 +- .../plusAssignWithLambdaInRhs.fir.txt | 4 +- .../inference/problems/buildListLazy.fir.txt | 2 +- .../runners/FirDiagnosticTestGenerated.java | 6 + ...DiagnosticsWithLightTreeTestGenerated.java | 6 + .../fir/resolve/dfa/FirDataFlowAnalyzer.kt | 9 +- .../fir/resolve/inference/FirCallCompleter.kt | 11 +- .../transformers/InvocationKindTransformer.kt | 115 +++++------------- .../FirExpressionsResolveTransformer.kt | 4 +- 17 files changed, 165 insertions(+), 112 deletions(-) create mode 100644 compiler/fir/analysis-tests/testData/resolve/cfg/inlineLambdasAreInplace.fir.txt create mode 100644 compiler/fir/analysis-tests/testData/resolve/cfg/inlineLambdasAreInplace.kt diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerFirTestdataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerFirTestdataTestGenerated.java index ccbf506a6a4..5d3e6055d07 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerFirTestdataTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerFirTestdataTestGenerated.java @@ -1159,6 +1159,12 @@ public class DiagnosisCompilerFirTestdataTestGenerated extends AbstractDiagnosis runTest("compiler/fir/analysis-tests/testData/resolve/cfg/initBlockAndInPlaceLambda.kt"); } + @Test + @TestMetadata("inlineLambdasAreInplace.kt") + public void testInlineLambdasAreInplace() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/cfg/inlineLambdasAreInplace.kt"); + } + @Test @TestMetadata("innerClassInAnonymousObject.kt") public void testInnerClassInAnonymousObject() throws Exception { diff --git a/compiler/fir/analysis-tests/legacy-fir-tests/tests-gen/org/jetbrains/kotlin/fir/LazyBodyIsNotTouchedTilContractsPhaseTestGenerated.java b/compiler/fir/analysis-tests/legacy-fir-tests/tests-gen/org/jetbrains/kotlin/fir/LazyBodyIsNotTouchedTilContractsPhaseTestGenerated.java index 5121b1d1ada..c58d25e00c4 100644 --- a/compiler/fir/analysis-tests/legacy-fir-tests/tests-gen/org/jetbrains/kotlin/fir/LazyBodyIsNotTouchedTilContractsPhaseTestGenerated.java +++ b/compiler/fir/analysis-tests/legacy-fir-tests/tests-gen/org/jetbrains/kotlin/fir/LazyBodyIsNotTouchedTilContractsPhaseTestGenerated.java @@ -1001,6 +1001,11 @@ public class LazyBodyIsNotTouchedTilContractsPhaseTestGenerated extends Abstract runTest("compiler/fir/analysis-tests/testData/resolve/cfg/initBlockAndInPlaceLambda.kt"); } + @TestMetadata("inlineLambdasAreInplace.kt") + public void testInlineLambdasAreInplace() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/cfg/inlineLambdasAreInplace.kt"); + } + @TestMetadata("innerClassInAnonymousObject.kt") public void testInnerClassInAnonymousObject() throws Exception { runTest("compiler/fir/analysis-tests/testData/resolve/cfg/innerClassInAnonymousObject.kt"); diff --git a/compiler/fir/analysis-tests/testData/resolve/cfg/inlineLambdasAreInplace.fir.txt b/compiler/fir/analysis-tests/testData/resolve/cfg/inlineLambdasAreInplace.fir.txt new file mode 100644 index 00000000000..7a8b42d1a95 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/cfg/inlineLambdasAreInplace.fir.txt @@ -0,0 +1,55 @@ +FILE: inlineLambdasAreInplace.kt + public final inline fun callsInPlaceInline(x: R|() -> kotlin/Unit|): R|kotlin/Unit| { + ^callsInPlaceInline R|/x|.R|SubstitutionOverride|() + } + public final inline fun any(x: R|T|): R|T| { + ^any R|/x| + } + public final inline fun noinline(noinline x: R|() -> kotlin/Unit|): R|() -> kotlin/Unit| { + ^noinline R|/x| + } + public final inline fun crossinline(crossinline x: R|() -> kotlin/Unit|): R|() -> kotlin/Unit| { + ^crossinline fun (): R|kotlin/Unit| { + R|/x|.R|SubstitutionOverride|() + } + + } + public final fun testInline(): R|kotlin/Unit| { + lvar x: R|kotlin/String?| = Null(null) + R|/callsInPlaceInline|( = callsInPlaceInline@fun (): R|kotlin/Unit| { + R|/x| = Null(null) + } + ) + R|/x| = String() + R|/x|.R|kotlin/String.length| + } + public final fun testGeneric(): R|kotlin/Unit| { + lvar x: R|kotlin/String?| = String() + lval lambda: R|() -> kotlin/Unit| = R|/any| kotlin/Unit|>( = any@fun (): R|kotlin/Unit| { + R|/x| = Null(null) + } + ) + R|/x| = String() + R|/lambda|.R|SubstitutionOverride|() + R|/x|.# + } + public final fun testNoinline(): R|kotlin/Unit| { + lvar x: R|kotlin/String?| = String() + lval lambda: R|() -> kotlin/Unit| = R|/noinline|( = noinline@fun (): R|kotlin/Unit| { + R|/x| = Null(null) + } + ) + R|/x| = String() + R|/lambda|.R|SubstitutionOverride|() + R|/x|.# + } + public final fun testCrossinline(): R|kotlin/Unit| { + lvar x: R|kotlin/String?| = String() + lval lambda: R|() -> kotlin/Unit| = R|/crossinline|( = crossinline@fun (): R|kotlin/Unit| { + R|/x| = Null(null) + } + ) + R|/x| = String() + R|/lambda|.R|SubstitutionOverride|() + R|/x|.# + } diff --git a/compiler/fir/analysis-tests/testData/resolve/cfg/inlineLambdasAreInplace.kt b/compiler/fir/analysis-tests/testData/resolve/cfg/inlineLambdasAreInplace.kt new file mode 100644 index 00000000000..989a25c0e30 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/cfg/inlineLambdasAreInplace.kt @@ -0,0 +1,38 @@ +inline fun callsInPlaceInline(x: () -> Unit) = x() + +inline fun any(x: T) = x + +inline fun noinline(noinline x: () -> Unit) = x + +inline fun crossinline(crossinline x: () -> Unit) = { x() } + +fun testInline() { + var x: String? = null + callsInPlaceInline { x = null } + x = "" + x.length // ok +} + +fun testGeneric() { + var x: String? = "" + val lambda = any { x = null } + x = "" + lambda() + x.length // bad +} + +fun testNoinline() { + var x: String? = "" + val lambda = noinline { x = null } + x = "" + lambda() + x.length // bad +} + +fun testCrossinline() { + var x: String? = "" + val lambda = crossinline { x = null } + x = "" + lambda() + x.length // bad +} diff --git a/compiler/fir/analysis-tests/testData/resolve/cfg/postponedLambdas.dot b/compiler/fir/analysis-tests/testData/resolve/cfg/postponedLambdas.dot index 03fda8ba06c..366ebd8df13 100644 --- a/compiler/fir/analysis-tests/testData/resolve/cfg/postponedLambdas.dot +++ b/compiler/fir/analysis-tests/testData/resolve/cfg/postponedLambdas.dot @@ -50,13 +50,11 @@ digraph postponedLambdas_kt { 8 -> {9 13 10}; 8 -> {13} [style=dashed]; 9 -> {10}; - 9 -> {8} [color=green style=dashed]; 10 -> {11}; 11 -> {12}; 13 -> {14}; 14 -> {15}; 15 -> {16}; 16 -> {17}; - 17 -> {9}; } diff --git a/compiler/fir/analysis-tests/testData/resolve/cfg/postponedLambdas.fir.txt b/compiler/fir/analysis-tests/testData/resolve/cfg/postponedLambdas.fir.txt index 79a57ee68a0..3a365515095 100644 --- a/compiler/fir/analysis-tests/testData/resolve/cfg/postponedLambdas.fir.txt +++ b/compiler/fir/analysis-tests/testData/resolve/cfg/postponedLambdas.fir.txt @@ -2,7 +2,7 @@ FILE: postponedLambdas.kt public final inline fun foo(vararg x: R|kotlin/Array|): R|kotlin/Unit| { } public final fun test(a: R|kotlin/Any|, b: R|kotlin/Any|, c: R|kotlin/Any|): R|kotlin/Unit| { - R|/foo|(vararg(R|/a|, foo@fun (): R|kotlin/String| { + R|/foo|(vararg(R|/a|, foo@fun (): R|kotlin/String| { ^ String() } , R|/b|)) diff --git a/compiler/fir/analysis-tests/testData/resolve/inference/lambdaWithoutExpectedType.fir.txt b/compiler/fir/analysis-tests/testData/resolve/inference/lambdaWithoutExpectedType.fir.txt index 43fcff178bb..4831b34f019 100644 --- a/compiler/fir/analysis-tests/testData/resolve/inference/lambdaWithoutExpectedType.fir.txt +++ b/compiler/fir/analysis-tests/testData/resolve/inference/lambdaWithoutExpectedType.fir.txt @@ -47,13 +47,13 @@ FILE: lambdaWithoutExpectedType.kt public abstract interface XdIssueFolder : R|kotlin/Any| { } public final fun test(): R|kotlin/Unit| { - lval array: R|kotlin/Array| = R|kotlin/arrayOf|(vararg(String(Project), arrayOf@fun (): R|XdProject?| { + lval array: R|kotlin/Array| = R|kotlin/arrayOf|(vararg(String(Project), arrayOf@fun (): R|XdProject?| { ^ Q|XdProject|.R|/filter|( = filter@fun R|FilteringContext|.(it: R|XdProject|): R|XdSearchingNode| { ^ (this@R|special/anonymous|, R|/it|.R|/XdProject.name|).R|/FilteringContext.eq|(String(foo)) } ).R|/firstOrNull|() } - , arrayOf@fun (: R|XdIssue|, : R|XdIssueFolder|): R|kotlin/Unit| { + , arrayOf@fun (: R|XdIssue|, : R|XdIssueFolder|): R|kotlin/Unit| { ^@arrayOf Unit } )) diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/delegates/delegateTypeMismatch.fir.txt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/delegates/delegateTypeMismatch.fir.txt index 7b0db5a9c27..79fa7c96839 100644 --- a/compiler/fir/analysis-tests/testData/resolveWithStdlib/delegates/delegateTypeMismatch.fir.txt +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/delegates/delegateTypeMismatch.fir.txt @@ -19,7 +19,7 @@ FILE: delegateTypeMismatch.kt public get(): R|kotlin/Boolean| private final fun property(initialValue: R|T|): R|kotlin/properties/ReadWriteProperty| { - ^property Q|kotlin/properties/Delegates|.R|kotlin/properties/Delegates.vetoable|(R|/initialValue|, = vetoable@fun (: R|@R|kotlin/ParameterName|(name = String(property)) kotlin/reflect/KProperty<*>|, : R|T|, : R|T|): R|kotlin/Boolean| { + ^property Q|kotlin/properties/Delegates|.R|kotlin/properties/Delegates.vetoable|(R|/initialValue|, = vetoable@fun (: R|@R|kotlin/ParameterName|(name = String(property)) kotlin/reflect/KProperty<*>|, : R|T|, : R|T|): R|kotlin/Boolean| { ^ when () { this@R|/A|.R|/A.isLocked| -> { throw R|java/lang/IllegalStateException.IllegalStateException|(String(Cannot modify readonly DescriptorRendererOptions)) diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/inference/plusAssignWithLambdaInRhs.dot b/compiler/fir/analysis-tests/testData/resolveWithStdlib/inference/plusAssignWithLambdaInRhs.dot index 2309a07a934..b3fd57f9b4d 100644 --- a/compiler/fir/analysis-tests/testData/resolveWithStdlib/inference/plusAssignWithLambdaInRhs.dot +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/inference/plusAssignWithLambdaInRhs.dot @@ -43,14 +43,12 @@ digraph plusAssignWithLambdaInRhs_kt { 7 -> {8 12 9} [style=dotted]; 7 -> {12} [style=dashed]; 8 -> {9} [style=dotted]; - 8 -> {7} [color=green style=dotted]; 9 -> {10} [style=dotted]; 10 -> {11} [style=dotted]; 12 -> {13} [style=dotted]; 13 -> {14} [style=dotted]; 14 -> {15} [style=dotted]; 15 -> {16} [style=dotted]; - 16 -> {8} [style=dotted]; subgraph cluster_4 { color=red @@ -120,7 +118,7 @@ digraph plusAssignWithLambdaInRhs_kt { 31 [label="Postponed exit from lambda"]; 32 [label="Function call: R|/A.A|(...)" style="filled" fillcolor=yellow]; 33 [label="Jump: ^postpone R|/A.A|( = A@fun (resolve: R|(T) -> kotlin/Unit|): R|kotlin/Unit| { - R|/queue|.R|kotlin/collections/plusAssign| kotlin/Unit|>(A@fun (): R|kotlin/Unit| { + R|/queue|.R|kotlin/collections/plusAssign| kotlin/Unit|>(A@fun (): R|kotlin/Unit| { R|/resolve|.R|SubstitutionOverride|(R|/computation|.R|SubstitutionOverride|()) } ) @@ -149,7 +147,6 @@ digraph plusAssignWithLambdaInRhs_kt { 40 -> {41 45 42}; 40 -> {45} [style=dashed]; 41 -> {42}; - 41 -> {40} [color=green style=dashed]; 42 -> {43}; 43 -> {44}; 45 -> {46}; @@ -157,6 +154,5 @@ digraph plusAssignWithLambdaInRhs_kt { 47 -> {48}; 48 -> {49}; 49 -> {50}; - 50 -> {41}; } diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/inference/plusAssignWithLambdaInRhs.fir.txt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/inference/plusAssignWithLambdaInRhs.fir.txt index a7ace331144..fc35d7979d2 100644 --- a/compiler/fir/analysis-tests/testData/resolveWithStdlib/inference/plusAssignWithLambdaInRhs.fir.txt +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/inference/plusAssignWithLambdaInRhs.fir.txt @@ -1,7 +1,7 @@ FILE: plusAssignWithLambdaInRhs.kt public final fun test(): R|kotlin/Unit| { lval list: R|kotlin/collections/MutableList>| = Null(null)!! - R|/list|.R|kotlin/collections/plusAssign| kotlin/String|>(fun (it: R|kotlin/String|): R|kotlin/String| { + R|/list|.R|kotlin/collections/plusAssign| kotlin/String|>(fun (it: R|kotlin/String|): R|kotlin/String| { ^ R|/it| } ) @@ -18,7 +18,7 @@ FILE: plusAssignWithLambdaInRhs.kt public final fun postpone(computation: R|() -> T|): R|A| { lval queue: R|kotlin/collections/MutableList>| = R|kotlin/collections/mutableListOf| kotlin/Unit|>() ^postpone R|/A.A|( = A@fun (resolve: R|(T) -> kotlin/Unit|): R|kotlin/Unit| { - R|/queue|.R|kotlin/collections/plusAssign| kotlin/Unit|>(A@fun (): R|kotlin/Unit| { + R|/queue|.R|kotlin/collections/plusAssign| kotlin/Unit|>(A@fun (): R|kotlin/Unit| { R|/resolve|.R|SubstitutionOverride|(R|/computation|.R|SubstitutionOverride|()) } ) diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/inference/problems/buildListLazy.fir.txt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/inference/problems/buildListLazy.fir.txt index 140d26fad41..7865e005f3f 100644 --- a/compiler/fir/analysis-tests/testData/resolveWithStdlib/inference/problems/buildListLazy.fir.txt +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/inference/problems/buildListLazy.fir.txt @@ -28,7 +28,7 @@ FILE: buildListLazy.kt this@R|special/anonymous|.R|kotlin/collections/plusAssign|(R|/NameAndSafeValue.NameAndSafeValue|(R|/name|, R|/value|)) } ) - this@R|special/anonymous|.R|kotlin/collections/sortBy|( = sortBy@fun (it: R|NameAndSafeValue|): R|kotlin/String?| { + this@R|special/anonymous|.R|kotlin/collections/sortBy|( = sortBy@fun (it: R|NameAndSafeValue|): R|kotlin/String?| { ^ R|/it|.R|/NameAndSafeValue.name| } ) diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirDiagnosticTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirDiagnosticTestGenerated.java index 6b743fee875..2036aec3006 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirDiagnosticTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirDiagnosticTestGenerated.java @@ -1159,6 +1159,12 @@ public class FirDiagnosticTestGenerated extends AbstractFirDiagnosticTest { runTest("compiler/fir/analysis-tests/testData/resolve/cfg/initBlockAndInPlaceLambda.kt"); } + @Test + @TestMetadata("inlineLambdasAreInplace.kt") + public void testInlineLambdasAreInplace() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/cfg/inlineLambdasAreInplace.kt"); + } + @Test @TestMetadata("innerClassInAnonymousObject.kt") public void testInnerClassInAnonymousObject() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirDiagnosticsWithLightTreeTestGenerated.java index 626f85a64fe..21aac4a2ae1 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirDiagnosticsWithLightTreeTestGenerated.java @@ -1159,6 +1159,12 @@ public class FirDiagnosticsWithLightTreeTestGenerated extends AbstractFirDiagnos runTest("compiler/fir/analysis-tests/testData/resolve/cfg/initBlockAndInPlaceLambda.kt"); } + @Test + @TestMetadata("inlineLambdasAreInplace.kt") + public void testInlineLambdasAreInplace() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/cfg/inlineLambdasAreInplace.kt"); + } + @Test @TestMetadata("innerClassInAnonymousObject.kt") public void testInnerClassInAnonymousObject() throws Exception { diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt index dcf61365ad5..e3082f20d0b 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt @@ -23,6 +23,7 @@ import org.jetbrains.kotlin.fir.resolve.dfa.cfg.* import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutorByMap import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformer import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.resultType +import org.jetbrains.kotlin.fir.resolve.transformers.unwrapAnonymousFunctionExpression import org.jetbrains.kotlin.fir.scopes.getFunctions import org.jetbrains.kotlin.fir.scopes.impl.declaredMemberScope import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol @@ -808,12 +809,6 @@ abstract class FirDataFlowAnalyzer( graphBuilder.exitResolvedQualifierNode(resolvedQualifier).mergeIncomingFlow() } - private tailrec fun FirExpression.getAnonymousFunction(): FirAnonymousFunction? = when (this) { - is FirAnonymousFunctionExpression -> anonymousFunction - is FirLambdaArgumentExpression -> expression.getAnonymousFunction() - else -> null - } - private var functionCallLevel = 0 private var resolvingAugmentedAssignmentOptions: Boolean = false @@ -844,7 +839,7 @@ abstract class FirDataFlowAnalyzer( } fun enterFunctionCall(functionCall: FirFunctionCall) { - val lambdaArgs = functionCall.arguments.mapNotNullTo(mutableSetOf()) { it.getAnonymousFunction() } + val lambdaArgs = functionCall.arguments.mapNotNullTo(mutableSetOf()) { it.unwrapAnonymousFunctionExpression() } val localVariableAssignmentAnalyzer = context.firLocalVariableAssignmentAnalyzer ?: if (lambdaArgs.isNotEmpty()) getOrCreateLocalVariableAssignmentAnalyzer(lambdaArgs.first()) else null localVariableAssignmentAnalyzer?.enterFunctionCall(lambdaArgs, functionCallLevel) diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/FirCallCompleter.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/FirCallCompleter.kt index 15cc50c3325..de7218e40ca 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/FirCallCompleter.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/FirCallCompleter.kt @@ -11,9 +11,7 @@ import org.jetbrains.kotlin.fir.* import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin import org.jetbrains.kotlin.fir.declarations.FirFunction -import org.jetbrains.kotlin.fir.declarations.FirReceiverParameter import org.jetbrains.kotlin.fir.declarations.builder.buildContextReceiver -import org.jetbrains.kotlin.fir.declarations.builder.buildReceiverParameterCopy import org.jetbrains.kotlin.fir.declarations.builder.buildValueParameter import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.resolve.ResolutionMode @@ -24,11 +22,10 @@ import org.jetbrains.kotlin.fir.resolve.inference.model.ConeExpectedTypeConstrai import org.jetbrains.kotlin.fir.resolve.initialTypeOfCandidate import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor import org.jetbrains.kotlin.fir.resolve.transformers.FirCallCompletionResultsWriterTransformer -import org.jetbrains.kotlin.fir.resolve.transformers.InvocationKindTransformer import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformer import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformerDispatcher -import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirBodyResolveTransformer import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.resultType +import org.jetbrains.kotlin.fir.resolve.transformers.replaceLambdaArgumentInvocationKinds import org.jetbrains.kotlin.fir.resolve.typeFromCallee import org.jetbrains.kotlin.fir.symbols.impl.FirValueParameterSymbol import org.jetbrains.kotlin.fir.types.* @@ -45,8 +42,6 @@ import org.jetbrains.kotlin.types.model.StubTypeMarker import org.jetbrains.kotlin.types.model.TypeVariableMarker import org.jetbrains.kotlin.types.model.safeSubstitute import org.jetbrains.kotlin.utils.addToStdlib.runIf -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract class FirCallCompleter( private val transformer: FirAbstractBodyResolveTransformerDispatcher, @@ -123,7 +118,9 @@ class FirCallCompleter( val completionMode = candidate.computeCompletionMode(session.inferenceComponents, expectedTypeRef, initialType) val analyzer = createPostponedArgumentsAnalyzer(transformer.resolutionContext) - call.transformSingle(InvocationKindTransformer, null) + if (call is FirFunctionCall) { + call.replaceLambdaArgumentInvocationKinds(session) + } return when (completionMode) { ConstraintSystemCompletionMode.FULL -> { diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/InvocationKindTransformer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/InvocationKindTransformer.kt index 54c422ef6da..bd2642ef258 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/InvocationKindTransformer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/InvocationKindTransformer.kt @@ -6,98 +6,49 @@ package org.jetbrains.kotlin.fir.resolve.transformers import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange -import org.jetbrains.kotlin.fir.FirElement +import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.contracts.description.ConeCallsEffectDeclaration import org.jetbrains.kotlin.fir.contracts.effects import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction +import org.jetbrains.kotlin.fir.declarations.FirValueParameter import org.jetbrains.kotlin.fir.declarations.utils.isInline -import org.jetbrains.kotlin.fir.expressions.* +import org.jetbrains.kotlin.fir.expressions.FirAnonymousFunctionExpression +import org.jetbrains.kotlin.fir.expressions.FirExpression +import org.jetbrains.kotlin.fir.expressions.FirFunctionCall +import org.jetbrains.kotlin.fir.expressions.FirWrappedArgumentExpression import org.jetbrains.kotlin.fir.resolve.calls.FirNamedReferenceWithCandidate -import org.jetbrains.kotlin.fir.visitors.FirTransformer +import org.jetbrains.kotlin.fir.types.coneType +import org.jetbrains.kotlin.fir.types.isFunctionalOrSuspendFunctionalType -object InvocationKindTransformer : FirTransformer() { - private object ArgumentsTransformer : FirTransformer, EventOccurrencesRange?>>() { - override fun transformElement(element: E, data: Pair, EventOccurrencesRange?>): E { - return element - } +tailrec fun FirExpression.unwrapAnonymousFunctionExpression(): FirAnonymousFunction? = when (this) { + is FirAnonymousFunctionExpression -> anonymousFunction + is FirWrappedArgumentExpression -> expression.unwrapAnonymousFunctionExpression() + else -> null +} - override fun transformAnonymousFunctionExpression( - anonymousFunctionExpression: FirAnonymousFunctionExpression, - data: Pair, EventOccurrencesRange?> - ): FirStatement { - val kind = data.second ?: data.first[anonymousFunctionExpression] - return anonymousFunctionExpression.transformAnonymousFunction(this, emptyMap() to kind) - } +fun FirFunctionCall.replaceLambdaArgumentInvocationKinds(session: FirSession) { + val calleeReference = calleeReference as? FirNamedReferenceWithCandidate ?: return + val argumentMapping = calleeReference.candidate.argumentMapping ?: return + val function = calleeReference.candidateSymbol.fir as? FirSimpleFunction ?: return + val isInline = function.isInline - override fun transformAnonymousFunction( - anonymousFunction: FirAnonymousFunction, - data: Pair, EventOccurrencesRange?> - ): FirStatement { - val kind = data.second - if (kind != null) { - anonymousFunction.replaceInvocationKind(kind) - } - return anonymousFunction - } - - override fun transformLambdaArgumentExpression( - lambdaArgumentExpression: FirLambdaArgumentExpression, - data: Pair, EventOccurrencesRange?> - ): FirStatement { - return data.first[lambdaArgumentExpression]?.let { - (lambdaArgumentExpression.transformChildren(this, data.first to it) as FirStatement) - } ?: lambdaArgumentExpression - } - - override fun transformNamedArgumentExpression( - namedArgumentExpression: FirNamedArgumentExpression, - data: Pair, EventOccurrencesRange?> - ): FirStatement { - return data.first[namedArgumentExpression]?.let { - (namedArgumentExpression.transformChildren(this, data.first to it) as FirStatement) - } ?: namedArgumentExpression - } + val byParameter = mutableMapOf() + function.contractDescription.effects?.forEach { fir -> + val effect = fir.effect as? ConeCallsEffectDeclaration ?: return@forEach + // TODO: Support callsInPlace contracts on receivers + val valueParameter = function.valueParameters.getOrNull(effect.valueParameterReference.parameterIndex) ?: return@forEach + byParameter[valueParameter] = effect.kind } + if (byParameter.isEmpty() && !isInline) return - override fun transformElement(element: E, data: Any?): E { - return element - } - - override fun transformFunctionCall(functionCall: FirFunctionCall, data: Any?): FirStatement { - val calleeReference = functionCall.calleeReference as? FirNamedReferenceWithCandidate ?: return functionCall - val argumentMapping = calleeReference.candidate.argumentMapping ?: return functionCall - val function = calleeReference.candidateSymbol.fir as? FirSimpleFunction ?: return functionCall - - val callsEffects = function.contractDescription.effects - ?.map { it.effect } - ?.filterIsInstance() ?: emptyList() - - val isInline = function.isInline - if (callsEffects.isEmpty() && !isInline) { - return functionCall - } - - val reversedArgumentMapping = argumentMapping.entries.map { (argument, parameter) -> - parameter to argument - }.toMap() - - val invocationKindMapping = mutableMapOf() - for (effect in callsEffects) { - // TODO: Support callsInPlace contracts on receivers - val valueParameter = function.valueParameters.getOrNull(effect.valueParameterReference.parameterIndex) ?: continue - val argument = reversedArgumentMapping[valueParameter] ?: continue - invocationKindMapping[argument] = effect.kind - } - if (isInline) { - for (argument in functionCall.arguments) { - invocationKindMapping.putIfAbsent(argument, EventOccurrencesRange.UNKNOWN) - } - } - if (invocationKindMapping.isEmpty()) { - return functionCall - } - functionCall.argumentList.transformArguments(ArgumentsTransformer, invocationKindMapping to null) - return functionCall + for ((argument, parameter) in argumentMapping) { + val lambda = argument.unwrapAnonymousFunctionExpression() ?: continue + val kind = byParameter[parameter] ?: EventOccurrencesRange.UNKNOWN.takeIf { + // Inline functional parameters have to be called in-place; that's the only permitted operation on them. + isInline && !parameter.isNoinline && !parameter.isCrossinline && + parameter.returnTypeRef.coneType.isFunctionalOrSuspendFunctionalType(session) + } ?: continue + lambda.replaceInvocationKind(kind) } } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirExpressionsResolveTransformer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirExpressionsResolveTransformer.kt index a86683fbb5d..214f6ce4393 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirExpressionsResolveTransformer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirExpressionsResolveTransformer.kt @@ -31,8 +31,8 @@ import org.jetbrains.kotlin.fir.resolve.dfa.unwrapSmartcastExpression import org.jetbrains.kotlin.fir.resolve.diagnostics.* import org.jetbrains.kotlin.fir.resolve.inference.FirStubInferenceSession import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor -import org.jetbrains.kotlin.fir.resolve.transformers.InvocationKindTransformer import org.jetbrains.kotlin.fir.resolve.transformers.StoreReceiver +import org.jetbrains.kotlin.fir.resolve.transformers.replaceLambdaArgumentInvocationKinds import org.jetbrains.kotlin.fir.scopes.impl.isWrappedIntegerOperator import org.jetbrains.kotlin.fir.scopes.impl.isWrappedIntegerOperatorForUnsignedType import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol @@ -377,7 +377,7 @@ open class FirExpressionsResolveTransformer(transformer: FirAbstractBodyResolveT } if (calleeReference is FirNamedReferenceWithCandidate) return functionCall functionCall.transformAnnotations(transformer, data) - functionCall.transformSingle(InvocationKindTransformer, null) + functionCall.replaceLambdaArgumentInvocationKinds(session) functionCall.transformTypeArguments(transformer, ResolutionMode.ContextIndependent) dataFlowAnalyzer.enterFunctionCall(functionCall) val (completeInference, callCompleted) =