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 e3082f20d0b..fa2b4cc4cf9 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 @@ -35,19 +35,16 @@ import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.name.StandardClassIds import org.jetbrains.kotlin.types.ConstantValueKind import org.jetbrains.kotlin.util.OperatorNameConventions -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull -class DataFlowAnalyzerContext( - val graphBuilder: ControlFlowGraphBuilder, - variableStorage: VariableStorageImpl, - val preliminaryLoopVisitor: PreliminaryLoopVisitor, - val variablesClearedBeforeLoop: Stack>, -) { - var variableStorage = variableStorage +class DataFlowAnalyzerContext(session: FirSession) { + val graphBuilder = ControlFlowGraphBuilder() + val preliminaryLoopVisitor = PreliminaryLoopVisitor() + val variablesClearedBeforeLoop = stackOf>() + internal val variableAssignmentAnalyzer = FirLocalVariableAssignmentAnalyzer() + + var variableStorage = VariableStorageImpl(session) private set - internal var firLocalVariableAssignmentAnalyzer: FirLocalVariableAssignmentAnalyzer? = null - private var assignmentCounter = 0 fun newAssignmentIndex(): Int { @@ -56,18 +53,10 @@ class DataFlowAnalyzerContext( fun reset() { graphBuilder.reset() - variableStorage = variableStorage.clear() preliminaryLoopVisitor.resetState() variablesClearedBeforeLoop.reset() - firLocalVariableAssignmentAnalyzer = null - } - - companion object { - fun empty(session: FirSession): DataFlowAnalyzerContext = - DataFlowAnalyzerContext( - ControlFlowGraphBuilder(), VariableStorageImpl(session), - PreliminaryLoopVisitor(), stackOf() - ) + variableAssignmentAnalyzer.reset() + variableStorage = variableStorage.clear() } } @@ -136,11 +125,8 @@ abstract class FirDataFlowAnalyzer( // ----------------------------------- Requests ----------------------------------- - fun isAccessToUnstableLocalVariable(expression: FirExpression): Boolean { - val analyzer = context.firLocalVariableAssignmentAnalyzer ?: return false - val realFir = expression.unwrapElement() as? FirQualifiedAccessExpression ?: return false - return analyzer.isAccessToUnstableLocalVariable(realFir) - } + fun isAccessToUnstableLocalVariable(expression: FirExpression): Boolean = + context.variableAssignmentAnalyzer.isAccessToUnstableLocalVariable(expression) open fun getTypeUsingSmartcastInfo(expression: FirExpression): Pair>? { val flow = graphBuilder.lastNode.flow @@ -164,25 +150,38 @@ abstract class FirDataFlowAnalyzer( fun enterFunction(function: FirFunction) { if (function is FirDefaultPropertyAccessor) return - if (function is FirAnonymousFunction) { - enterAnonymousFunction(function) - return - } - // All non-lambda function are treated as concurrent since we do not make any assumption about when and how it's invoked. - getOrCreateLocalVariableAssignmentAnalyzer(function)?.enterLocalFunction(function) - val (functionEnterNode, localFunctionNode) = graphBuilder.enterFunction(function) + val (localFunctionNode, functionEnterNode) = if (function is FirAnonymousFunction) { + graphBuilder.enterAnonymousFunction(function) + } else { + graphBuilder.enterFunction(function) + } localFunctionNode?.mergeIncomingFlow() - functionEnterNode.mergeIncomingFlow() + functionEnterNode.mergeIncomingFlow { + // TODO: ||? + if (function is FirAnonymousFunction && function.invocationKind?.canBeRevisited() != false) { + enterCapturingStatement(it, function) + } + } + context.variableAssignmentAnalyzer.enterFunction(function) } fun exitFunction(function: FirFunction): FirControlFlowGraphReference? { if (function is FirDefaultPropertyAccessor) return null - if (function is FirAnonymousFunction) { - return exitAnonymousFunction(function) + + context.variableAssignmentAnalyzer.exitFunction() + // TODO: ||? + if (function is FirAnonymousFunction && function.invocationKind?.canBeRevisited() != false) { + exitCapturingStatement(function) + } + + if (function is FirAnonymousFunction) { + val (functionExitNode, postponedLambdaExitNode, graph) = graphBuilder.exitAnonymousFunction(function) + functionExitNode.mergeIncomingFlow() + postponedLambdaExitNode?.mergeIncomingFlow() + resetReceivers() // roll back to state before function + return FirControlFlowGraphReferenceImpl(graph) } - // All non-lambda function are treated as concurrent since we do not make any assumption about when and how it's invoked. - getOrCreateLocalVariableAssignmentAnalyzer(function)?.exitLocalFunction(function) val (node, graph) = graphBuilder.exitFunction(function) node.mergeIncomingFlow() @@ -202,35 +201,7 @@ abstract class FirDataFlowAnalyzer( // ----------------------------------- Anonymous function ----------------------------------- - private fun enterAnonymousFunction(anonymousFunction: FirAnonymousFunction) { - getOrCreateLocalVariableAssignmentAnalyzer(anonymousFunction)?.apply { - finishPostponedAnonymousFunction() - enterLocalFunction(anonymousFunction) - } - val (functionDeclarationNode, functionEnterNode) = graphBuilder.enterAnonymousFunction(anonymousFunction) - functionDeclarationNode?.mergeIncomingFlow() - functionEnterNode.mergeIncomingFlow { - if (anonymousFunction.invocationKind?.canBeRevisited() != false) { - enterCapturingStatement(it, anonymousFunction) - } - } - } - - private fun exitAnonymousFunction(anonymousFunction: FirAnonymousFunction): FirControlFlowGraphReference { - getOrCreateLocalVariableAssignmentAnalyzer(anonymousFunction)?.exitLocalFunction(anonymousFunction) - val (functionExitNode, postponedLambdaExitNode, graph) = graphBuilder.exitAnonymousFunction(anonymousFunction) - if (anonymousFunction.invocationKind?.canBeRevisited() != false) { - exitCapturingStatement(anonymousFunction) - } - functionExitNode.mergeIncomingFlow() - postponedLambdaExitNode?.mergeIncomingFlow() - resetReceivers() // roll back to state before function - return FirControlFlowGraphReferenceImpl(graph) - } - fun visitPostponedAnonymousFunction(anonymousFunctionExpression: FirAnonymousFunctionExpression) { - val anonymousFunction = anonymousFunctionExpression.anonymousFunction - getOrCreateLocalVariableAssignmentAnalyzer(anonymousFunction)?.visitPostponedAnonymousFunction(anonymousFunction) graphBuilder.visitPostponedAnonymousFunction(anonymousFunctionExpression).mergeIncomingFlow() } @@ -809,7 +780,6 @@ abstract class FirDataFlowAnalyzer( graphBuilder.exitResolvedQualifierNode(resolvedQualifier).mergeIncomingFlow() } - private var functionCallLevel = 0 private var resolvingAugmentedAssignmentOptions: Boolean = false // The expected sequence of calls for augmented assignment: @@ -822,10 +792,7 @@ abstract class FirDataFlowAnalyzer( // 7. exitFunctionCall(top-level call in the chosen option) fun enterAugmentedAssignmentCall() { graphBuilder.enterCall() - // Add an extra space in the local variable assignment analyzer. That way all postponed - // lambdas from all alternatives ([get+]plusAssign/[get+]plus[+set]) will be collected - // into that space and only removed when `exitFunctionCall` finalizes the chosen option. - functionCallLevel++ + context.variableAssignmentAnalyzer.enterFunctionCall(emptyList()) } fun enterSelectAugmentedAssignmentCall() { @@ -839,23 +806,16 @@ abstract class FirDataFlowAnalyzer( } fun enterFunctionCall(functionCall: FirFunctionCall) { - val lambdaArgs = functionCall.arguments.mapNotNullTo(mutableSetOf()) { it.unwrapAnonymousFunctionExpression() } - val localVariableAssignmentAnalyzer = context.firLocalVariableAssignmentAnalyzer - ?: if (lambdaArgs.isNotEmpty()) getOrCreateLocalVariableAssignmentAnalyzer(lambdaArgs.first()) else null - localVariableAssignmentAnalyzer?.enterFunctionCall(lambdaArgs, functionCallLevel) - functionCallLevel++ - if (!resolvingAugmentedAssignmentOptions) { - graphBuilder.enterCall() - } + if (resolvingAugmentedAssignmentOptions) return // shouldn't be any lambda arguments anyway, they're visited before that + context.variableAssignmentAnalyzer.enterFunctionCall(functionCall.arguments.mapNotNull { it.unwrapAnonymousFunctionExpression() }) + graphBuilder.enterCall() } fun exitFunctionCall(functionCall: FirFunctionCall, callCompleted: Boolean) { - functionCallLevel-- - context.firLocalVariableAssignmentAnalyzer?.exitFunctionCall(callCompleted) - if (!resolvingAugmentedAssignmentOptions) { - graphBuilder.exitFunctionCall(functionCall, callCompleted).mergeIncomingFlow { - processConditionalContract(it, functionCall) - } + if (resolvingAugmentedAssignmentOptions) return + context.variableAssignmentAnalyzer.exitFunctionCall(callCompleted) + graphBuilder.exitFunctionCall(functionCall, callCompleted).mergeIncomingFlow { + processConditionalContract(it, functionCall) } } @@ -1180,16 +1140,6 @@ abstract class FirDataFlowAnalyzer( // ------------------------------------------------------ Utils ------------------------------------------------------ - private fun getOrCreateLocalVariableAssignmentAnalyzer(firFunction: FirFunction): FirLocalVariableAssignmentAnalyzer? { - // Only return analyzer for nested functions so that we won't waste time on functions that don't contain any lambda or local - // function. - val rootFunction = components.containingDeclarations.firstIsInstanceOrNull() ?: return null - if (rootFunction == firFunction) return null - return context.firLocalVariableAssignmentAnalyzer ?: FirLocalVariableAssignmentAnalyzer.analyzeFunction(rootFunction).also { - context.firLocalVariableAssignmentAnalyzer = it - } - } - private val CFGNode<*>.livePreviousFlows: List get() = previousNodes.mapNotNull { it.takeIf { this.isDead || !it.isDead }?.flow } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirLocalVariableAssignmentAnalyzer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirLocalVariableAssignmentAnalyzer.kt index c667cac7425..c67ff73a02b 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirLocalVariableAssignmentAnalyzer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirLocalVariableAssignmentAnalyzer.kt @@ -5,7 +5,6 @@ package org.jetbrains.kotlin.fir.resolve.dfa -import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange import org.jetbrains.kotlin.contracts.description.isInPlace import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.declarations.* @@ -16,7 +15,6 @@ import org.jetbrains.kotlin.fir.references.FirReference import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol import org.jetbrains.kotlin.fir.visitors.FirVisitor import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.utils.addToStdlib.popLast /** * Helper that checks if an access to a local variable access is stable. @@ -25,141 +23,121 @@ import org.jetbrains.kotlin.utils.addToStdlib.popLast * [isAccessToUnstableLocalVariable] only works for an access during the natural FIR tree traversal. This class will not work if one * queries after the traversal is done. **/ -internal class FirLocalVariableAssignmentAnalyzer( - private val assignedLocalVariablesByFunction: Map, FunctionFork> -) { - /** - * Stack storing concurrent lambda arguments for the current visited anonymous function. For example - * ``` - * callWithMultipleLambdaExactlyOnceEach( - * l1 = { x.length }, - * l2 = { x = null } - * ) - * ``` - * From the call, it's nondeterministic whether `l1` runs before `l2` or vice versa. So when handling `l1`, we must mark all variables - * touched in `l2` unstable. - */ - private val concurrentLambdaArgsStack: MutableList> = mutableListOf() +internal class FirLocalVariableAssignmentAnalyzer { + private var rootFunction: FirFunction? = null + private var assignedLocalVariablesByFunction: Map, FunctionFork>? = null - /** - * Stack whose element tracks all concurrently modified variables in execution paths other than this one. It's a stack because after - * exiting a local function, we must restore the variables to the state before entering this local function. Initially, there is an - * empty set for the root function when we starts the analysis. - */ - private val concurrentlyAssignedLocalVariablesStack: MutableList> = mutableListOf(mutableSetOf()) + private val functionScopes: Stack>> = stackOf() - /** - * Temporary storage that tracks concurrently modified variables during function call resolution. For example, consider the following, - * - * ``` - * foo(bar { x= null }, x.length) - * ``` - * - * Sometimes during resolution, when stability of `x` is retrieved with [isAccessToUnstableLocalVariable], the resolution of `foo` and - * `bar` is not yet finished. Hence, the lambda arg passed to `bar` is not traversed. In this case, the resolution logic first calls - * [visitPostponedAnonymousFunction], then [isAccessToUnstableLocalVariable]. Next, it calls [enterLocalFunction] and starts traversing - * the lambda passed to `bar`. - */ - private val ephemeralConcurrentlyAssignedLocalVariables: MutableSet = mutableSetOf() + // Example of control-flow-postponed lambdas: callBoth({ a.x }, { a = null }) + // Lambdas are called in an unknown order, so control flow edges to both of them go from before the call. + // However, the assignment in the second lambda should invalidate the smart cast in the first. + // + // Example of data-flow-postponed lambdas: genericFunction(run { a = null }, a.x) + // Although in control flow the first lambda always executes before the second argument, in order to determine + // the type arguments to `run` - and thus resolve its argument lambda - we may have to resolve `a.x` first. + // Because smart casts can only get information from statements that have previously been resolved, data flows + // from the result of `run` to the result of `genericFunction` so smart casts should be prohibited in `a.x`. + // + // This mirrors `ControlFlowGraphBuilder.postponedLambdaExits`. + private val postponedLambdas: Stack> = stackOf() - private val functionStack = mutableListOf() + fun reset() { + rootFunction = null + assignedLocalVariablesByFunction = null + postponedLambdas.reset() + functionScopes.reset() + } /** Checks whether the given access is an unstable access to a local variable at this moment. */ - fun isAccessToUnstableLocalVariable(qualifiedAccessExpression: FirQualifiedAccessExpression): Boolean { - val property = qualifiedAccessExpression.referredPropertySymbol?.fir ?: return false - return property in ephemeralConcurrentlyAssignedLocalVariables || property in concurrentlyAssignedLocalVariablesStack.last() - } + @OptIn(DfaInternals::class) + fun isAccessToUnstableLocalVariable(fir: FirExpression): Boolean { + if (assignedLocalVariablesByFunction == null) return false - fun visitPostponedAnonymousFunction(anonymousFunction: FirAnonymousFunction) { - // Postponed anonymous function is visited before the current function call with lambda is resolved. Hence, the invocationKind is - // always null and hence there is no need to check it. In addition, since multiple lambda can be passed, we accumulate the - // effects by appending to `ephemeralConcurrentlyAssignedLocalVariables`. After the function call is resolved, - // `exitAnonymousFunction` will be invoked at some point to properly set up the `persistentConcurrentlyAssignedLocalVariables`. - assignedLocalVariablesByFunction[anonymousFunction.symbol]?.assignedInside?.let { - ephemeralConcurrentlyAssignedLocalVariables.addAll(it) + val realFir = fir.unwrapElement() as? FirQualifiedAccessExpression ?: return false + val property = realFir.referredPropertySymbol?.fir ?: return false + // Have data => have a root function => functionScopes is not empty. + return property in functionScopes.top().second || postponedLambdas.all().any { lambdas -> + // Control-flow-postponed lambdas' assignments should be in `functionScopes.top()`. + // The reason we can't check them here is that one of the entries may be the lambda + // that is currently being analyzed, and assignments in it are, in fact, totally fine. + lambdas.any { (lambda, dataFlowOnly) -> dataFlowOnly && property in lambda.assignedInside } } } - fun finishPostponedAnonymousFunction() { - // Clear the temporarily assigned local variables in `visitPostponedAnonymousFunction`. - ephemeralConcurrentlyAssignedLocalVariables.clear() + private fun getInfoForLocalFunction(symbol: FirFunctionSymbol<*>): FunctionFork? { + val root = rootFunction ?: return null + if (root.symbol == symbol) return null + val cachedMap = assignedLocalVariablesByFunction ?: run { + val data = MiniCfgBuilder.MiniCfgData() + MiniCfgBuilder().visitElement(root, data) + data.functionForks.also { assignedLocalVariablesByFunction = it } + } + return cachedMap[symbol] } - fun enterLocalFunction(function: FirFunction) { - val concurrentlyAssignedLocalVariables = concurrentlyAssignedLocalVariablesStack.last().toMutableSet() - concurrentlyAssignedLocalVariablesStack.add(concurrentlyAssignedLocalVariables) + fun enterFunction(function: FirFunction) { + val prohibitSmartCasts = functionScopes.topOrNull()?.second?.toMutableSet() + ?: mutableSetOf().also { rootFunction = function } - // 1. As mentioned in the comment above, we don't know whether other lambda arguments passed to the same call will be - // called before or after this lambda, so their assignments might have executed. Unless they're not called at all. - // 2. While lambdas from outer calls are not concurrent from control flow point of view, they are concurrent in data flow - // because the way this lambda resolves may affect the way those lambdas resolve, thus we need to forbid dependencies - // from smartcasts in this lambda to statements in these other lambdas. - for (concurrentLambdas in concurrentLambdaArgsStack) { - for (otherLambda in concurrentLambdas) { - if (otherLambda != function && otherLambda.invocationKind != EventOccurrencesRange.ZERO) { - assignedLocalVariablesByFunction[otherLambda.symbol]?.assignedInside?.let { - concurrentlyAssignedLocalVariables += it - } + val info = getInfoForLocalFunction(function.symbol) + for (concurrentLambdas in postponedLambdas.all()) { + for ((otherLambda, dataFlowOnly) in concurrentLambdas) { + if (!dataFlowOnly && otherLambda != info) { + prohibitSmartCasts += otherLambda.assignedInside } } } - - assignedLocalVariablesByFunction[function.symbol]?.let { - functionStack.add(it) - if (function !is FirAnonymousFunction || !function.invocationKind.isInPlace) { - // The function may be called twice concurrently in an SMT environment, which means any assignment it executes - // might in theory happen in between any check it does and a subsequent use of the variable. So if this function - // does any assignments, it cannot smartcast the target variables. - concurrentlyAssignedLocalVariables += it.assignedInside - // The function may also be stored and called later, so assignments done outside its scope after the definition - // might also have executed. - for (outerScope in functionStack) { - concurrentlyAssignedLocalVariables += outerScope.assignedLater + if (function !is FirAnonymousFunction || !function.invocationKind.isInPlace) { + // The function may be stored and then called later => + for ((outerInfo, prohibitInOuterScope) in functionScopes.all()) { + if (info != null) { + // => any access of the variables it touches is no longer smartcastable ever. + // + // TODO: this incorrectly affects separate branches that are visited after this one: + // if (p is Something) { + // if (condition)) { + // foo { p = whatever } + // p.memberOfSomething // Bad + // } else { + // p.memberOfSomething // Marked as an error, but actually OK + // } + // p.memberOfSomething // Bad + // } + // FE1.0 has the same behavior. + prohibitInOuterScope.addAll(info.assignedInside) + } + if (outerInfo != null) { + // => any write to a variable outside the function invalidates smart casts inside it + prohibitSmartCasts += outerInfo.assignedLater } } + if (info != null) { + prohibitSmartCasts.addAll(info.assignedLater) + // => it may be called twice in parallel, so it can't even trust its own assignments + prohibitSmartCasts.addAll(info.assignedInside) + } + } + functionScopes.push(info to prohibitSmartCasts) + } + + fun exitFunction() { + functionScopes.pop() + if (functionScopes.isEmpty) { + rootFunction = null + assignedLocalVariablesByFunction = null } } - fun exitLocalFunction(function: FirFunction) { - concurrentlyAssignedLocalVariablesStack.removeLast() - assignedLocalVariablesByFunction[function.symbol]?.let { - functionStack.popLast() - if (function !is FirAnonymousFunction || !function.invocationKind.isInPlace) { - // The function may be stored and then called later, so any access to the variables it touches - // is no longer smartcastable ever. - // - // TODO: this incorrectly affects separate branches that are visited after this one: - // if (p is Something) { - // if (condition)) { - // foo { p = whatever } - // p.memberOfSomething // Bad - // } else { - // p.memberOfSomething // Marked as an error, but actually OK - // } - // p.memberOfSomething // Bad - // } - // FE1.0 has the same behavior. - for (outerScope in concurrentlyAssignedLocalVariablesStack) { - outerScope += it.assignedInside - } - } - } - } - - fun enterFunctionCall(lambdaArgs: MutableSet, level: Int) { - while (concurrentLambdaArgsStack.size < level) { - // This object is only created on first local anonymous function, so we might have missed some - // `enterFunctionCall`s. None of them have lambda arguments. - concurrentLambdaArgsStack.add(mutableSetOf()) - } - concurrentLambdaArgsStack.add(lambdaArgs) + fun enterFunctionCall(lambdaArgs: Collection) { + // If not inside a function at all, then there is no concept of a local and nothing to track. + if (rootFunction == null) return + postponedLambdas.push(lambdaArgs.mapNotNull { getInfoForLocalFunction(it.symbol) }.associateWithTo(mutableMapOf()) { false }) } fun exitFunctionCall(callCompleted: Boolean) { - // If we had anonymous functions but no calls with lambdas, the stack might have never been initialized. - if (concurrentLambdaArgsStack.isEmpty()) return - - val lambdasInCall = concurrentLambdaArgsStack.popLast() + if (rootFunction == null) return + val lambdasInCall = postponedLambdas.pop() if (!callCompleted) { // TODO: this has the same problem as above: // if (p is Something) { @@ -172,7 +150,10 @@ internal class FirLocalVariableAssignmentAnalyzer( // ) // } // And also as above, FE1.0 produces the same error. - concurrentLambdaArgsStack.lastOrNull()?.addAll(lambdasInCall) + // + // TODO: this should never return null. Also somehow throwing an exception here leads to weird effects: + // apparently the compiler attempts to continue somewhere... + lambdasInCall.keys.associateWithTo(postponedLambdas.topOrNull() ?: return) { true } } } @@ -254,12 +235,6 @@ internal class FirLocalVariableAssignmentAnalyzer( * so that shadowed names are handled correctly. This works because local variables at any scope have higher priority * than members on implicit receivers, even if the implicit receiver is introduced by a later scope. */ - fun analyzeFunction(rootFunction: FirFunction): FirLocalVariableAssignmentAnalyzer { - val data = MiniCfgBuilder.MiniCfgData() - MiniCfgBuilder().visitElement(rootFunction, data) - return FirLocalVariableAssignmentAnalyzer(data.functionForks) - } - class FunctionFork( val assignedLater: Set, val assignedInside: Set, diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt index 959c0730680..c0de1e6ed3e 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt @@ -137,7 +137,7 @@ class ControlFlowGraphBuilder { // ----------------------------------- Regular function ----------------------------------- - fun enterFunction(function: FirFunction): Pair { + fun enterFunction(function: FirFunction): Pair { require(function !is FirAnonymousFunction) val name = when (function) { is FirSimpleFunction -> function.name.asString() @@ -181,7 +181,7 @@ class ControlFlowGraphBuilder { exitTargetsForTry.push(it) } - return Pair(enterNode, localFunctionNode) + return Pair(localFunctionNode, enterNode) } fun exitFunction(function: FirFunction): Pair { diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirAbstractBodyResolveTransformerDispatcher.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirAbstractBodyResolveTransformerDispatcher.kt index 69cdbc4e348..7f920e02f1b 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirAbstractBodyResolveTransformerDispatcher.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirAbstractBodyResolveTransformerDispatcher.kt @@ -37,7 +37,7 @@ abstract class FirAbstractBodyResolveTransformerDispatcher( ) : FirAbstractBodyResolveTransformer(phase) { final override val context: BodyResolveContext = - outerBodyResolveContext ?: BodyResolveContext(returnTypeCalculator, DataFlowAnalyzerContext.empty(session)) + outerBodyResolveContext ?: BodyResolveContext(returnTypeCalculator, DataFlowAnalyzerContext(session)) final override val components: BodyResolveTransformerComponents = BodyResolveTransformerComponents(session, scopeSession, this, context) diff --git a/compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.fir.kt index 4e3b787ebbf..8796fcdf8e2 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.fir.kt @@ -10,7 +10,7 @@ fun test_1() { foo( x.length, // stable smartcast run { x = "" }, - x.length // stable smartcast + x.length // can be stable smartcast ) } } @@ -36,3 +36,14 @@ fun test_3() { ) } } + +fun test_4() { + var x: String? = null + if (x != null) { + foo( + x.length, // stable smartcast + run { x = null }, + x.length // either unstable or not a smartcast + ) + } +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.kt b/compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.kt index afb27c65940..70263f54a4b 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.kt @@ -10,7 +10,7 @@ fun test_1() { foo( x.length, // stable smartcast run { x = "" }, - x.length // stable smartcast + x.length // can be stable smartcast ) } } @@ -36,3 +36,14 @@ fun test_3() { ) } } + +fun test_4() { + var x: String? = null + if (x != null) { + foo( + x.length, // stable smartcast + run { x = null }, + x.length // either unstable or not a smartcast + ) + } +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.txt b/compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.txt index 4a8237b2eff..eac390143b8 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.txt +++ b/compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.txt @@ -5,4 +5,5 @@ public fun myRun(/*0*/ block: () -> kotlin.Unit): kotlin.Any? public fun test_1(): kotlin.Unit public fun test_2(): kotlin.Unit public fun test_3(): kotlin.Unit +public fun test_4(): kotlin.Unit