FIR DFA: restructure handling of += to avoid losing data flow

This commit is contained in:
pyos
2022-11-22 22:43:14 +01:00
committed by teamcity
parent b2fe5831ed
commit 5dce772f0b
3 changed files with 56 additions and 47 deletions
@@ -133,9 +133,6 @@ abstract class FirDataFlowAnalyzer(
private val any = components.session.builtinTypes.anyType.type
private val nullableNothing = components.session.builtinTypes.nullableNothingType.type
@PrivateForInline
var ignoreFunctionCalls: Boolean = false
// ----------------------------------- Requests -----------------------------------
fun isAccessToUnstableLocalVariable(expression: FirExpression): Boolean {
@@ -162,17 +159,6 @@ abstract class FirDataFlowAnalyzer(
graphBuilder.dropSubgraphFromCall(call)
}
@OptIn(PrivateForInline::class)
inline fun <T> withIgnoreFunctionCalls(block: () -> T): T {
val oldValue = ignoreFunctionCalls
ignoreFunctionCalls = true
return try {
block()
} finally {
ignoreFunctionCalls = oldValue
}
}
// ----------------------------------- Named function -----------------------------------
fun enterFunction(function: FirFunction) {
@@ -841,28 +827,52 @@ abstract class FirDataFlowAnalyzer(
}
private var functionCallLevel = 0
private var resolvingAugmentedAssignmentOptions: Boolean = false
// The expected sequence of calls for augmented assignment:
// 1. enterAugmentedAssignmentCall()
// 2. resolve arguments
// 3. enterSelectAugmentedAssignmentCall()
// 4. resolve all options for calls in context-independent mode
// 5. exitSelectAugmentedAssignmentCall()
// 6. complete the chosen version
// 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++
}
fun enterSelectAugmentedAssignmentCall() {
assert(!resolvingAugmentedAssignmentOptions) { "resolving augmented assignment while resolving augmented assignment?" }
resolvingAugmentedAssignmentOptions = true
}
fun exitSelectAugmentedAssignmentCall() {
assert(resolvingAugmentedAssignmentOptions) { "no enterSelectAugmentedAssignmentCall before exitSelectAugmentedAssignmentCall" }
resolvingAugmentedAssignmentOptions = false
}
@OptIn(PrivateForInline::class)
fun enterFunctionCall(functionCall: FirFunctionCall) {
if (!ignoreFunctionCalls) {
graphBuilder.enterCall()
}
val lambdaArgs = functionCall.arguments.mapNotNullTo(mutableSetOf()) { it.getAnonymousFunction() }
val localVariableAssignmentAnalyzer = context.firLocalVariableAssignmentAnalyzer
?: if (lambdaArgs.isNotEmpty()) getOrCreateLocalVariableAssignmentAnalyzer(lambdaArgs.first()) else null
localVariableAssignmentAnalyzer?.enterFunctionCall(lambdaArgs, functionCallLevel)
functionCallLevel++
if (!resolvingAugmentedAssignmentOptions) {
graphBuilder.enterCall()
}
}
@OptIn(PrivateForInline::class)
fun exitFunctionCall(functionCall: FirFunctionCall, callCompleted: Boolean) {
functionCallLevel--
context.firLocalVariableAssignmentAnalyzer?.exitFunctionCall(callCompleted)
if (ignoreFunctionCalls) {
return
}
graphBuilder.exitFunctionCall(functionCall, callCompleted).mergeIncomingFlow {
processConditionalContract(it, functionCall)
if (!resolvingAugmentedAssignmentOptions) {
graphBuilder.exitFunctionCall(functionCall, callCompleted).mergeIncomingFlow {
processConditionalContract(it, functionCall)
}
}
}
@@ -247,6 +247,7 @@ class ControlFlowGraphBuilder {
postponedAnonymousFunctionNodes[symbol] = enterNode to exitNode
addNewSimpleNode(enterNode)
addNewSimpleNode(exitNode, preferredKind = EdgeKind.DfgForward)
require(dataFlowSourcesForNextCompletedCall.isNotEmpty)
return enterNode to exitNode
}
@@ -533,10 +533,12 @@ open class FirExpressionsResolveTransformer(transformer: FirAbstractBodyResolveT
require(operation != FirOperation.ASSIGN)
assignmentOperatorStatement.transformAnnotations(transformer, ResolutionMode.ContextIndependent)
dataFlowAnalyzer.enterAugmentedAssignmentCall()
val leftArgument = assignmentOperatorStatement.leftArgument.transformSingle(transformer, ResolutionMode.ContextIndependent)
val rightArgument = assignmentOperatorStatement.rightArgument.transformSingle(transformer, ResolutionMode.ContextDependent)
val generator = GeneratorOfPlusAssignCalls(assignmentOperatorStatement, operation, leftArgument, rightArgument)
dataFlowAnalyzer.enterSelectAugmentedAssignmentCall()
// x.plusAssign(y)
val assignOperatorCall = generator.createAssignOperatorCall()
@@ -554,6 +556,8 @@ open class FirExpressionsResolveTransformer(transformer: FirAbstractBodyResolveT
val operatorCallReference = resolvedOperatorCall.calleeReference as? FirNamedReferenceWithCandidate
val operatorIsSuccessful = operatorCallReference?.isError == false
dataFlowAnalyzer.exitSelectAugmentedAssignmentCall()
fun operatorReturnTypeMatches(candidate: Candidate): Boolean {
// After KT-45503, non-assign flavor of operator is checked more strictly: the return type must be assignable to the variable.
val operatorCallReturnType = resolvedOperatorCall.typeRef.coneType
@@ -574,14 +578,12 @@ open class FirExpressionsResolveTransformer(transformer: FirAbstractBodyResolveT
val lhsIsVar = lhsVariable?.isVar == true
fun chooseAssign(): FirStatement {
dataFlowAnalyzer.enterFunctionCall(resolvedAssignCall)
callCompleter.completeCall(resolvedAssignCall, noExpectedType)
dataFlowAnalyzer.exitFunctionCall(resolvedAssignCall, callCompleted = true)
return resolvedAssignCall
}
fun chooseOperator(): FirStatement {
dataFlowAnalyzer.enterFunctionCall(resolvedAssignCall)
callCompleter.completeCall(
resolvedOperatorCall,
lhsVariable?.returnTypeRef ?: noExpectedType,
@@ -669,11 +671,9 @@ open class FirExpressionsResolveTransformer(transformer: FirAbstractBodyResolveT
}
private inline fun <T> resolveCandidateForAssignmentOperatorCall(block: () -> T): T {
return dataFlowAnalyzer.withIgnoreFunctionCalls {
callResolver.withNoArgumentsTransform {
context.withInferenceSession(InferenceSessionForAssignmentOperatorCall) {
block()
}
return callResolver.withNoArgumentsTransform {
context.withInferenceSession(InferenceSessionForAssignmentOperatorCall) {
block()
}
}
}
@@ -1230,11 +1230,14 @@ open class FirExpressionsResolveTransformer(transformer: FirAbstractBodyResolveT
augmentedArraySetCall.transformAnnotations(transformer, data)
// transformedLhsCall: a.get(index)
dataFlowAnalyzer.enterAugmentedAssignmentCall()
val transformedLhsCall = augmentedArraySetCall.lhsGetCall.transformSingle(transformer, ResolutionMode.ContextIndependent)
val transformedRhs = augmentedArraySetCall.rhs.transformSingle(transformer, ResolutionMode.ContextDependent)
val generator = GeneratorOfPlusAssignCalls(augmentedArraySetCall, operation, transformedLhsCall, transformedRhs)
dataFlowAnalyzer.enterSelectAugmentedAssignmentCall()
// a.get(b).plusAssign(c)
val assignOperatorCall = generator.createAssignOperatorCall()
val resolvedAssignCall = resolveCandidateForAssignmentOperatorCall {
@@ -1243,24 +1246,23 @@ open class FirExpressionsResolveTransformer(transformer: FirAbstractBodyResolveT
val assignCallReference = resolvedAssignCall.calleeReference as? FirNamedReferenceWithCandidate
val assignIsSuccessful = assignCallReference?.isError == false
fun completeAssignCall() {
dataFlowAnalyzer.enterFunctionCall(resolvedAssignCall)
fun chooseAssign(): FirFunctionCall {
callCompleter.completeCall(resolvedAssignCall, noExpectedType)
dataFlowAnalyzer.exitFunctionCall(resolvedAssignCall, callCompleted = true)
}
fun chooseAssign(): FirStatement {
completeAssignCall()
return resolvedAssignCall
}
// prefer a "simpler" variant for dynamics
if (transformedLhsCall.calleeReference.resolvedSymbol?.origin == FirDeclarationOrigin.DynamicScope) {
dataFlowAnalyzer.exitSelectAugmentedAssignmentCall()
return chooseAssign()
}
// <array>.set(<index_i>, <array>.get(<index_i>).plus(c))
val info = tryResolveAugmentedArraySetCallAsSetGetBlock(augmentedArraySetCall, transformedLhsCall, transformedRhs)
dataFlowAnalyzer.exitSelectAugmentedAssignmentCall()
val resolvedOperatorCall = info.operatorCall
val operatorCallReference = resolvedOperatorCall.calleeReference as? FirNamedReferenceWithCandidate
val operatorIsSuccessful = operatorCallReference?.isError == false
@@ -1276,27 +1278,23 @@ open class FirExpressionsResolveTransformer(transformer: FirAbstractBodyResolveT
val setIsSuccessful = setCallReference?.isError == false
fun chooseSetOperator(): FirStatement {
dataFlowAnalyzer.enterFunctionCall(resolvedSetCall)
dataFlowAnalyzer.enterFunctionCall(resolvedOperatorCall)
callCompleter.completeCall(
resolvedSetCall,
noExpectedType,
expectedTypeMismatchIsReportedInChecker = true
)
dataFlowAnalyzer.exitFunctionCall(resolvedOperatorCall, callCompleted = true)
dataFlowAnalyzer.exitFunctionCall(resolvedSetCall, callCompleted = true)
return info.toBlock()
}
fun reportError(diagnostic: ConeDiagnostic): FirStatement {
completeAssignCall()
val errorReference = buildErrorNamedReference {
source = augmentedArraySetCall.source
this.diagnostic = diagnostic
return chooseAssign().also {
val errorReference = buildErrorNamedReference {
source = augmentedArraySetCall.source
this.diagnostic = diagnostic
}
it.replaceCalleeReference(errorReference)
}
resolvedAssignCall.replaceCalleeReference(errorReference)
return resolvedAssignCall
}
fun reportAmbiguity(