diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ieee754.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/ieee754.kt index 35a820ecfc3..77d4cc83ad2 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ieee754.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ieee754.kt @@ -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) diff --git a/core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java b/core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java index c81a6ca15ee..4942b18e03f 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java +++ b/core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java @@ -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); } 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 8896328d0eb..36f1e2ec0ee 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/conventionNameCalls/ReplaceCallWithBinaryOperatorInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/conventionNameCalls/ReplaceCallWithBinaryOperatorInspection.kt @@ -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::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 { diff --git a/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsDoubleSmartCastFromGeneric.kt b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsDoubleSmartCastFromGeneric.kt new file mode 100644 index 00000000000..9eede2857a7 --- /dev/null +++ b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsDoubleSmartCastFromGeneric.kt @@ -0,0 +1,4 @@ +// FIX: Replace total order equality with IEEE 754 equality + +fun test(a: T, b: T) = + a is Double && b is Double && a.equals(b) \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsDoubleSmartCastFromGeneric.kt.after b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsDoubleSmartCastFromGeneric.kt.after new file mode 100644 index 00000000000..3b5dc6a8a24 --- /dev/null +++ b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsDoubleSmartCastFromGeneric.kt.after @@ -0,0 +1,4 @@ +// FIX: Replace total order equality with IEEE 754 equality + +fun test(a: T, b: T) = + a is Double && b is 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 3265e781c78..44d45b817cc 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -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");