diff --git a/compiler/fir/analysis-tests/testData/resolve/smartcasts/booleans/jumpFromRhsOfOperator.dot b/compiler/fir/analysis-tests/testData/resolve/smartcasts/booleans/jumpFromRhsOfOperator.dot index 7738dfcf264..a6c351b20a7 100644 --- a/compiler/fir/analysis-tests/testData/resolve/smartcasts/booleans/jumpFromRhsOfOperator.dot +++ b/compiler/fir/analysis-tests/testData/resolve/smartcasts/booleans/jumpFromRhsOfOperator.dot @@ -388,10 +388,11 @@ digraph jumpFromRhsOfOperator_kt { 153 [label="Exit when"]; } 154 [label="Access variable R|/a|"]; - 155 [label="Function call: R|/a|.#()"]; - 156 [label="Exit block"]; + 155 [label="Smart cast: R|/a|"]; + 156 [label="Function call: R|/a|.#()"]; + 157 [label="Exit block"]; } - 157 [label="Exit function test_7" style="filled" fillcolor=red]; + 158 [label="Exit function test_7" style="filled" fillcolor=red]; } 130 -> {131}; 131 -> {132}; @@ -404,7 +405,7 @@ digraph jumpFromRhsOfOperator_kt { 138 -> {143 139}; 139 -> {140}; 140 -> {141}; - 141 -> {157} [label=onUncaughtException]; + 141 -> {158} [label=onUncaughtException]; 141 -> {142} [style=dotted]; 142 -> {143} [style=dotted]; 143 -> {144}; @@ -421,54 +422,55 @@ digraph jumpFromRhsOfOperator_kt { 154 -> {155}; 155 -> {156}; 156 -> {157}; + 157 -> {158}; subgraph cluster_32 { color=red - 158 [label="Enter function test_8" style="filled" fillcolor=red]; + 159 [label="Enter function test_8" style="filled" fillcolor=red]; subgraph cluster_33 { color=blue - 159 [label="Enter block"]; + 160 [label="Enter block"]; subgraph cluster_34 { color=blue - 160 [label="Enter when"]; + 161 [label="Enter when"]; subgraph cluster_35 { color=blue - 161 [label="Enter when branch condition "]; + 162 [label="Enter when branch condition "]; subgraph cluster_36 { color=blue - 162 [label="Enter &&"]; - 163 [label="Access variable R|/a|"]; - 164 [label="Const: Null(null)"]; - 165 [label="Equality operator !="]; - 166 [label="Exit left part of &&"]; - 167 [label="Enter right part of &&"]; - 168 [label="Function call: R|java/lang/Exception.Exception|()"]; - 169 [label="Throw: throw R|java/lang/Exception.Exception|()"]; - 170 [label="Stub" style="filled" fillcolor=gray]; - 171 [label="Exit &&"]; + 163 [label="Enter &&"]; + 164 [label="Access variable R|/a|"]; + 165 [label="Const: Null(null)"]; + 166 [label="Equality operator !="]; + 167 [label="Exit left part of &&"]; + 168 [label="Enter right part of &&"]; + 169 [label="Function call: R|java/lang/Exception.Exception|()"]; + 170 [label="Throw: throw R|java/lang/Exception.Exception|()"]; + 171 [label="Stub" style="filled" fillcolor=gray]; + 172 [label="Exit &&"]; } - 172 [label="Exit when branch condition"]; + 173 [label="Exit when branch condition"]; } - 173 [label="Synthetic else branch"]; - 174 [label="Enter when branch result"]; + 174 [label="Synthetic else branch"]; + 175 [label="Enter when branch result"]; subgraph cluster_37 { color=blue - 175 [label="Enter block"]; - 176 [label="Access variable R|/a|"]; - 177 [label="Smart cast: R|/a|"]; - 178 [label="Function call: R|/a|.#()"]; - 179 [label="Exit block"]; + 176 [label="Enter block"]; + 177 [label="Access variable R|/a|"]; + 178 [label="Smart cast: R|/a|"]; + 179 [label="Function call: R|/a|.#()"]; + 180 [label="Exit block"]; } - 180 [label="Exit when branch result"]; - 181 [label="Exit when"]; + 181 [label="Exit when branch result"]; + 182 [label="Exit when"]; } - 182 [label="Access variable R|/a|"]; - 183 [label="Function call: R|/a|.#()"]; - 184 [label="Exit block"]; + 183 [label="Access variable R|/a|"]; + 184 [label="Smart cast: R|/a|"]; + 185 [label="Function call: R|/a|.#()"]; + 186 [label="Exit block"]; } - 185 [label="Exit function test_8" style="filled" fillcolor=red]; + 187 [label="Exit function test_8" style="filled" fillcolor=red]; } - 158 -> {159}; 159 -> {160}; 160 -> {161}; 161 -> {162}; @@ -476,16 +478,16 @@ digraph jumpFromRhsOfOperator_kt { 163 -> {164}; 164 -> {165}; 165 -> {166}; - 166 -> {171 167}; - 167 -> {168}; + 166 -> {167}; + 167 -> {172 168}; 168 -> {169}; - 169 -> {185} [label=onUncaughtException]; - 169 -> {170} [style=dotted]; + 169 -> {170}; + 170 -> {187} [label=onUncaughtException]; 170 -> {171} [style=dotted]; - 171 -> {172}; - 172 -> {174 173}; - 173 -> {181}; - 174 -> {175}; + 171 -> {172} [style=dotted]; + 172 -> {173}; + 173 -> {175 174}; + 174 -> {182}; 175 -> {176}; 176 -> {177}; 177 -> {178}; @@ -496,5 +498,7 @@ digraph jumpFromRhsOfOperator_kt { 182 -> {183}; 183 -> {184}; 184 -> {185}; + 185 -> {186}; + 186 -> {187}; } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirReturnsImpliesAnalyzer.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirReturnsImpliesAnalyzer.kt index accd2a96962..976265e9742 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirReturnsImpliesAnalyzer.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirReturnsImpliesAnalyzer.kt @@ -181,9 +181,7 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() { } } is ConeIsInstancePredicate -> - getOrCreateRealVariable(arg)?.let { - if (isNegated == inverted) it typeEq type else it typeNotEq type - }?.singleton() + if (isNegated == inverted) getOrCreateRealVariable(arg)?.let { it typeEq type }?.singleton() else mapOf() is ConeIsNullPredicate -> getOrCreateRealVariable(arg)?.nullabilityStatement(context.session.builtinTypes, isNull = isNegated == inverted) ?.singleton() @@ -195,10 +193,7 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() { } private fun RealVariable.nullabilityStatement(builtinTypes: BuiltinTypes, isNull: Boolean) = - if (isNull) - this typeEq builtinTypes.nullableNothingType.type andTypeNotEq builtinTypes.anyType.type - else - this typeEq builtinTypes.anyType.type andTypeNotEq builtinTypes.nullableNothingType.type + this typeEq (if (isNull) builtinTypes.nullableNothingType.type else builtinTypes.anyType.type) private fun TypeStatement.singleton(): TypeStatements = mapOf(variable to this) 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 3c530bd773b..995a89eca0a 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 @@ -433,7 +433,6 @@ abstract class FirDataFlowAnalyzer( val expressionVariable = variableStorage.createSyntheticVariable(typeOperatorCall) if (operandVariable.isReal()) { flow.addImplication((expressionVariable eq isType) implies (operandVariable typeEq type)) - flow.addImplication((expressionVariable notEq isType) implies (operandVariable typeNotEq type)) } if (!type.canBeNull) { flow.addImplication((expressionVariable eq isType) implies (operandVariable notEq null)) @@ -459,9 +458,6 @@ abstract class FirDataFlowAnalyzer( flow.addImplication((expressionVariable notEq null) implies (operandVariable notEq null)) if (operandVariable.isReal()) { flow.addImplication((expressionVariable notEq null) implies (operandVariable typeEq type)) - if (!type.canBeNull) { - flow.addImplication((expressionVariable eq null) implies (operandVariable typeNotEq type)) - } } } @@ -614,12 +610,10 @@ abstract class FirDataFlowAnalyzer( if (leftOperandVariable.isReal()) { flow.addImplication((expressionVariable eq isEq) implies (leftOperandVariable typeEq rightOperandType)) - flow.addImplication((expressionVariable notEq isEq) implies (leftOperandVariable typeNotEq rightOperandType)) } if (rightOperandVariable.isReal()) { flow.addImplication((expressionVariable eq isEq) implies (rightOperandVariable typeEq leftOperandType)) - flow.addImplication((expressionVariable notEq isEq) implies (rightOperandVariable typeNotEq leftOperandType)) } } @@ -686,7 +680,7 @@ abstract class FirDataFlowAnalyzer( private fun FLOW.assumeNotNull(variable: DataFlowVariable, shouldForkFlow: Boolean, shouldRemoveSynthetics: Boolean): FLOW = logicSystem.approveStatementsInsideFlow(this, variable notEq null, shouldForkFlow, shouldRemoveSynthetics).also { if (variable is RealVariable) { - it.addTypeStatement(variable typeEq any andTypeNotEq nullableNothing) + it.addTypeStatement(variable typeEq any) } } @@ -1446,8 +1440,8 @@ abstract class FirDataFlowAnalyzer( val variable = effect.variable if (variable.isReal()) { when (effect.operation) { - Operation.EqNull -> variable typeEq nullableNothing andTypeNotEq any - Operation.NotEqNull -> variable typeEq any andTypeNotEq nullableNothing + Operation.EqNull -> variable typeEq nullableNothing + Operation.NotEqNull -> variable typeEq any else -> null }?.let { logicSystem.addImplication(this, statement.condition implies it) } } 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 9d53fdd4fbf..e40bace27f0 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 @@ -91,31 +91,18 @@ abstract class LogicSystem(protected val context: ConeInferenceCont } } - // ------------------------------- Util functions ------------------------------- - - private fun foldStatements(statements: Collection, all: Boolean): TypeStatement { - require(statements.isNotEmpty()) - statements.singleOrNull()?.let { return it } - val variable = statements.first().variable - assert(statements.all { it.variable == variable }) - // TypeStatement(variable, exactType, exactNotType) = - // variable is intersect(exactType) && variable !is intersect(exactNotType) - // So `and` of two type statements computes `and` of exactType and `or` of `exactNotType`, - // while `or` is the opposite. - return if (all) { - val exactType = statements.flatMapTo(mutableSetOf()) { it.exactType } - // variable !is a && variable !is b =/=> variable !is commonSuperType(a, b) - // So in this case we can only take the union if either type is a subtype of the other. - val exactNotType = unifyTypes(statements.map { it.exactNotType }, onlyInputTypes = true) - MutableTypeStatement(variable, exactType, exactNotType?.let { mutableSetOf(it) } ?: mutableSetOf()) - } else { - val exactType = unifyTypes(statements.map { it.exactType }, onlyInputTypes = false) - val exactNotType = statements.flatMapTo(mutableSetOf()) { it.exactNotType } - MutableTypeStatement(variable, exactType?.let { mutableSetOf(it) } ?: mutableSetOf(), exactNotType) + private inline fun Collection.singleOrNew(exactType: () -> MutableSet): TypeStatement = + when (size) { + 0 -> throw AssertionError("need at least one statement") + 1 -> first() + else -> { + val variable = first().variable + assert(all { it.variable == variable }) { "folding statements for different variables" } + MutableTypeStatement(variable, exactType()) + } } - } - private fun unifyTypes(types: Collection>, onlyInputTypes: Boolean): ConeKotlinType? { + private fun unifyTypes(types: Collection>): ConeKotlinType? { if (types.any { it.isEmpty() }) return null val intersected = types.map { ConeTypeIntersector.intersectTypes(context, it.toList()) } val unified = context.commonSuperTypeOrNull(intersected) ?: return null @@ -123,14 +110,14 @@ abstract class LogicSystem(protected val context: ConeInferenceCont unified.isAcceptableForSmartcast() -> unified unified.canBeNull -> null else -> context.anyType() - }.takeIf { !onlyInputTypes || it in intersected } + } } protected fun and(statements: Collection): TypeStatement = - foldStatements(statements, all = true) + statements.singleOrNew { statements.flatMapTo(mutableSetOf()) { it.exactType } } protected fun or(statements: Collection): TypeStatement = - foldStatements(statements, all = false) + statements.singleOrNew { unifyTypes(statements.map { it.exactType })?.let { mutableSetOf(it) } ?: mutableSetOf() } } /* 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 e3f035556ee..242d6f6bd89 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 @@ -16,7 +16,6 @@ import kotlin.math.max data class PersistentTypeStatement( override val variable: RealVariable, override val exactType: PersistentSet, - override val exactNotType: PersistentSet ) : TypeStatement() typealias PersistentApprovedTypeStatements = PersistentMap @@ -391,7 +390,7 @@ private fun lowestCommonFlow(left: PersistentFlow, right: PersistentFlow): Persi private fun TypeStatement.toPersistent(): PersistentTypeStatement = when (this) { is PersistentTypeStatement -> this - else -> PersistentTypeStatement(variable, exactType.toPersistentSet(), exactNotType.toPersistentSet()) + else -> PersistentTypeStatement(variable, exactType.toPersistentSet()) } @JvmName("replaceVariableInStatements") @@ -436,5 +435,5 @@ private fun Statement.replaceVariable(from: RealVariable, to: RealVariable): Sta if (variable != from) this else when (this) { is OperationStatement -> copy(variable = to) is PersistentTypeStatement -> copy(variable = to) - is MutableTypeStatement -> MutableTypeStatement(to, exactType, exactNotType) + is MutableTypeStatement -> MutableTypeStatement(to, exactType) } diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/model.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/model.kt index 15b7ec665df..61f683b4e2a 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/model.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/model.kt @@ -15,9 +15,8 @@ import kotlin.contracts.contract class MutableTypeStatement( override val variable: RealVariable, override val exactType: MutableSet = linkedSetOf(), - override val exactNotType: MutableSet = linkedSetOf() ) : TypeStatement() { - fun copy(): MutableTypeStatement = MutableTypeStatement(variable, LinkedHashSet(exactType), LinkedHashSet(exactNotType)) + fun copy(): MutableTypeStatement = MutableTypeStatement(variable, LinkedHashSet(exactType)) } fun Implication.invertCondition(): Implication = Implication(condition.invert(), effect) @@ -49,25 +48,7 @@ infix fun DataFlowVariable.notEq(constant: Boolean?): OperationStatement { infix fun OperationStatement.implies(effect: Statement): Implication = Implication(this, effect) infix fun RealVariable.typeEq(type: ConeKotlinType): MutableTypeStatement = - MutableTypeStatement(this) andTypeEq type - -infix fun RealVariable.typeNotEq(type: ConeKotlinType): MutableTypeStatement = - MutableTypeStatement(this) andTypeNotEq type - -infix fun MutableTypeStatement.andTypeEq(type: ConeKotlinType): MutableTypeStatement = - this.apply { - if (type !is ConeErrorType) { - exactType += type - } - } - -infix fun MutableTypeStatement.andTypeNotEq(type: ConeKotlinType): MutableTypeStatement = - this.apply { - require(exactNotType.isEmpty()) { "statement $this already has a negation; use `logicSystem.and`" } - if (type !is ConeErrorType) { - exactNotType += type - } - } + MutableTypeStatement(this, if (type is ConeErrorType) linkedSetOf() else linkedSetOf(type)) // --------------------------------------- Utils --------------------------------------- diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/statements.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/statements.kt index 718d55bf427..c67506785bb 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/statements.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/statements.kt @@ -31,16 +31,15 @@ data class OperationStatement(override val variable: DataFlowVariable, val opera sealed class TypeStatement : Statement() { abstract override val variable: RealVariable abstract val exactType: Set - abstract val exactNotType: Set val isEmpty: Boolean - get() = exactType.isEmpty() && exactNotType.isEmpty() + get() = exactType.isEmpty() val isNotEmpty: Boolean get() = !isEmpty override fun toString(): String { - return "$variable: $exactType, $exactNotType" + return "$variable: $exactType" } } diff --git a/compiler/testData/diagnostics/tests/smartCasts/loops/safeCallBreakInsideDoWhile.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/loops/safeCallBreakInsideDoWhile.fir.kt index 2c3d36fffa2..c4a3c5e1d0b 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/loops/safeCallBreakInsideDoWhile.fir.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/loops/safeCallBreakInsideDoWhile.fir.kt @@ -8,7 +8,7 @@ fun calc(x: String?, y: String?): Int { // x is not null in condition but we do not see it yet } while (x.length > 0) // y is nullable because of break - y.length + y.length // x is not null, at least in theory return x.length } diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/4.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/4.fir.kt index f10e197ec29..fc8cdaa6fbf 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/4.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/4.fir.kt @@ -102,7 +102,7 @@ fun case_11(x: TypealiasString, y: TypealiasString) { if (y == null) { if (stringProperty != null) { if (false || false || false || z == null || false) { - x + x } } } diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/40.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/40.fir.kt index 43411254616..c31d1d048c9 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/40.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/40.fir.kt @@ -134,8 +134,8 @@ fun case_14(x: Any?) { if (x == null) { x x?.equals(10) - x!!.equals(10) - x.equals(10) + x!!.equals(10) + x.equals(10) } } @@ -148,8 +148,8 @@ fun case_15(x: Any?) { if (x !== null) else { x x?.equals(10) - x!!.equals(10) - x.equals(10) + x!!.equals(10) + x.equals(10) } } diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/6.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/6.fir.kt index e74948d4bdc..f37dde6069f 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/6.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/6.fir.kt @@ -840,8 +840,8 @@ fun case_46(x: TypealiasNullableString?, y: TypealiasNu if (y === nullableNothingProperty) { if (z != nullableStringProperty) { if (z === t || t == nullableNothingProperty) { - x - x.hashCode() + x + x.hashCode() } } }