diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/conventionNameCalls/ReplaceCallWithBinaryOperatorInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/conventionNameCalls/ReplaceCallWithBinaryOperatorInspection.kt index faf127c96ad..4e5eda16b39 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/conventionNameCalls/ReplaceCallWithBinaryOperatorInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/conventionNameCalls/ReplaceCallWithBinaryOperatorInspection.kt @@ -21,20 +21,25 @@ 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.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall import org.jetbrains.kotlin.idea.inspections.AbstractApplicabilityBasedInspection -import org.jetbrains.kotlin.idea.intentions.callExpression +import org.jetbrains.kotlin.idea.intentions.* import org.jetbrains.kotlin.idea.intentions.conventionNameCalls.isAnyEquals -import org.jetbrains.kotlin.idea.intentions.isReceiverExpressionWithValue -import org.jetbrains.kotlin.idea.intentions.toResolvedCall 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.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.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 class ReplaceCallWithBinaryOperatorInspection : AbstractApplicabilityBasedInspection( @@ -69,10 +74,13 @@ class ReplaceCallWithBinaryOperatorInspection : AbstractApplicabilityBasedInspec override fun inspectionHighlightType(element: KtDotQualifiedExpression): ProblemHighlightType { val calleeExpression = element.callExpression?.calleeExpression as? KtSimpleNameExpression val identifier = calleeExpression?.getReferencedNameAsName() - return if (identifier == OperatorNameConventions.EQUALS || identifier == OperatorNameConventions.COMPARE_TO) { + val isFloatingPointNumberEquals = calleeExpression?.isFloatingPointNumberEquals() ?: false + + return if (isFloatingPointNumberEquals) { + ProblemHighlightType.INFORMATION + } else if (identifier == OperatorNameConventions.EQUALS || identifier == OperatorNameConventions.COMPARE_TO) { ProblemHighlightType.GENERIC_ERROR_OR_WARNING - } - else { + } else { ProblemHighlightType.INFORMATION } } @@ -84,6 +92,10 @@ class ReplaceCallWithBinaryOperatorInspection : AbstractApplicabilityBasedInspec override fun fixText(element: KtDotQualifiedExpression): String { val calleeExpression = element.callExpression?.calleeExpression as? KtSimpleNameExpression ?: return defaultFixText + if (calleeExpression.isFloatingPointNumberEquals()) { + return "Replace total order equality with IEEE 754 equality" + } + val operation = operation(calleeExpression) ?: return defaultFixText return "Replace with '${operation.value}'" } @@ -123,7 +135,6 @@ class ReplaceCallWithBinaryOperatorInspection : AbstractApplicabilityBasedInspec return when (identifier) { OperatorNameConventions.EQUALS -> { if (!dotQualified.isAnyEquals()) return null - val prefixExpression = dotQualified.getWrappingPrefixExpressionIfAny() if (prefixExpression != null && prefixExpression.operationToken == KtTokens.EXCL) KtTokens.EXCLEQ else KtTokens.EQEQ @@ -148,4 +159,25 @@ class ReplaceCallWithBinaryOperatorInspection : AbstractApplicabilityBasedInspec else -> OperatorConventions.BINARY_OPERATION_NAMES.inverse()[identifier] } } -} + + 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()) + } + + private fun KtSimpleNameExpression.isFloatingPointNumberEquals(): Boolean { + val dotQualified = parent.parent as? KtDotQualifiedExpression ?: return false + return dotQualified.isFloatingPointNumberEquals() + } + + private fun KotlinType.isFpType(): Boolean { + return isFloat() || isDouble() + } + + private fun KotlinType.isNumericType(): Boolean { + return isFpType() || isByte() || isShort() || isInt() || isLong() + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsDoubleSmartCast.kt b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsDoubleSmartCast.kt new file mode 100644 index 00000000000..a723f12101f --- /dev/null +++ b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsDoubleSmartCast.kt @@ -0,0 +1,4 @@ +// FIX: Replace total order equality with IEEE 754 equality + +fun test(a: Any, b: Any) = + a is Double && b is Double && a.equals(b) \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsDoubleSmartCast.kt.after b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsDoubleSmartCast.kt.after new file mode 100644 index 00000000000..1432f7eb740 --- /dev/null +++ b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsDoubleSmartCast.kt.after @@ -0,0 +1,4 @@ +// FIX: Replace total order equality with IEEE 754 equality + +fun test(a: Any, b: Any) = + a is Double && b is Double && a == b \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatSmartCast.kt b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatSmartCast.kt new file mode 100644 index 00000000000..2eec23a5b8e --- /dev/null +++ b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatSmartCast.kt @@ -0,0 +1,4 @@ +// FIX: Replace total order equality with IEEE 754 equality + +fun test(a: Any, b: Any) = + a is Float && b is Float && a.equals(b) \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatSmartCast.kt.after b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatSmartCast.kt.after new file mode 100644 index 00000000000..a0dafe98b4c --- /dev/null +++ b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatSmartCast.kt.after @@ -0,0 +1,4 @@ +// FIX: Replace total order equality with IEEE 754 equality + +fun test(a: Any, b: Any) = + a is Float && b is Float && a == b \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatingPointLiteral1.kt b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatingPointLiteral1.kt new file mode 100644 index 00000000000..95dbe5e0986 --- /dev/null +++ b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatingPointLiteral1.kt @@ -0,0 +1,3 @@ +// FIX: Replace total order equality with IEEE 754 equality + +fun checkPi(x: Double) = x.equals(3.14) \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatingPointLiteral1.kt.after b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatingPointLiteral1.kt.after new file mode 100644 index 00000000000..03cb7fcf364 --- /dev/null +++ b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatingPointLiteral1.kt.after @@ -0,0 +1,3 @@ +// FIX: Replace total order equality with IEEE 754 equality + +fun checkPi(x: Double) = x == 3.14 \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatingPointLiteral2.kt b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatingPointLiteral2.kt new file mode 100644 index 00000000000..8142ff1b362 --- /dev/null +++ b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatingPointLiteral2.kt @@ -0,0 +1,3 @@ +// FIX: Replace total order equality with IEEE 754 equality + +fun checkPi(x: Double) = 3.14.equals(x) \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatingPointLiteral2.kt.after b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatingPointLiteral2.kt.after new file mode 100644 index 00000000000..c28368a84b6 --- /dev/null +++ b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatingPointLiteral2.kt.after @@ -0,0 +1,3 @@ +// FIX: Replace total order equality with IEEE 754 equality + +fun checkPi(x: Double) = 3.14 == x \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatingPointType.kt b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatingPointType.kt new file mode 100644 index 00000000000..7c38c0860fc --- /dev/null +++ b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatingPointType.kt @@ -0,0 +1,3 @@ +// FIX: Replace total order equality with IEEE 754 equality + +fun test(a: Double, b: Double) = a.equals(b) \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatingPointType.kt.after b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatingPointType.kt.after new file mode 100644 index 00000000000..aefd51507d6 --- /dev/null +++ b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatingPointType.kt.after @@ -0,0 +1,3 @@ +// FIX: Replace total order equality with IEEE 754 equality + +fun test(a: Double, b: Double) = a == b \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 0c90a715dee..3265e781c78 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -1526,11 +1526,36 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsCompareTo.kt"); } + @TestMetadata("equalsDoubleSmartCast.kt") + public void testEqualsDoubleSmartCast() throws Exception { + runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsDoubleSmartCast.kt"); + } + @TestMetadata("equalsExtensionFunction.kt") public void testEqualsExtensionFunction() throws Exception { runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsExtensionFunction.kt"); } + @TestMetadata("equalsFloatSmartCast.kt") + public void testEqualsFloatSmartCast() throws Exception { + runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatSmartCast.kt"); + } + + @TestMetadata("equalsFloatingPointLiteral1.kt") + public void testEqualsFloatingPointLiteral1() throws Exception { + runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatingPointLiteral1.kt"); + } + + @TestMetadata("equalsFloatingPointLiteral2.kt") + public void testEqualsFloatingPointLiteral2() throws Exception { + runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatingPointLiteral2.kt"); + } + + @TestMetadata("equalsFloatingPointType.kt") + public void testEqualsFloatingPointType() throws Exception { + runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsFloatingPointType.kt"); + } + @TestMetadata("extensionFunction.kt") public void testExtensionFunction() throws Exception { runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/extensionFunction.kt");