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 3e9b5556d98..6b3ec58294d 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 @@ -34,6 +34,7 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol import org.jetbrains.kotlin.fir.types.* +import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.StandardClassIds import org.jetbrains.kotlin.types.ConstantValueKind import org.jetbrains.kotlin.util.OperatorNameConventions @@ -439,7 +440,7 @@ abstract class FirDataFlowAnalyzer( private fun processEqConst( flow: MutableFlow, - expression: FirExpression, + expression: FirEqualityOperatorCall, operand: FirExpression, const: FirConstExpression<*>, isEq: Boolean @@ -450,6 +451,7 @@ abstract class FirDataFlowAnalyzer( val operandVariable = variableStorage.getOrCreateIfReal(flow, operand) ?: return val expressionVariable = variableStorage.createSynthetic(expression) + if (const.kind == ConstantValueKind.Boolean && operand.resolvedType.isBooleanOrNullableBoolean) { val expected = (const.value as Boolean) flow.addImplication((expressionVariable eq isEq) implies (operandVariable eq expected)) @@ -460,6 +462,11 @@ abstract class FirDataFlowAnalyzer( // expression == non-null const -> expression != null flow.addImplication((expressionVariable eq isEq) implies (operandVariable notEq null)) } + + // We can imply type information if the constant is the left operand and is a supported primitive type. + if (operandVariable is RealVariable && const == expression.arguments[0] && isSmartcastPrimitive(const.resolvedType.classId)) { + flow.addImplication((expressionVariable eq isEq) implies (operandVariable typeEq const.resolvedType)) + } } private fun processEqNull(flow: MutableFlow, expression: FirExpression, operand: FirExpression, isEq: Boolean) { @@ -542,8 +549,9 @@ abstract class FirDataFlowAnalyzer( val status = resolvedStatus if (checkModality && status.modality != Modality.FINAL) return true if (status.isExpect) return true + if (isSmartcastPrimitive(classId)) return false when (classId) { - StandardClassIds.Any, StandardClassIds.String -> return false + StandardClassIds.Any -> return false // Float and Double effectively had non-trivial `equals` semantics while they don't have explicit overrides (see KT-50535) StandardClassIds.Float, StandardClassIds.Double -> return true } @@ -563,6 +571,21 @@ abstract class FirDataFlowAnalyzer( } } + /** + * Determines if type smart-casting to the specified [ClassId] can be performed when values are + * compared via equality. Because this is determined using the ClassId, only standard built-in + * types are considered. + */ + private fun isSmartcastPrimitive(classId: ClassId?): Boolean { + return when (classId) { + // Support other primitives as well: KT-62246. + StandardClassIds.String, + -> true + + else -> false + } + } + // ----------------------------------- Jump ----------------------------------- fun enterJump(jump: FirJump<*>) { diff --git a/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/kt1778.fir.kt b/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/kt1778.fir.kt index ad92ea64684..411aebe6894 100644 --- a/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/kt1778.fir.kt +++ b/compiler/testData/diagnostics/tests/nullabilityAndSmartCasts/kt1778.fir.kt @@ -8,8 +8,8 @@ import checkSubtype fun main(args : Array) { val x = checkSubtype(args[0]) if(x is java.lang.CharSequence) { - if ("a" == x) x.length else x.length() // OK - if ("a" == x || "b" == x) x.length else x.length() // <– THEN ERROR - if ("a" == x && "a" == x) x.length else x.length() // <– ELSE ERROR + if ("a" == x) x.length else x.length() // OK + if ("a" == x || "b" == x) x.length else x.length() // <– THEN ERROR + if ("a" == x && "a" == x) x.length else x.length() // <– ELSE ERROR } } diff --git a/compiler/testData/diagnostics/tests/smartCasts/equals.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/equals.fir.kt index e9cd378080a..18d903ce1e7 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/equals.fir.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/equals.fir.kt @@ -23,3 +23,31 @@ fun gav(i: TestWithEquals?, j: TestWithEquals?) { if (i == j) foo(i) } } + +fun string(foo: Any) { + val string = "" + if ("" == foo) foo.length + if (string == foo) foo.length + if (foo == "") foo.length +} + +fun int(foo: Any) { + val int = 1 + if (1 == foo) foo.plus(1) + if (int == foo) foo.plus(1) + if (foo == 1) foo.plus(1) +} + +fun long(foo: Any) { + val long = 1L + if (1L == foo) foo.plus(1L) + if (long == foo) foo.plus(1L) + if (foo == 1L) foo.plus(1L) +} + +fun char(foo: Any) { + val char = 'a' + if ('a' == foo) foo.compareTo('a') + if (char == foo) foo.compareTo('a') + if (foo == 'a') foo.compareTo('a') +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/equals.kt b/compiler/testData/diagnostics/tests/smartCasts/equals.kt index b05e0e80aaa..db9450c121e 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/equals.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/equals.kt @@ -22,4 +22,32 @@ fun gav(i: TestWithEquals?, j: TestWithEquals?) { if (j == null) { if (i == j) foo(i) } -} \ No newline at end of file +} + +fun string(foo: Any) { + val string = "" + if ("" == foo) foo.length + if (string == foo) foo.length + if (foo == "") foo.length +} + +fun int(foo: Any) { + val int = 1 + if (1 == foo) foo.plus(1) + if (int == foo) foo.plus(1) + if (foo == 1) foo.plus(1) +} + +fun long(foo: Any) { + val long = 1L + if (1L == foo) foo.plus(1L) + if (long == foo) foo.plus(1L) + if (foo == 1L) foo.plus(1L) +} + +fun char(foo: Any) { + val char = 'a' + if ('a' == foo) foo.compareTo('a') + if (char == foo) foo.compareTo('a') + if (foo == 'a') foo.compareTo('a') +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/equals.txt b/compiler/testData/diagnostics/tests/smartCasts/equals.txt index 76b063c9af2..750d4f8dd15 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/equals.txt +++ b/compiler/testData/diagnostics/tests/smartCasts/equals.txt @@ -2,8 +2,12 @@ package public fun bar(/*0*/ i: Test?): kotlin.Unit public fun bar(/*0*/ i: TestWithEquals?): kotlin.Unit +public fun char(/*0*/ foo: kotlin.Any): kotlin.Unit public fun foo(/*0*/ x: kotlin.String?): kotlin.String? public fun gav(/*0*/ i: TestWithEquals?, /*1*/ j: TestWithEquals?): kotlin.Unit +public fun int(/*0*/ foo: kotlin.Any): kotlin.Unit +public fun long(/*0*/ foo: kotlin.Any): kotlin.Unit +public fun string(/*0*/ foo: kotlin.Any): kotlin.Unit public final class Test { public constructor Test()