[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
This commit is contained in:
Tagir Valeev
2024-10-17 16:15:24 +00:00
committed by intellij-monorepo-bot
parent 44ac817eb2
commit 0914c4dc26
3 changed files with 45 additions and 12 deletions
@@ -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()) {
@@ -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
}
@@ -579,6 +579,44 @@ class K2DfaAssistTest : DfaAssistTest(), ExpectedPluginModeProvider {
doTest(text) { _, _ -> }
}
fun testSmartCastDoesNotAffectDebugging() {
val text = """
fun useNullable(clazz: Any?) {
<caret>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?) {
<caret>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<MockVirtualMachine, MockStackFrame>) {
doTest(text, mockValues, "Test.kt")
}