Replace equals with == involving FP types: handle possible smart casts

This fixes two relevant tests with smart casts
#KT-25933 Fixed
This commit is contained in:
Mikhail Glukhikh
2018-10-09 14:47:01 +03:00
parent 04cadf3f58
commit 6be65e7d1a
6 changed files with 67 additions and 5 deletions
@@ -38,7 +38,7 @@ fun calcProperTypeForIeee754ArithmeticIfNeeded(
private fun KotlinType.isFloatingPointOrNullable() =
KotlinBuiltIns.isDoubleOrNullableDouble(this) || KotlinBuiltIns.isFloatOrNullableFloat(this)
private fun KtExpression.getKotlinTypeForComparison(bindingContext: BindingContext): KotlinType? =
fun KtExpression.getKotlinTypeForComparison(bindingContext: BindingContext): KotlinType? =
when {
this is KtProperty -> bindingContext[BindingContext.VARIABLE, this]?.type
else -> kotlinType(bindingContext)
@@ -986,6 +986,10 @@ public abstract class KotlinBuiltIns {
return isConstructedFromGivenClassAndNotNullable(type, FQ_NAMES.number);
}
public static boolean isNumberOrNullableNumber(@NotNull KotlinType type) {
return isConstructedFromGivenClass(type, FQ_NAMES.number);
}
public static boolean isChar(@NotNull KotlinType type) {
return isConstructedFromGivenClassAndNotNullable(type, FQ_NAMES._char);
}
@@ -21,26 +21,36 @@ import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.tree.IElementType
import org.jetbrains.kotlin.codegen.kotlinType
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.cfg.pseudocode.containingDeclarationForPseudocode
import org.jetbrains.kotlin.codegen.getKotlinTypeForComparison
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.inspections.AbstractApplicabilityBasedInspection
import org.jetbrains.kotlin.idea.intentions.*
import org.jetbrains.kotlin.idea.intentions.conventionNameCalls.isAnyEquals
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.lexer.KtSingleValueToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getLastParentOfTypeInRow
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfoBefore
import org.jetbrains.kotlin.resolve.calls.callUtil.getFirstArgumentExpression
import org.jetbrains.kotlin.resolve.calls.callUtil.getReceiverExpression
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
import org.jetbrains.kotlin.resolve.calls.model.isReallySuccess
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.expressions.OperatorConventions
import org.jetbrains.kotlin.types.typeUtil.*
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
class ReplaceCallWithBinaryOperatorInspection : AbstractApplicabilityBasedInspection<KtDotQualifiedExpression>(
KtDotQualifiedExpression::class.java
@@ -162,9 +172,44 @@ class ReplaceCallWithBinaryOperatorInspection : AbstractApplicabilityBasedInspec
private fun KtDotQualifiedExpression.isFloatingPointNumberEquals(): Boolean {
val resolvedCall = resolveToCall() ?: return false
val context = analyze(BodyResolveMode.PARTIAL)
val receiverType = resolvedCall.getReceiverExpression().kotlinType(context) ?: return false
val argumentType = resolvedCall.getFirstArgumentExpression().kotlinType(context) ?: return false
return (receiverType.isFpType() && argumentType.isNumericType()) || (argumentType.isFpType() && receiverType.isNumericType())
val calcDescriptor = {
this.containingDeclarationForPseudocode?.resolveToDescriptorIfAny()
}
val receiverType = resolvedCall.getReceiverExpression()?.getKotlinTypeWithPossibleSmartCastToFP(
context, calcDescriptor
) ?: return false
val argumentType = resolvedCall.getFirstArgumentExpression()?.getKotlinTypeWithPossibleSmartCastToFP(
context, calcDescriptor
) ?: return false
return receiverType.isFpType() && argumentType.isNumericType() ||
argumentType.isFpType() && receiverType.isNumericType()
}
private fun KtExpression.getKotlinTypeWithPossibleSmartCastToFP(
context: BindingContext,
calcDescriptor: () -> DeclarationDescriptor?
): KotlinType? {
val givenType = getKotlinTypeForComparison(context) ?: return null
if (KotlinBuiltIns.isDoubleOrNullableDouble(givenType) || KotlinBuiltIns.isFloatOrNullableFloat(givenType)) {
return givenType
}
val dataFlowValueFactory = getResolutionFacade().getFrontendService(DataFlowValueFactory::class.java)
val dataFlow = dataFlowValueFactory.createDataFlowValue(
this,
givenType,
context,
calcDescriptor() ?: return givenType
)
val stableTypes = context.getDataFlowInfoBefore(this).getStableTypes(dataFlow, languageVersionSettings)
return stableTypes.firstNotNullResult {
when {
KotlinBuiltIns.isDoubleOrNullableDouble(it) -> it
KotlinBuiltIns.isFloatOrNullableFloat(it) -> it
else -> null
}
} ?: givenType
}
private fun KtSimpleNameExpression.isFloatingPointNumberEquals(): Boolean {
@@ -0,0 +1,4 @@
// FIX: Replace total order equality with IEEE 754 equality
fun <T> test(a: T, b: T) =
a is Double && b is Double && a.<caret>equals(b)
@@ -0,0 +1,4 @@
// FIX: Replace total order equality with IEEE 754 equality
fun <T> test(a: T, b: T) =
a is Double && b is Double && a == b
@@ -1531,6 +1531,11 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsDoubleSmartCast.kt");
}
@TestMetadata("equalsDoubleSmartCastFromGeneric.kt")
public void testEqualsDoubleSmartCastFromGeneric() throws Exception {
runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsDoubleSmartCastFromGeneric.kt");
}
@TestMetadata("equalsExtensionFunction.kt")
public void testEqualsExtensionFunction() throws Exception {
runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsExtensionFunction.kt");