FIR DFA: align FirLocalVariableAssignmentAnalyzer with the graph builder

I.e. maintain a set of seen lambdas where each marked as either "data
flow only" or "both data and control flow". The latter are truly
parallel with the function being analyzed, while the former have
technically already terminated, we just don't know the types inside them
because they may not have been analyzed yet.
This commit is contained in:
pyos
2022-11-24 13:50:01 +01:00
committed by teamcity
parent c231477375
commit 402ea98e02
7 changed files with 168 additions and 220 deletions
@@ -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<List<RealVariable>>,
) {
var variableStorage = variableStorage
class DataFlowAnalyzerContext(session: FirSession) {
val graphBuilder = ControlFlowGraphBuilder()
val preliminaryLoopVisitor = PreliminaryLoopVisitor()
val variablesClearedBeforeLoop = stackOf<List<RealVariable>>()
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<PropertyStability, MutableList<ConeKotlinType>>? {
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<FirFunction>() ?: return null
if (rootFunction == firFunction) return null
return context.firLocalVariableAssignmentAnalyzer ?: FirLocalVariableAssignmentAnalyzer.analyzeFunction(rootFunction).also {
context.firLocalVariableAssignmentAnalyzer = it
}
}
private val CFGNode<*>.livePreviousFlows: List<PersistentFlow>
get() = previousNodes.mapNotNull { it.takeIf { this.isDead || !it.isDead }?.flow }
@@ -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<FirFunctionSymbol<*>, 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<MutableSet<FirAnonymousFunction>> = mutableListOf()
internal class FirLocalVariableAssignmentAnalyzer {
private var rootFunction: FirFunction? = null
private var assignedLocalVariablesByFunction: Map<FirFunctionSymbol<*>, 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<MutableSet<FirProperty>> = mutableListOf(mutableSetOf())
private val functionScopes: Stack<Pair<FunctionFork?, MutableSet<FirProperty>>> = 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<FirProperty> = 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<MutableMap<FunctionFork, Boolean /* data-flow only */>> = stackOf()
private val functionStack = mutableListOf<FunctionFork>()
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<FirProperty>().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<FirAnonymousFunction>, 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<FirAnonymousFunction>) {
// 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<FirProperty>,
val assignedInside: Set<FirProperty>,
@@ -137,7 +137,7 @@ class ControlFlowGraphBuilder {
// ----------------------------------- Regular function -----------------------------------
fun enterFunction(function: FirFunction): Pair<FunctionEnterNode, LocalFunctionDeclarationNode?> {
fun enterFunction(function: FirFunction): Pair<LocalFunctionDeclarationNode?, FunctionEnterNode> {
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<FunctionExitNode, ControlFlowGraph> {
@@ -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)
@@ -10,7 +10,7 @@ fun test_1() {
foo(
x.length, // stable smartcast
run { x = "" },
x.length // stable smartcast
<!SMARTCAST_IMPOSSIBLE!>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 },
<!SMARTCAST_IMPOSSIBLE!>x<!>.length // either unstable or not a smartcast
)
}
}
@@ -10,7 +10,7 @@ fun test_1() {
foo(
<!DEBUG_INFO_SMARTCAST!>x<!>.length, // stable smartcast
run { x = "" },
<!SMARTCAST_IMPOSSIBLE!>x<!>.length // stable smartcast
<!SMARTCAST_IMPOSSIBLE!>x<!>.length // can be stable smartcast
)
}
}
@@ -36,3 +36,14 @@ fun test_3() {
)
}
}
fun test_4() {
var x: String? = null
if (x != null) {
foo(
<!DEBUG_INFO_SMARTCAST!>x<!>.length, // stable smartcast
run { x = null },
<!SMARTCAST_IMPOSSIBLE!>x<!>.length // either unstable or not a smartcast
)
}
}
@@ -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