From 0914c4dc263f112fc423075fb23b24dd5d3ce915 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Thu, 17 Oct 2024 16:14:42 +0200 Subject: [PATCH] [kotlin-dfa] Better approach to ignore smart-casts while computing DFA hints in debugging Fixes IDEA-360842 Kotlin Debugger: no DFA hints in if condition with return statement and smartcast GitOrigin-RevId: c7700f782d6298490d9604d60915014748b77f7a --- .../dfa/KotlinConstantConditionsInspection.kt | 12 +++--- .../debugger/dfaassist/K2DfaAssistProvider.kt | 7 +--- .../idea/k2/debugger/test/K2DfaAssistTest.kt | 38 +++++++++++++++++++ 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/plugins/kotlin/code-insight/inspections-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/inspections/dfa/KotlinConstantConditionsInspection.kt b/plugins/kotlin/code-insight/inspections-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/inspections/dfa/KotlinConstantConditionsInspection.kt index 4a306c31e7d9..8f28f59f26bf 100644 --- a/plugins/kotlin/code-insight/inspections-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/inspections/dfa/KotlinConstantConditionsInspection.kt +++ b/plugins/kotlin/code-insight/inspections-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/inspections/dfa/KotlinConstantConditionsInspection.kt @@ -316,7 +316,7 @@ class KotlinConstantConditionsInspection : AbstractKotlinInspection() { if (condition.textLength == 0) return true return isCompilationWarning(condition) } - + private fun isFailingBranchInExhaustiveWhen(condition: KtWhenCondition): Boolean { val entry = condition.parent as? KtWhenEntry ?: return false val whenExpr = entry.parent as? KtWhenExpression ?: return false @@ -538,11 +538,11 @@ class KotlinConstantConditionsInspection : AbstractKotlinInspection() { DfTypes.intValue(0), DfTypes.longValue(0) -> ConstantValue.ZERO else -> ConstantValue.UNKNOWN } - return analyze(expression) { shouldSuppress(constant, expression) } + return analyze(expression) { shouldSuppress(constant, expression, true) } } context(KaSession) - private fun shouldSuppress(value: ConstantValue, expression: KtExpression): Boolean { + private fun shouldSuppress(value: ConstantValue, expression: KtExpression, ignoreSmartCasts: Boolean = false): Boolean { var parent = expression.parent if (parent is KtDotQualifiedExpression && parent.selectorExpression == expression) { // Will be reported for parent qualified expression @@ -577,7 +577,7 @@ class KotlinConstantConditionsInspection : AbstractKotlinInspection() { ConstantValue.TRUE -> { //if (isUselessIsCheck(expression)) return true if (isAndOrConditionWithNothingOperand(expression, KtTokens.OROR)) return true - if (isSmartCastNecessary(expression, true)) return true + if (!ignoreSmartCasts && isSmartCastNecessary(expression, true)) return true if (isPairingConditionInWhen(expression)) return true if (isAssertion(parent, true)) return true } @@ -585,7 +585,7 @@ class KotlinConstantConditionsInspection : AbstractKotlinInspection() { ConstantValue.FALSE -> { //if (isUselessIsCheck(expression)) return true if (isAndOrConditionWithNothingOperand(expression, KtTokens.ANDAND)) return true - if (isSmartCastNecessary(expression, false)) return true + if (!ignoreSmartCasts && isSmartCastNecessary(expression, false)) return true if (isAssertion(parent, false)) return true } @@ -638,7 +638,7 @@ class KotlinConstantConditionsInspection : AbstractKotlinInspection() { } if (typeParameterType != null && expression.expectedType == typeParameterType) { // Do not report always-null when an expected expression type is the same type parameter - // as it's not possible to replace it with a null literal without an unchecked cast + // as it's not possible to replace it with a null literal without an unchecked cast return true } if (expression is KtBinaryExpressionWithTypeRHS && expression.left.isNull()) { diff --git a/plugins/kotlin/jvm-debugger/evaluation/k2/src/org/jetbrains/kotlin/idea/k2/debugger/dfaassist/K2DfaAssistProvider.kt b/plugins/kotlin/jvm-debugger/evaluation/k2/src/org/jetbrains/kotlin/idea/k2/debugger/dfaassist/K2DfaAssistProvider.kt index e429aa2f4c2d..b564dd27bd34 100644 --- a/plugins/kotlin/jvm-debugger/evaluation/k2/src/org/jetbrains/kotlin/idea/k2/debugger/dfaassist/K2DfaAssistProvider.kt +++ b/plugins/kotlin/jvm-debugger/evaluation/k2/src/org/jetbrains/kotlin/idea/k2/debugger/dfaassist/K2DfaAssistProvider.kt @@ -241,12 +241,7 @@ class K2DfaAssistProvider : DfaAssistProvider { var psi = when (anchor) { is KotlinAnchor.KotlinExpressionAnchor -> { if (!shouldTrackExpressionValue(anchor.expression)) return - if (KotlinConstantConditionsInspection.shouldSuppress(dfType, anchor.expression) && - dfType.tryNegate() - ?.let { negated -> KotlinConstantConditionsInspection.shouldSuppress(negated, anchor.expression) } != false - ) { - return - } + if (KotlinConstantConditionsInspection.shouldSuppress(dfType, anchor.expression)) return anchor.expression } diff --git a/plugins/kotlin/jvm-debugger/test/k2/test/org/jetbrains/kotlin/idea/k2/debugger/test/K2DfaAssistTest.kt b/plugins/kotlin/jvm-debugger/test/k2/test/org/jetbrains/kotlin/idea/k2/debugger/test/K2DfaAssistTest.kt index fe810ec56999..a4a910a85ffc 100644 --- a/plugins/kotlin/jvm-debugger/test/k2/test/org/jetbrains/kotlin/idea/k2/debugger/test/K2DfaAssistTest.kt +++ b/plugins/kotlin/jvm-debugger/test/k2/test/org/jetbrains/kotlin/idea/k2/debugger/test/K2DfaAssistTest.kt @@ -579,6 +579,44 @@ class K2DfaAssistTest : DfaAssistTest(), ExpectedPluginModeProvider { doTest(text) { _, _ -> } } + fun testSmartCastDoesNotAffectDebugging() { + val text = """ + fun useNullable(clazz: Any?) { + if (clazz == null/*TRUE*/) return + /*unreachable_start*/with(clazz) { + if (/*unreachable_start*/this !is String/*unreachable_end*/) /*unreachable_start*/return/*unreachable_end*/ + println(length) + }/*unreachable_end*/ + } + + fun main() { + useNullable(null) + } + """.trimIndent() + doTest(text) { vm, frame -> + frame.addVariable(MockLocalVariable(vm, "clazz", vm.createReferenceType(Object::class.java), null)) + } + } + + fun testSmartCastDoesNotAffectDebugging2() { + val text = """ + fun useNullable(clazz: Any?) { + if (clazz == null/*FALSE*/) /*unreachable_start*/return/*unreachable_end*/ + with(clazz) { + if (this !is String) return + println(length) + } + } + + fun main() { + useNullable("foo") + } + """.trimIndent() + doTest(text) { vm, frame -> + frame.addVariable("clazz", MockValue.createValue("foo", String::class.java, vm)) + } + } + private fun doTest(text: String, mockValues: BiConsumer) { doTest(text, mockValues, "Test.kt") }