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 b2df69f6479..7d418ee377c 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 @@ -3474,6 +3474,12 @@ public class DiagnosisCompilerFirTestdataTestGenerated extends AbstractDiagnosis @TestMetadata("compiler/fir/analysis-tests/testData/resolve/smartcasts") @TestDataPath("$PROJECT_ROOT") public class Smartcasts { + @Test + @TestMetadata("accessToMemberAfterReceiverReassignment.kt") + public void testAccessToMemberAfterReceiverReassignment() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/smartcasts/accessToMemberAfterReceiverReassignment.kt"); + } + @Test public void testAllFilesPresentInSmartcasts() throws Exception { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/smartcasts"), Pattern.compile("^([^.]+)\\.kt$"), null, true); 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 a26c351db8e..692c9f042b0 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 @@ -3075,6 +3075,11 @@ public class LazyBodyIsNotTouchedTilContractsPhaseTestGenerated extends Abstract KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); } + @TestMetadata("accessToMemberAfterReceiverReassignment.kt") + public void testAccessToMemberAfterReceiverReassignment() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/smartcasts/accessToMemberAfterReceiverReassignment.kt"); + } + public void testAllFilesPresentInSmartcasts() throws Exception { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/smartcasts"), Pattern.compile("^([^.]+)\\.kt$"), null, true); } diff --git a/compiler/fir/analysis-tests/testData/resolve/smartcasts/accessToMemberAfterReceiverReassignment.fir.txt b/compiler/fir/analysis-tests/testData/resolve/smartcasts/accessToMemberAfterReceiverReassignment.fir.txt new file mode 100644 index 00000000000..0fb82d67237 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/smartcasts/accessToMemberAfterReceiverReassignment.fir.txt @@ -0,0 +1,64 @@ +FILE: accessToMemberAfterReceiverReassignment.kt + public final class My : R|kotlin/Any| { + public constructor(x: R|My?|, z: R|My?| = Null(null)): R|My| { + super() + } + + public final val x: R|My?| = R|/x| + public get(): R|My?| + + public final val z: R|My?| = R|/z| + public get(): R|My?| + + } + public final fun baseTest(): R|kotlin/Unit| { + lvar y: R|My?| = R|/My.My|(R|/My.My|(Null(null))) + when () { + !=(R|/y|?.{ $subj$.R|/My.x| }, Null(null)) -> { + R|/y|.R|/My.x|.R|/My.x| + R|/y| = R|/My.My|(Null(null)) + R|/y|.R|/My.x|.# + } + } + + } + public final fun deepChainTest(): R|kotlin/Unit| { + lvar y: R|My?| = R|/My.My|(R|/My.My|(Null(null))) + when () { + !=(R|/y|?.{ $subj$.R|/My.x| }?.{ $subj$.R|/My.x| }, Null(null)) -> { + R|/y|.R|/My.x|.R|/My.x|.R|/My.x| + R|/y| = R|/My.My|(Null(null)) + R|/y|.R|/My.x|.#.# + } + } + + } + public final fun backwardAliasTest(z: R|My|): R|kotlin/Unit| { + lvar y: R|My| = R|/z| + when () { + !=(R|/y|.R|/My.x|, Null(null)) -> { + R|/y|.R|/My.x|.R|/My.x| + R|/y| = R|/My.My|(Null(null)) + R|/y|.R|/My.x|.# + } + } + + } + public final fun severalMembersTest(): R|kotlin/Unit| { + lvar y: R|My| = R|/My.My|(R|/My.My|(Null(null)), R|/My.My|(Null(null))) + when () { + !=(R|/y|.R|/My.x|, Null(null)) -> { + when () { + !=(R|/y|.R|/My.z|, Null(null)) -> { + R|/y|.R|/My.x|.R|/My.x| + R|/y|.R|/My.z|.R|/My.z| + R|/y| = R|/My.My|(Null(null)) + R|/y|.R|/My.x|.# + R|/y|.R|/My.z|.# + } + } + + } + } + + } diff --git a/compiler/fir/analysis-tests/testData/resolve/smartcasts/accessToMemberAfterReceiverReassignment.kt b/compiler/fir/analysis-tests/testData/resolve/smartcasts/accessToMemberAfterReceiverReassignment.kt new file mode 100644 index 00000000000..2a02c17fcf4 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/smartcasts/accessToMemberAfterReceiverReassignment.kt @@ -0,0 +1,41 @@ +class My(val x: My?, val z: My? = null) + +fun baseTest() { + var y: My? = My(My(null)) + if (y?.x != null) { + y.x.x + y = My(null) + y.x.x + } +} + +fun deepChainTest() { + var y: My? = My(My(null)) + if (y?.x?.x != null) { + y.x.x.x + y = My(null) + y.x.x.x + } +} + +fun backwardAliasTest(z: My) { + var y = z + if (y.x != null) { + y.x.x + y = My(null) + y.x.x + } +} + +fun severalMembersTest() { + var y = My(My(null), My(null)) + if (y.x != null) { + if (y.z != null) { + y.x.x + y.z.z + y = My(null) + y.x.x + y.z.z + } + } +} \ No newline at end of file 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 c855cd8a961..098975860c6 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 @@ -3474,6 +3474,12 @@ public class FirDiagnosticTestGenerated extends AbstractFirDiagnosticTest { @TestMetadata("compiler/fir/analysis-tests/testData/resolve/smartcasts") @TestDataPath("$PROJECT_ROOT") public class Smartcasts { + @Test + @TestMetadata("accessToMemberAfterReceiverReassignment.kt") + public void testAccessToMemberAfterReceiverReassignment() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/smartcasts/accessToMemberAfterReceiverReassignment.kt"); + } + @Test public void testAllFilesPresentInSmartcasts() throws Exception { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/smartcasts"), Pattern.compile("^([^.]+)\\.kt$"), null, true); 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 12cfff39ba9..40fadc30c89 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 @@ -3474,6 +3474,12 @@ public class FirDiagnosticsWithLightTreeTestGenerated extends AbstractFirDiagnos @TestMetadata("compiler/fir/analysis-tests/testData/resolve/smartcasts") @TestDataPath("$PROJECT_ROOT") public class Smartcasts { + @Test + @TestMetadata("accessToMemberAfterReceiverReassignment.kt") + public void testAccessToMemberAfterReceiverReassignment() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/smartcasts/accessToMemberAfterReceiverReassignment.kt"); + } + @Test public void testAllFilesPresentInSmartcasts() throws Exception { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/smartcasts"), Pattern.compile("^([^.]+)\\.kt$"), null, true); 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 4110a9bd260..feb184ed1f1 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 @@ -863,7 +863,7 @@ abstract class FirDataFlowAnalyzer( if (possiblyChangedVariables.isEmpty()) return val flow = node.flow for (variable in possiblyChangedVariables) { - flow.removeAllAboutVariable(variable) + logicSystem.removeAllAboutVariable(flow, variable) } } @@ -1170,7 +1170,7 @@ abstract class FirDataFlowAnalyzer( val isAssignment = assignment != null if (isAssignment) { logicSystem.removeLocalVariableAlias(flow, propertyVariable) - flow.removeAllAboutVariable(propertyVariable) + logicSystem.removeAllAboutVariable(flow, propertyVariable) logicSystem.recordNewAssignment(flow, propertyVariable, context.newAssignmentIndex()) } @@ -1508,13 +1508,6 @@ abstract class FirDataFlowAnalyzer( logicSystem.addTypeStatement(this, info) } - private fun FLOW.removeAllAboutVariable(variable: RealVariable?) { - if (variable == null) return - logicSystem.removeTypeStatementsAboutVariable(this, variable) - logicSystem.removeLogicStatementsAboutVariable(this, variable) - logicSystem.removeAliasInformationAboutVariable(this, variable) - } - private fun FLOW.fork(): FLOW { return logicSystem.forkFlow(this) } diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/DfaVariables.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/DfaVariables.kt index ac1edadcfb9..a41fa04d9fe 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/DfaVariables.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/DfaVariables.kt @@ -61,6 +61,8 @@ class RealVariable( variableIndexForDebug: Int, val stability: PropertyStability, ) : DataFlowVariable(variableIndexForDebug) { + val dependentVariables = mutableSetOf() + override fun equals(other: Any?): Boolean { return this === other } diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt index 501a251b0a9..9fc6baf5c60 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt @@ -22,6 +22,13 @@ abstract class LogicSystem(protected val context: ConeInferenceCont abstract fun addImplication(flow: FLOW, implication: Implication) + fun removeAllAboutVariable(flow: FLOW, variable: RealVariable?) { + if (variable == null) return + removeTypeStatementsAboutVariable(flow, variable) + removeLogicStatementsAboutVariable(flow, variable) + removeAliasInformationAboutVariable(flow, variable) + } + abstract fun removeTypeStatementsAboutVariable(flow: FLOW, variable: RealVariable) abstract fun removeLogicStatementsAboutVariable(flow: FLOW, variable: DataFlowVariable) abstract fun removeAliasInformationAboutVariable(flow: FLOW, variable: RealVariable) diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/PersistentLogicSystem.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/PersistentLogicSystem.kt index 52035e95af2..eaa7461f4e1 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/PersistentLogicSystem.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/PersistentLogicSystem.kt @@ -135,11 +135,11 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste val variables = flows.flatMap { it.approvedTypeStatements.keys }.toSet() for (variable in variables) { val info = mergeOperation(flows.map { it.getApprovedTypeStatements(variable) }) ?: continue - removeTypeStatementsAboutVariable(commonFlow, variable) + commonFlow.approvedTypeStatements -= variable + commonFlow.approvedTypeStatementsDiff -= variable val thereWereReassignments = variable.hasDifferentReassignments(flows) if (thereWereReassignments) { - removeLogicStatementsAboutVariable(commonFlow, variable) - removeAliasInformationAboutVariable(commonFlow, variable) + removeAllAboutVariable(commonFlow, variable) } commonFlow.addApprovedStatements(info) } @@ -222,18 +222,24 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste flow.logicStatements[backAlias]?.let { existing -> existing + newStatements } ?: newStatements.toPersistentList() flow.logicStatements = flow.logicStatements.put(backAlias, replacedStatements) } - flow.approvedTypeStatements[alias]?.let { it -> - val newStatements = it.replaceVariable(alias, backAlias) - val replacedStatements = - flow.approvedTypeStatements[backAlias]?.let { existing -> existing + newStatements } ?: newStatements - flow.approvedTypeStatements = flow.approvedTypeStatements.put(backAlias, replacedStatements.toPersistent()) - } - flow.approvedTypeStatementsDiff[alias]?.let { it -> - val newStatements = it.replaceVariable(alias, backAlias) - val replacedStatements = - flow.approvedTypeStatementsDiff[backAlias]?.let { existing -> existing + newStatements } ?: newStatements - flow.approvedTypeStatementsDiff = flow.approvedTypeStatementsDiff.put(backAlias, replacedStatements.toPersistent()) + + fun processApprovedStatements(isDiff: Boolean) { + val statements = if (isDiff) flow.approvedTypeStatementsDiff else flow.approvedTypeStatements + statements[alias]?.let { it -> + val newStatements = it.replaceVariable(alias, backAlias) + val replacedStatements = + statements[backAlias]?.let { existing -> existing + newStatements } ?: newStatements + val result = statements.put(backAlias, replacedStatements.toPersistent()) + if (isDiff) { + flow.approvedTypeStatementsDiff = result + } else { + flow.approvedTypeStatements = result + } + } } + + processApprovedStatements(isDiff = false) + processApprovedStatements(isDiff = true) } val original = flow.directAliasMap[alias]?.variable @@ -310,17 +316,31 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste override fun removeTypeStatementsAboutVariable(flow: PersistentFlow, variable: RealVariable) { flow.approvedTypeStatements -= variable flow.approvedTypeStatementsDiff -= variable + variable.forEachTransitiveDependentVariable { + flow.approvedTypeStatements -= it + flow.approvedTypeStatementsDiff -= it + } } override fun removeLogicStatementsAboutVariable(flow: PersistentFlow, variable: DataFlowVariable) { flow.logicStatements -= variable + val realVariable = variable as? RealVariable + realVariable?.forEachTransitiveDependentVariable { + flow.logicStatements -= it + } var newLogicStatements = flow.logicStatements for ((key, implications) in flow.logicStatements) { val implicationsToDelete = mutableListOf() implications.forEach { implication -> - if (implication.effect.variable == variable) { + val implicationVariable = implication.effect.variable + if (implicationVariable == variable) { implicationsToDelete += implication } + realVariable?.forEachTransitiveDependentVariable { + if (implicationVariable == it) { + implicationsToDelete += implication + } + } } if (implicationsToDelete.isEmpty()) continue val newImplications = implications.removeAll(implicationsToDelete) @@ -346,6 +366,13 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste } } + private fun RealVariable.forEachTransitiveDependentVariable(action: (RealVariable) -> Unit) { + dependentVariables.forEach { + it.forEachTransitiveDependentVariable(action) + action(it) + } + } + override fun translateVariableFromConditionInStatements( flow: PersistentFlow, originalVariable: DataFlowVariable, diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/VariableStorageImpl.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/VariableStorageImpl.kt index 34d5b16c965..80e4e9fad61 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/VariableStorageImpl.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/VariableStorageImpl.kt @@ -106,7 +106,9 @@ class VariableStorageImpl(private val session: FirSession) : VariableStorage() { } val receiverVariable = receiver?.let { getOrCreateVariable(flow, it) } - return RealVariable(identifier, isThisReference, receiverVariable, counter++, stability) + return RealVariable(identifier, isThisReference, receiverVariable, counter++, stability).also { + (receiverVariable as? RealVariable)?.dependentVariables?.add(it) + } } @JvmName("getOrCreateRealVariableOrNull")