diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/propertyAccessors.kt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/propertyAccessors.kt index 9ca97d57d65..f41966ee0c7 100644 --- a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/propertyAccessors.kt +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/propertyAccessors.kt @@ -20,11 +20,11 @@ var Any?.isNotNull: Boolean fun test_1(a: A?) { if (a.isNotNull) { - a.foo() + a.foo() } } fun test_2(a: A?) { a.isNotNull = true - a.foo() + a.foo() } \ No newline at end of file diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/propertyAccessors.txt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/propertyAccessors.txt index df434f4d6ea..d9a89954ce6 100644 --- a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/propertyAccessors.txt +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/propertyAccessors.txt @@ -24,12 +24,12 @@ FILE: propertyAccessors.kt public final fun test_1(a: R|A?|): R|kotlin/Unit| { when () { R|/a|.R|/isNotNull| -> { - R|/a|.#() + R|/a|.R|/A.foo|() } } } public final fun test_2(a: R|A?|): R|kotlin/Unit| { R|/a|.R|/isNotNull| = Boolean(true) - R|/a|.#() + R|/a|.R|/A.foo|() } 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 99fd9879bb1..21de6b98ab3 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 @@ -14,6 +14,7 @@ import org.jetbrains.kotlin.fir.contracts.description.ConeConstantReference import org.jetbrains.kotlin.fir.contracts.description.ConeReturnsEffectDeclaration import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.expressions.* +import org.jetbrains.kotlin.fir.references.FirResolvedCallableReference import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.resolve.ImplicitReceiverStackImpl import org.jetbrains.kotlin.fir.resolve.ResolutionMode @@ -618,6 +619,7 @@ abstract class FirDataFlowAnalyzer( fun exitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) { graphBuilder.exitQualifiedAccessExpression(qualifiedAccessExpression).mergeIncomingFlow() + processConditionalContract(qualifiedAccessExpression) exitSafeCall(qualifiedAccessExpression) } @@ -637,9 +639,7 @@ abstract class FirDataFlowAnalyzer( exitBooleanNot(functionCall, functionCallNode) } processConditionalContract(functionCall) - if (functionCall.safe) { - exitSafeCall(functionCall) - } + exitSafeCall(functionCall) } fun exitDelegatedConstructorCall(call: FirDelegatedConstructorCall, callCompleted: Boolean) { @@ -671,18 +671,28 @@ abstract class FirDataFlowAnalyzer( } } - private fun processConditionalContract(functionCall: FirFunctionCall) { - val contractDescription = (functionCall.symbol as? FirNamedFunctionSymbol) - ?.fir - ?.contractDescription as? FirResolvedContractDescription - ?: return + private fun processConditionalContract(qualifiedAccess: FirQualifiedAccess) { + val owner: FirContractDescriptionOwner? = when (qualifiedAccess) { + is FirFunctionCall -> qualifiedAccess.toResolvedCallableSymbol()?.fir as? FirSimpleFunction + is FirQualifiedAccessExpression -> { + val property = (qualifiedAccess.calleeReference as? FirResolvedNamedReference)?.resolvedSymbol?.fir as? FirProperty + property?.getter + } + is FirVariableAssignment -> { + val property = (qualifiedAccess.lValue as? FirResolvedNamedReference)?.resolvedSymbol?.fir as? FirProperty + property?.setter + } + else -> null + } + + val contractDescription = owner?.contractDescription as? FirResolvedContractDescription ?: return val conditionalEffects = contractDescription.effects.filterIsInstance() if (conditionalEffects.isEmpty()) return - val argumentsMapping = createArgumentsMapping(functionCall) ?: return + val argumentsMapping = createArgumentsMapping(qualifiedAccess) ?: return contractDescriptionVisitingMode = true - graphBuilder.enterContract(functionCall).mergeIncomingFlow() + graphBuilder.enterContract(qualifiedAccess).mergeIncomingFlow() val lastFlow = graphBuilder.lastNode.flow - val functionCallVariable = variableStorage.getOrCreateVariable(lastFlow, functionCall) + val functionCallVariable = variableStorage.getOrCreateVariable(lastFlow, qualifiedAccess) for (conditionalEffect in conditionalEffects) { val fir = conditionalEffect.buildContractFir(argumentsMapping) ?: continue val effect = conditionalEffect.effect as? ConeReturnsEffectDeclaration ?: continue @@ -728,7 +738,7 @@ abstract class FirDataFlowAnalyzer( else -> throw IllegalArgumentException("Unsupported constant reference: $value") } } - graphBuilder.exitContract(functionCall).mergeIncomingFlow(updateReceivers = true) + graphBuilder.exitContract(qualifiedAccess).mergeIncomingFlow(updateReceivers = true) contractDescriptionVisitingMode = false } @@ -747,8 +757,10 @@ abstract class FirDataFlowAnalyzer( val node = graphBuilder.exitVariableAssignment(assignment).mergeIncomingFlow() val property = (assignment.lValue as? FirResolvedNamedReference)?.resolvedSymbol?.fir as? FirProperty ?: return // TODO: add unstable smartcast - if (!property.isLocal && property.isVar) return - exitVariableInitialization(node, assignment.rValue, property, assignment) + if (property.isLocal || !property.isVar) { + exitVariableInitialization(node, assignment.rValue, property, assignment) + } + processConditionalContract(assignment) } private fun exitVariableInitialization( diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraph.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraph.kt index 45fe049a652..765e0093983 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraph.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraph.kt @@ -538,12 +538,12 @@ class VariableAssignmentNode(owner: ControlFlowGraph, override val fir: FirVaria } } -class EnterContractNode(owner: ControlFlowGraph, override val fir: FirFunctionCall, level: Int, id: Int) : CFGNode(owner, level, id), EnterNodeMarker { +class EnterContractNode(owner: ControlFlowGraph, override val fir: FirQualifiedAccess, level: Int, id: Int) : CFGNode(owner, level, id), EnterNodeMarker { override fun accept(visitor: ControlFlowGraphVisitor, data: D): R { return visitor.visitEnterContractNode(this, data) } } -class ExitContractNode(owner: ControlFlowGraph, override val fir: FirFunctionCall, level: Int, id: Int) : CFGNode(owner, level, id), ExitNodeMarker { +class ExitContractNode(owner: ControlFlowGraph, override val fir: FirQualifiedAccess, level: Int, id: Int) : CFGNode(owner, level, id), ExitNodeMarker { override fun accept(visitor: ControlFlowGraphVisitor, data: D): R { return visitor.visitExitContractNode(this, data) } 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 38cbc8ab871..c2b25ed9a3a 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 @@ -607,12 +607,12 @@ class ControlFlowGraphBuilder { return leftExitNode to rightExitNode } - fun enterContract(functionCall: FirFunctionCall): EnterContractNode { - return createEnterContractNode(functionCall).also { addNewSimpleNode(it) } + fun enterContract(qualifiedAccess: FirQualifiedAccess): EnterContractNode { + return createEnterContractNode(qualifiedAccess).also { addNewSimpleNode(it) } } - fun exitContract(functionCall: FirFunctionCall): ExitContractNode { - return createExitContractNode(functionCall).also { addNewSimpleNode(it) } + fun exitContract(qualifiedAccess: FirQualifiedAccess): ExitContractNode { + return createExitContractNode(qualifiedAccess).also { addNewSimpleNode(it) } } fun exitBinaryOr(binaryLogicExpression: FirBinaryLogicExpression): BinaryOrExitNode { diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphNodeBuilder.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphNodeBuilder.kt index 6f36c73ef45..e43cb938afd 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphNodeBuilder.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphNodeBuilder.kt @@ -132,10 +132,10 @@ fun ControlFlowGraphBuilder.createAnnotationEnterNode(fir: FirAnnotationCall): A fun ControlFlowGraphBuilder.createVariableDeclarationNode(fir: FirProperty): VariableDeclarationNode = VariableDeclarationNode(graph, fir, levelCounter, createId()) -fun ControlFlowGraphBuilder.createExitContractNode(fir: FirFunctionCall): ExitContractNode = +fun ControlFlowGraphBuilder.createExitContractNode(fir: FirQualifiedAccess): ExitContractNode = ExitContractNode(graph, fir, levelCounter, createId()) -fun ControlFlowGraphBuilder.createEnterContractNode(fir: FirFunctionCall): EnterContractNode = +fun ControlFlowGraphBuilder.createEnterContractNode(fir: FirQualifiedAccess): EnterContractNode = EnterContractNode(graph, fir, levelCounter, createId()) fun ControlFlowGraphBuilder.createConstExpressionNode(fir: FirConstExpression<*>): ConstExpressionNode = diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/contracts/ConeConditionalEffectToFirVisitor.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/contracts/ConeConditionalEffectToFirVisitor.kt index 861aadedd68..a7f9e28cbe7 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/contracts/ConeConditionalEffectToFirVisitor.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/contracts/ConeConditionalEffectToFirVisitor.kt @@ -85,18 +85,23 @@ fun ConeConditionalEffectDeclaration.buildContractFir(argumentMapping: Map? { - val function = functionCall.toResolvedCallableSymbol()?.fir as? FirSimpleFunction ?: return null +fun createArgumentsMapping(qualifiedAccess: FirQualifiedAccess): Map? { val argumentsMapping = mutableMapOf() - functionCall.extensionReceiver.takeIf { it != FirNoReceiverExpression }?.let { argumentsMapping[-1] = it } - ?: functionCall.dispatchReceiver.takeIf { it != FirNoReceiverExpression }?.let { argumentsMapping[-1] = it } - - - val nameToIndex = function.valueParameters.mapIndexed { index, parameter -> parameter.name to index }.toMap() - functionCall.arguments.forEachIndexed { index, argument -> - when (argument) { - is FirNamedArgumentExpression -> argumentsMapping[nameToIndex[argument.name]!!] = argument - else -> argumentsMapping[index] = argument + qualifiedAccess.extensionReceiver.takeIf { it != FirNoReceiverExpression }?.let { argumentsMapping[-1] = it } + ?: qualifiedAccess.dispatchReceiver.takeIf { it != FirNoReceiverExpression }?.let { argumentsMapping[-1] = it } + when (qualifiedAccess) { + is FirFunctionCall -> { + val function = qualifiedAccess.toResolvedCallableSymbol()?.fir as? FirSimpleFunction ?: return null + val nameToIndex = function.valueParameters.mapIndexed { index, parameter -> parameter.name to index }.toMap() + qualifiedAccess.arguments.forEachIndexed { index, argument -> + when (argument) { + is FirNamedArgumentExpression -> argumentsMapping[nameToIndex[argument.name]!!] = argument + else -> argumentsMapping[index] = argument + } + } + } + is FirVariableAssignment -> { + argumentsMapping[0] = qualifiedAccess.rValue } } return argumentsMapping