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:
@@ -38,7 +38,7 @@ fun calcProperTypeForIeee754ArithmeticIfNeeded(
|
|||||||
private fun KotlinType.isFloatingPointOrNullable() =
|
private fun KotlinType.isFloatingPointOrNullable() =
|
||||||
KotlinBuiltIns.isDoubleOrNullableDouble(this) || KotlinBuiltIns.isFloatOrNullableFloat(this)
|
KotlinBuiltIns.isDoubleOrNullableDouble(this) || KotlinBuiltIns.isFloatOrNullableFloat(this)
|
||||||
|
|
||||||
private fun KtExpression.getKotlinTypeForComparison(bindingContext: BindingContext): KotlinType? =
|
fun KtExpression.getKotlinTypeForComparison(bindingContext: BindingContext): KotlinType? =
|
||||||
when {
|
when {
|
||||||
this is KtProperty -> bindingContext[BindingContext.VARIABLE, this]?.type
|
this is KtProperty -> bindingContext[BindingContext.VARIABLE, this]?.type
|
||||||
else -> kotlinType(bindingContext)
|
else -> kotlinType(bindingContext)
|
||||||
|
|||||||
@@ -986,6 +986,10 @@ public abstract class KotlinBuiltIns {
|
|||||||
return isConstructedFromGivenClassAndNotNullable(type, FQ_NAMES.number);
|
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) {
|
public static boolean isChar(@NotNull KotlinType type) {
|
||||||
return isConstructedFromGivenClassAndNotNullable(type, FQ_NAMES._char);
|
return isConstructedFromGivenClassAndNotNullable(type, FQ_NAMES._char);
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-4
@@ -21,26 +21,36 @@ import com.intellij.openapi.editor.Editor
|
|||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.psi.tree.IElementType
|
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.analyze
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
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.inspections.AbstractApplicabilityBasedInspection
|
||||||
import org.jetbrains.kotlin.idea.intentions.*
|
import org.jetbrains.kotlin.idea.intentions.*
|
||||||
import org.jetbrains.kotlin.idea.intentions.conventionNameCalls.isAnyEquals
|
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.KtSingleValueToken
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getLastParentOfTypeInRow
|
import org.jetbrains.kotlin.psi.psiUtil.getLastParentOfTypeInRow
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
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.getFirstArgumentExpression
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getReceiverExpression
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getReceiverExpression
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
|
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.isReallySuccess
|
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.resolve.lazy.BodyResolveMode
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
import org.jetbrains.kotlin.types.expressions.OperatorConventions
|
import org.jetbrains.kotlin.types.expressions.OperatorConventions
|
||||||
import org.jetbrains.kotlin.types.typeUtil.*
|
import org.jetbrains.kotlin.types.typeUtil.*
|
||||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||||
|
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
|
||||||
|
|
||||||
class ReplaceCallWithBinaryOperatorInspection : AbstractApplicabilityBasedInspection<KtDotQualifiedExpression>(
|
class ReplaceCallWithBinaryOperatorInspection : AbstractApplicabilityBasedInspection<KtDotQualifiedExpression>(
|
||||||
KtDotQualifiedExpression::class.java
|
KtDotQualifiedExpression::class.java
|
||||||
@@ -162,9 +172,44 @@ class ReplaceCallWithBinaryOperatorInspection : AbstractApplicabilityBasedInspec
|
|||||||
private fun KtDotQualifiedExpression.isFloatingPointNumberEquals(): Boolean {
|
private fun KtDotQualifiedExpression.isFloatingPointNumberEquals(): Boolean {
|
||||||
val resolvedCall = resolveToCall() ?: return false
|
val resolvedCall = resolveToCall() ?: return false
|
||||||
val context = analyze(BodyResolveMode.PARTIAL)
|
val context = analyze(BodyResolveMode.PARTIAL)
|
||||||
val receiverType = resolvedCall.getReceiverExpression().kotlinType(context) ?: return false
|
val calcDescriptor = {
|
||||||
val argumentType = resolvedCall.getFirstArgumentExpression().kotlinType(context) ?: return false
|
this.containingDeclarationForPseudocode?.resolveToDescriptorIfAny()
|
||||||
return (receiverType.isFpType() && argumentType.isNumericType()) || (argumentType.isFpType() && receiverType.isNumericType())
|
}
|
||||||
|
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 {
|
private fun KtSimpleNameExpression.isFloatingPointNumberEquals(): Boolean {
|
||||||
|
|||||||
+4
@@ -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)
|
||||||
+4
@@ -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
|
||||||
+5
@@ -1531,6 +1531,11 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
|
|||||||
runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsDoubleSmartCast.kt");
|
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")
|
@TestMetadata("equalsExtensionFunction.kt")
|
||||||
public void testEqualsExtensionFunction() throws Exception {
|
public void testEqualsExtensionFunction() throws Exception {
|
||||||
runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsExtensionFunction.kt");
|
runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsExtensionFunction.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user