[kotlin-dfa] KTIJ-21930 Dataflow assistant: suppress hints when they are suppressed for original dataflow

GitOrigin-RevId: 0eea5ecaaaa329ab9c80b70bfea245ebc97f0fc1
This commit is contained in:
Tagir Valeev
2022-06-06 15:24:41 +00:00
committed by intellij-monorepo-bot
parent 3a45b11030
commit b7c6103f12
3 changed files with 296 additions and 278 deletions
@@ -13,6 +13,7 @@ import com.intellij.codeInspection.dataFlow.lang.UnsatisfiedConditionProblem
import com.intellij.codeInspection.dataFlow.lang.ir.DataFlowIRProvider
import com.intellij.codeInspection.dataFlow.lang.ir.DfaInstructionState
import com.intellij.codeInspection.dataFlow.memory.DfaMemoryState
import com.intellij.codeInspection.dataFlow.types.DfType
import com.intellij.codeInspection.dataFlow.types.DfTypes
import com.intellij.codeInspection.dataFlow.value.DfaValue
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory
@@ -86,260 +87,6 @@ class KotlinConstantConditionsInspection : AbstractKotlinInspection() {
}
}
private fun shouldSuppress(value: ConstantValue, expression: KtExpression): Boolean {
// TODO: do something with always false branches in exhaustive when statements
// TODO: return x && y.let {return...}
var parent = expression.parent
if (parent is KtDotQualifiedExpression && parent.selectorExpression == expression) {
// Will be reported for parent qualified expression
return true
}
while (parent is KtParenthesizedExpression) {
parent = parent.parent
}
if (expression is KtConstantExpression ||
// If result of initialization is constant, then the initializer will be reported
expression is KtProperty ||
// If result of assignment is constant, then the right-hand part will be reported
expression is KtBinaryExpression && expression.operationToken == KtTokens.EQ ||
// Negation operand: negation itself will be reported
(parent as? KtPrefixExpression)?.operationToken == KtTokens.EXCL
) {
return true
}
if (expression is KtBinaryExpression && expression.operationToken == KtTokens.ELVIS) {
// Left part of Elvis is Nothing?, so the right part is always executed
// Could be caused by code like return x?.let { return ... } ?: true
// While inner "return" is redundant, the "always true" warning is confusing
// probably separate inspection could report extra "return"
if (expression.left?.getKotlinType()?.isNullableNothing() == true) {
return true
}
}
if (isAlsoChain(expression) || isLetConstant(expression) || isUpdateChain(expression)) return true
when (value) {
ConstantValue.TRUE -> {
if (isSmartCastNecessary(expression, true)) return true
if (isPairingConditionInWhen(expression)) return true
if (isAssertion(parent, true)) return true
}
ConstantValue.FALSE -> {
if (isSmartCastNecessary(expression, false)) return true
if (isAssertion(parent, false)) return true
}
ConstantValue.ZERO -> {
if (expression.readWriteAccess(false).isWrite) {
// like if (x == 0) x++, warning would be somewhat annoying
return true
}
if (expression is KtDotQualifiedExpression && expression.selectorExpression?.textMatches("ordinal") == true) {
var receiver: KtExpression? = expression.receiverExpression
if (receiver is KtQualifiedExpression) {
receiver = receiver.selectorExpression
}
if (receiver is KtSimpleNameExpression && receiver.mainReference.resolve() is KtEnumEntry) {
// ordinal() call on explicit enum constant
return true
}
}
val bindingContext = expression.analyze()
if (ConstantExpressionEvaluator.getConstant(expression, bindingContext) != null) return true
if (expression is KtSimpleNameExpression &&
(parent is KtValueArgument || parent is KtContainerNode && parent.parent is KtArrayAccessExpression)
) {
// zero value is passed as argument to another method or used for array access. Often, such a warning is annoying
return true
}
}
ConstantValue.NULL -> {
if (parent is KtProperty && parent.typeReference == null && expression is KtSimpleNameExpression) {
// initialize other variable with null to copy type, like
// var x1 : X = null
// var x2 = x1 -- let's suppress this
return true
}
if (expression is KtBinaryExpressionWithTypeRHS && expression.left.isNull()) {
// like (null as? X)
return true
}
if (parent is KtBinaryExpression) {
val token = parent.operationToken
if ((token === KtTokens.EQEQ || token === KtTokens.EXCLEQ || token === KtTokens.EQEQEQ || token === KtTokens.EXCLEQEQEQ) &&
(parent.left?.isNull() == true || parent.right?.isNull() == true)
) {
// like if (x == null) when 'x' is known to be null: report 'always true' instead
return true
}
}
val kotlinType = expression.getKotlinType()
if (kotlinType.toDfType(expression) == DfTypes.NULL) {
// According to type system, nothing but null could be stored in such an expression (likely "Void?" type)
return true
}
}
else -> {}
}
if (expression is KtSimpleNameExpression) {
val target = expression.mainReference.resolve()
if (target is KtProperty && !target.isVar && target.initializer is KtConstantExpression) {
// suppress warnings uses of boolean constant like 'val b = true'
return true
}
}
if (isCompilationWarning(expression)) {
return true
}
return expression.isUsedAsStatement(expression.analyze(BodyResolveMode.FULL))
}
private fun isUpdateChain(expression: KtExpression): Boolean {
// x = x or ..., etc.
if (expression !is KtSimpleNameExpression) return false
val binOp = expression.parent as? KtBinaryExpression ?: return false
val op = binOp.operationReference.text
if (op != "or" && op != "and" && op != "xor" && op != "||" && op != "&&") return false
val assignment = binOp.parent as? KtBinaryExpression ?: return false
if (assignment.operationToken != KtTokens.EQ) return false
val left = assignment.left
if (left !is KtSimpleNameExpression || !left.textMatches(expression.text)) return false
val variable = expression.mainReference.resolve() as? KtProperty ?: return false
val varParent = variable.parent as? KtBlockExpression ?: return false
var context: PsiElement = assignment
var block = context.parent
while (block is KtContainerNode ||
block is KtBlockExpression && block.statements.first() == context ||
block is KtIfExpression && block.then?.parent == context && block.`else` == null && !hasWritesTo(block.condition, variable)) {
context = block
block = context.parent
}
if (block !== varParent) return false
var curExpression = variable.nextSibling
while (curExpression != context) {
if (hasWritesTo(curExpression, variable)) return false
curExpression = curExpression.nextSibling
}
return true
}
private fun hasWritesTo(block: PsiElement?, variable: KtProperty): Boolean {
return !PsiTreeUtil.processElements(block, KtSimpleNameExpression::class.java) { ref ->
val write = ref.mainReference.isReferenceTo(variable) && ref.readWriteAccess(false).isWrite
!write
}
}
// Do not report on also, as it always returns the qualifier. If necessary, qualifier itself will be reported
private fun isAlsoChain(expr: KtExpression): Boolean {
val call = (expr as? KtQualifiedExpression)?.selectorExpression as? KtCallExpression ?: return false
return isCallToMethod(call, "kotlin", "also")
}
// Do not report x.let { true } or x.let { false } as it's pretty evident
private fun isLetConstant(expr: KtExpression): Boolean {
val call = (expr as? KtQualifiedExpression)?.selectorExpression as? KtCallExpression ?: return false
if (!isCallToMethod(call, "kotlin", "let")) return false
val lambda = call.lambdaArguments.singleOrNull()?.getLambdaExpression() ?: return false
return lambda.bodyExpression?.statements?.singleOrNull() is KtConstantExpression
}
private fun isCallToMethod(call: KtCallExpression, packageName: String, methodName: String): Boolean {
val descriptor = call.resolveToCall()?.resultingDescriptor ?: return false
if (descriptor.name.asString() != methodName) return false
val packageFragment = descriptor.containingDeclaration as? PackageFragmentDescriptor ?: return false
return packageFragment.fqName.asString() == packageName
}
private fun isAssertion(parent: PsiElement?, value: Boolean): Boolean {
return when (parent) {
is KtBinaryExpression ->
(parent.operationToken == KtTokens.ANDAND || parent.operationToken == KtTokens.OROR) && isAssertion(parent.parent, value)
is KtParenthesizedExpression ->
isAssertion(parent.parent, value)
is KtPrefixExpression ->
parent.operationToken == KtTokens.EXCL && isAssertion(parent.parent, !value)
is KtValueArgument -> {
if (!value) return false
val valueArgList = parent.parent as? KtValueArgumentList ?: return false
val call = valueArgList.parent as? KtCallExpression ?: return false
val descriptor = call.resolveToCall()?.resultingDescriptor ?: return false
val name = descriptor.name.asString()
if (name != "assert" && name != "require" && name != "check") return false
val pkg = descriptor.containingDeclaration as? PackageFragmentDescriptor ?: return false
return pkg.fqName.asString() == "kotlin"
}
else -> false
}
}
/**
* Returns true if expression is part of when condition expression that looks like
* ```
* when {
* a && b -> ...
* a && !b -> ...
* }
* ```
* In this case, !b could be reported as 'always true' but such warnings are annoying
*/
private fun isPairingConditionInWhen(expression: KtExpression): Boolean {
val parent = expression.parent
if (parent is KtBinaryExpression && parent.operationToken == KtTokens.ANDAND) {
var topAnd: KtBinaryExpression = parent
while (true) {
val nextParent = topAnd.parent
if (nextParent is KtBinaryExpression && nextParent.operationToken == KtTokens.ANDAND) {
topAnd = nextParent
} else break
}
val topAndParent = topAnd.parent
if (topAndParent is KtWhenConditionWithExpression) {
val whenExpression = (topAndParent.parent as? KtWhenEntry)?.parent as? KtWhenExpression
if (whenExpression != null && hasOppositeCondition(whenExpression, topAnd, expression)) {
return true
}
}
}
return false
}
private fun hasOppositeCondition(whenExpression: KtWhenExpression, topAnd: KtBinaryExpression, expression: KtExpression): Boolean {
for (entry in whenExpression.entries) {
for (condition in entry.conditions) {
if (condition is KtWhenConditionWithExpression) {
val candidate = condition.expression
if (candidate === topAnd) return false
if (isOppositeCondition(candidate, topAnd, expression)) return true
}
}
}
return false
}
private tailrec fun isOppositeCondition(candidate: KtExpression?, template: KtBinaryExpression, expression: KtExpression): Boolean {
if (candidate !is KtBinaryExpression || candidate.operationToken !== KtTokens.ANDAND) return false
val left = candidate.left
val right = candidate.right
if (left == null || right == null) return false
val templateLeft = template.left
val templateRight = template.right
if (templateLeft == null || templateRight == null) return false
if (templateRight === expression) {
return areEquivalent(left, templateLeft) && areEquivalent(right.negate(false), templateRight)
}
if (!areEquivalent(right, templateRight)) return false
if (templateLeft === expression) {
return areEquivalent(left.negate(false), templateLeft)
}
if (templateLeft !is KtBinaryExpression || templateLeft.operationToken !== KtTokens.ANDAND) return false
return isOppositeCondition(left, templateLeft, expression)
}
private fun areEquivalent(e1: KtElement, e2: KtElement): Boolean {
return PsiEquivalenceUtil.areElementsEquivalent(e1, e2,
{ref1, ref2 -> ref1.element.text.compareTo(ref2.element.text)},
null, null, false)
}
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
// Non-JVM is not supported now
if (holder.file.module?.platform?.isJvm() != true) return PsiElementVisitor.EMPTY_VISITOR
@@ -501,28 +248,6 @@ class KotlinConstantConditionsInspection : AbstractKotlinInspection() {
return false
}
private fun isCompilationWarning(anchor: KtElement): Boolean
{
val context = anchor.analyze(BodyResolveMode.FULL)
if (context.diagnostics.forElement(anchor).any
{ it.factory == Errors.CAST_NEVER_SUCCEEDS
|| it.factory == Errors.SENSELESS_COMPARISON
|| it.factory == Errors.SENSELESS_NULL_IN_WHEN
|| it.factory == Errors.USELESS_IS_CHECK
|| it.factory == Errors.DUPLICATE_LABEL_IN_WHEN }
) {
return true
}
val rootElement = anchor.containingFile
val suppressionCache = KotlinCacheService.getInstance(anchor.project).getSuppressionCache()
return suppressionCache.isSuppressed(anchor, rootElement, "CAST_NEVER_SUCCEEDS", Severity.WARNING) ||
suppressionCache.isSuppressed(anchor, rootElement, "SENSELESS_COMPARISON", Severity.WARNING) ||
suppressionCache.isSuppressed(anchor, rootElement, "SENSELESS_NULL_IN_WHEN", Severity.WARNING) ||
suppressionCache.isSuppressed(anchor, rootElement, "USELESS_IS_CHECK", Severity.WARNING) ||
suppressionCache.isSuppressed(anchor, rootElement, "DUPLICATE_LABEL_IN_WHEN", Severity.WARNING)
}
private fun shouldSuppressWhenCondition(
cv: ConstantValue,
condition: KtWhenCondition
@@ -547,4 +272,294 @@ class KotlinConstantConditionsInspection : AbstractKotlinInspection() {
}
return false
}
companion object {
private fun areEquivalent(e1: KtElement, e2: KtElement): Boolean {
return PsiEquivalenceUtil.areElementsEquivalent(e1, e2,
{ref1, ref2 -> ref1.element.text.compareTo(ref2.element.text)},
null, null, false)
}
private tailrec fun isOppositeCondition(candidate: KtExpression?, template: KtBinaryExpression, expression: KtExpression): Boolean {
if (candidate !is KtBinaryExpression || candidate.operationToken !== KtTokens.ANDAND) return false
val left = candidate.left
val right = candidate.right
if (left == null || right == null) return false
val templateLeft = template.left
val templateRight = template.right
if (templateLeft == null || templateRight == null) return false
if (templateRight === expression) {
return areEquivalent(left, templateLeft) && areEquivalent(right.negate(false), templateRight)
}
if (!areEquivalent(right, templateRight)) return false
if (templateLeft === expression) {
return areEquivalent(left.negate(false), templateLeft)
}
if (templateLeft !is KtBinaryExpression || templateLeft.operationToken !== KtTokens.ANDAND) return false
return isOppositeCondition(left, templateLeft, expression)
}
private fun hasOppositeCondition(whenExpression: KtWhenExpression, topAnd: KtBinaryExpression, expression: KtExpression): Boolean {
for (entry in whenExpression.entries) {
for (condition in entry.conditions) {
if (condition is KtWhenConditionWithExpression) {
val candidate = condition.expression
if (candidate === topAnd) return false
if (isOppositeCondition(candidate, topAnd, expression)) return true
}
}
}
return false
}
/**
* Returns true if expression is part of when condition expression that looks like
* ```
* when {
* a && b -> ...
* a && !b -> ...
* }
* ```
* In this case, !b could be reported as 'always true' but such warnings are annoying
*/
private fun isPairingConditionInWhen(expression: KtExpression): Boolean {
val parent = expression.parent
if (parent is KtBinaryExpression && parent.operationToken == KtTokens.ANDAND) {
var topAnd: KtBinaryExpression = parent
while (true) {
val nextParent = topAnd.parent
if (nextParent is KtBinaryExpression && nextParent.operationToken == KtTokens.ANDAND) {
topAnd = nextParent
} else break
}
val topAndParent = topAnd.parent
if (topAndParent is KtWhenConditionWithExpression) {
val whenExpression = (topAndParent.parent as? KtWhenEntry)?.parent as? KtWhenExpression
if (whenExpression != null && hasOppositeCondition(whenExpression, topAnd, expression)) {
return true
}
}
}
return false
}
private fun isCompilationWarning(anchor: KtElement): Boolean
{
val context = anchor.analyze(BodyResolveMode.FULL)
if (context.diagnostics.forElement(anchor).any
{ it.factory == Errors.CAST_NEVER_SUCCEEDS
|| it.factory == Errors.SENSELESS_COMPARISON
|| it.factory == Errors.SENSELESS_NULL_IN_WHEN
|| it.factory == Errors.USELESS_IS_CHECK
|| it.factory == Errors.DUPLICATE_LABEL_IN_WHEN }
) {
return true
}
val rootElement = anchor.containingFile
val suppressionCache = KotlinCacheService.getInstance(anchor.project).getSuppressionCache()
return suppressionCache.isSuppressed(anchor, rootElement, "CAST_NEVER_SUCCEEDS", Severity.WARNING) ||
suppressionCache.isSuppressed(anchor, rootElement, "SENSELESS_COMPARISON", Severity.WARNING) ||
suppressionCache.isSuppressed(anchor, rootElement, "SENSELESS_NULL_IN_WHEN", Severity.WARNING) ||
suppressionCache.isSuppressed(anchor, rootElement, "USELESS_IS_CHECK", Severity.WARNING) ||
suppressionCache.isSuppressed(anchor, rootElement, "DUPLICATE_LABEL_IN_WHEN", Severity.WARNING)
}
private fun isCallToMethod(call: KtCallExpression, packageName: String, methodName: String): Boolean {
val descriptor = call.resolveToCall()?.resultingDescriptor ?: return false
if (descriptor.name.asString() != methodName) return false
val packageFragment = descriptor.containingDeclaration as? PackageFragmentDescriptor ?: return false
return packageFragment.fqName.asString() == packageName
}
// Do not report x.let { true } or x.let { false } as it's pretty evident
private fun isLetConstant(expr: KtExpression): Boolean {
val call = (expr as? KtQualifiedExpression)?.selectorExpression as? KtCallExpression ?: return false
if (!isCallToMethod(call, "kotlin", "let")) return false
val lambda = call.lambdaArguments.singleOrNull()?.getLambdaExpression() ?: return false
return lambda.bodyExpression?.statements?.singleOrNull() is KtConstantExpression
}
// Do not report on also, as it always returns the qualifier. If necessary, qualifier itself will be reported
private fun isAlsoChain(expr: KtExpression): Boolean {
val call = (expr as? KtQualifiedExpression)?.selectorExpression as? KtCallExpression ?: return false
return isCallToMethod(call, "kotlin", "also")
}
private fun isAssertion(parent: PsiElement?, value: Boolean): Boolean {
return when (parent) {
is KtBinaryExpression ->
(parent.operationToken == KtTokens.ANDAND || parent.operationToken == KtTokens.OROR) && isAssertion(parent.parent, value)
is KtParenthesizedExpression ->
isAssertion(parent.parent, value)
is KtPrefixExpression ->
parent.operationToken == KtTokens.EXCL && isAssertion(parent.parent, !value)
is KtValueArgument -> {
if (!value) return false
val valueArgList = parent.parent as? KtValueArgumentList ?: return false
val call = valueArgList.parent as? KtCallExpression ?: return false
val descriptor = call.resolveToCall()?.resultingDescriptor ?: return false
val name = descriptor.name.asString()
if (name != "assert" && name != "require" && name != "check") return false
val pkg = descriptor.containingDeclaration as? PackageFragmentDescriptor ?: return false
return pkg.fqName.asString() == "kotlin"
}
else -> false
}
}
private fun hasWritesTo(block: PsiElement?, variable: KtProperty): Boolean {
return !PsiTreeUtil.processElements(block, KtSimpleNameExpression::class.java) { ref ->
val write = ref.mainReference.isReferenceTo(variable) && ref.readWriteAccess(false).isWrite
!write
}
}
private fun isUpdateChain(expression: KtExpression): Boolean {
// x = x or ..., etc.
if (expression !is KtSimpleNameExpression) return false
val binOp = expression.parent as? KtBinaryExpression ?: return false
val op = binOp.operationReference.text
if (op != "or" && op != "and" && op != "xor" && op != "||" && op != "&&") return false
val assignment = binOp.parent as? KtBinaryExpression ?: return false
if (assignment.operationToken != KtTokens.EQ) return false
val left = assignment.left
if (left !is KtSimpleNameExpression || !left.textMatches(expression.text)) return false
val variable = expression.mainReference.resolve() as? KtProperty ?: return false
val varParent = variable.parent as? KtBlockExpression ?: return false
var context: PsiElement = assignment
var block = context.parent
while (block is KtContainerNode ||
block is KtBlockExpression && block.statements.first() == context ||
block is KtIfExpression && block.then?.parent == context && block.`else` == null && !hasWritesTo(block.condition, variable)
) {
context = block
block = context.parent
}
if (block !== varParent) return false
var curExpression = variable.nextSibling
while (curExpression != context) {
if (hasWritesTo(curExpression, variable)) return false
curExpression = curExpression.nextSibling
}
return true
}
fun shouldSuppress(value: DfType, expression: KtExpression): Boolean {
val constant = when(value) {
DfTypes.NULL -> ConstantValue.NULL
DfTypes.TRUE -> ConstantValue.TRUE
DfTypes.FALSE -> ConstantValue.FALSE
DfTypes.intValue(0), DfTypes.longValue(0) -> ConstantValue.ZERO
else -> ConstantValue.UNKNOWN
}
return shouldSuppress(constant, expression)
}
private fun shouldSuppress(value: ConstantValue, expression: KtExpression): Boolean {
// TODO: do something with always false branches in exhaustive when statements
// TODO: return x && y.let {return...}
var parent = expression.parent
if (parent is KtDotQualifiedExpression && parent.selectorExpression == expression) {
// Will be reported for parent qualified expression
return true
}
while (parent is KtParenthesizedExpression) {
parent = parent.parent
}
if (expression is KtConstantExpression ||
// If result of initialization is constant, then the initializer will be reported
expression is KtProperty ||
// If result of assignment is constant, then the right-hand part will be reported
expression is KtBinaryExpression && expression.operationToken == KtTokens.EQ ||
// Negation operand: negation itself will be reported
(parent as? KtPrefixExpression)?.operationToken == KtTokens.EXCL
) {
return true
}
if (expression is KtBinaryExpression && expression.operationToken == KtTokens.ELVIS) {
// Left part of Elvis is Nothing?, so the right part is always executed
// Could be caused by code like return x?.let { return ... } ?: true
// While inner "return" is redundant, the "always true" warning is confusing
// probably separate inspection could report extra "return"
if (expression.left?.getKotlinType()?.isNullableNothing() == true) {
return true
}
}
if (isAlsoChain(expression) || isLetConstant(expression) || isUpdateChain(expression)) return true
when (value) {
ConstantValue.TRUE -> {
if (isSmartCastNecessary(expression, true)) return true
if (isPairingConditionInWhen(expression)) return true
if (isAssertion(parent, true)) return true
}
ConstantValue.FALSE -> {
if (isSmartCastNecessary(expression, false)) return true
if (isAssertion(parent, false)) return true
}
ConstantValue.ZERO -> {
if (expression.readWriteAccess(false).isWrite) {
// like if (x == 0) x++, warning would be somewhat annoying
return true
}
if (expression is KtDotQualifiedExpression && expression.selectorExpression?.textMatches("ordinal") == true) {
var receiver: KtExpression? = expression.receiverExpression
if (receiver is KtQualifiedExpression) {
receiver = receiver.selectorExpression
}
if (receiver is KtSimpleNameExpression && receiver.mainReference.resolve() is KtEnumEntry) {
// ordinal() call on explicit enum constant
return true
}
}
val bindingContext = expression.analyze()
if (ConstantExpressionEvaluator.getConstant(expression, bindingContext) != null) return true
if (expression is KtSimpleNameExpression &&
(parent is KtValueArgument || parent is KtContainerNode && parent.parent is KtArrayAccessExpression)
) {
// zero value is passed as argument to another method or used for array access. Often, such a warning is annoying
return true
}
}
ConstantValue.NULL -> {
if (parent is KtProperty && parent.typeReference == null && expression is KtSimpleNameExpression) {
// initialize other variable with null to copy type, like
// var x1 : X = null
// var x2 = x1 -- let's suppress this
return true
}
if (expression is KtBinaryExpressionWithTypeRHS && expression.left.isNull()) {
// like (null as? X)
return true
}
if (parent is KtBinaryExpression) {
val token = parent.operationToken
if ((token === KtTokens.EQEQ || token === KtTokens.EXCLEQ || token === KtTokens.EQEQEQ || token === KtTokens.EXCLEQEQEQ) &&
(parent.left?.isNull() == true || parent.right?.isNull() == true)
) {
// like if (x == null) when 'x' is known to be null: report 'always true' instead
return true
}
}
val kotlinType = expression.getKotlinType()
if (kotlinType.toDfType(expression) == DfTypes.NULL) {
// According to type system, nothing but null could be stored in such an expression (likely "Void?" type)
return true
}
}
else -> {}
}
if (expression is KtSimpleNameExpression) {
val target = expression.mainReference.resolve()
if (target is KtProperty && !target.isVar && target.initializer is KtConstantExpression) {
// suppress warnings uses of boolean constant like 'val b = true'
return true
}
}
if (isCompilationWarning(expression)) {
return true
}
return expression.isUsedAsStatement(expression.analyze(BodyResolveMode.FULL))
}
}
}
@@ -123,16 +123,18 @@ class KotlinDfaAssistProvider : DfaAssistProvider {
val hints = hashMapOf<PsiElement, DfaHint>()
override fun beforePush(args: Array<out DfaValue>, value: DfaValue, anchor: DfaAnchor, state: DfaMemoryState) {
val dfType = state.getDfType(value)
var psi = when (anchor) {
is KotlinAnchor.KotlinExpressionAnchor -> {
if (shouldTrackExpressionValue(anchor.expression)) anchor.expression
if (shouldTrackExpressionValue(anchor.expression) &&
!KotlinConstantConditionsInspection.shouldSuppress(dfType, anchor.expression)
) anchor.expression
else return
}
is KotlinAnchor.KotlinWhenConditionAnchor -> anchor.condition
else -> return
}
var hint = DfaHint.ANY_VALUE
val dfType = state.getDfType(value)
if (dfType === DfTypes.TRUE) {
hint = DfaHint.TRUE
} else if (dfType === DfTypes.FALSE) {
@@ -42,6 +42,7 @@ class KotlinDfaAssistTest : DfaAssistTest() {
if (y || x/*TRUE*/) {}
var z: Boolean
z = x/*TRUE*/
var b = true
}""") { vm, frame -> frame.addVariable("x", MockBooleanValue(vm, true)) }
}