From 917be5d2715a6d7fcc7147f41e38ce4bb663f09d Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Mon, 27 Nov 2017 20:01:19 +0300 Subject: [PATCH] Support "recursive equals" for direct equals call (related to KT-13702) --- .../RecursiveEqualsCallInspection.kt | 63 ++++++++++++++----- .../recursiveEqualsCall/recursiveDirect.kt | 6 ++ .../recursiveDirect.kt.after | 6 ++ .../recursiveDirectFake.kt | 9 +++ .../recursiveEqualsCall/recursiveImplicit.kt | 6 ++ .../recursiveImplicit.kt.after | 6 ++ .../LocalInspectionTestGenerated.java | 18 ++++++ 7 files changed, 98 insertions(+), 16 deletions(-) create mode 100644 idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveDirect.kt create mode 100644 idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveDirect.kt.after create mode 100644 idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveDirectFake.kt create mode 100644 idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveImplicit.kt create mode 100644 idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveImplicit.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/RecursiveEqualsCallInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/RecursiveEqualsCallInspection.kt index 823bf98a673..9a2941e78bf 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/RecursiveEqualsCallInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/RecursiveEqualsCallInspection.kt @@ -29,29 +29,47 @@ import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.scopes.receivers.ThisClassReceiver +import org.jetbrains.kotlin.util.OperatorNameConventions class RecursiveEqualsCallInspection : AbstractKotlinInspection() { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor { return object : KtVisitorVoid() { + private fun KtExpression.isRecursiveEquals(): Boolean { + val context = analyze(BodyResolveMode.PARTIAL) + val resolvedCall = getResolvedCall(context) + val dispatchReceiver = resolvedCall?.dispatchReceiver as? ThisClassReceiver ?: return false + val calledFunctionDescriptor = resolvedCall.resultingDescriptor as? FunctionDescriptor + if (calledFunctionDescriptor?.isAnyEquals() != true) return false + + val containingFunctionDescriptor = getNonStrictParentOfType()?.descriptor as? FunctionDescriptor + return calledFunctionDescriptor == containingFunctionDescriptor && + dispatchReceiver.classDescriptor == containingFunctionDescriptor.containingDeclaration + } + + private fun KtExpression.reportRecursiveEquals() { + holder.registerProblem(this, + "Recursive equals call", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + ReplaceWithReferentialEqualityFix()) + } + override fun visitBinaryExpression(expr: KtBinaryExpression) { super.visitBinaryExpression(expr) if (expr.operationToken != KtTokens.EQEQ) return - val context = expr.analyze(BodyResolveMode.PARTIAL) - val resolvedCall = expr.getResolvedCall(context) - val dispatchReceiver = resolvedCall?.dispatchReceiver as? ThisClassReceiver ?: return - val calledFunctionDescriptor = resolvedCall.resultingDescriptor as? FunctionDescriptor - if (calledFunctionDescriptor?.isAnyEquals() != true) return + if (!expr.isRecursiveEquals()) return + expr.reportRecursiveEquals() + } - val containingFunctionDescriptor = expr.getNonStrictParentOfType()?.descriptor as? FunctionDescriptor - if (calledFunctionDescriptor != containingFunctionDescriptor) return - if (dispatchReceiver.classDescriptor != containingFunctionDescriptor.containingDeclaration) return + override fun visitCallExpression(expr: KtCallExpression) { + super.visitCallExpression(expr) + val calleeExpression = expr.calleeExpression as? KtSimpleNameExpression ?: return + if (calleeExpression.getReferencedNameAsName() != OperatorNameConventions.EQUALS) return + if (expr.parent is KtSafeQualifiedExpression) return - holder.registerProblem(expr, - "Recursive equals call", - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - ReplaceWithReferentialEqualityFix()) + if (!expr.isRecursiveEquals()) return + expr.reportRecursiveEquals() } } } @@ -63,10 +81,23 @@ private class ReplaceWithReferentialEqualityFix : LocalQuickFix { override fun getFamilyName() = name override fun applyFix(project: Project, descriptor: ProblemDescriptor) { - val expr = descriptor.psiElement as? KtBinaryExpression ?: return - val left = expr.left ?: return - val right = expr.right ?: return - expr.replace(KtPsiFactory(project).createExpressionByPattern("$0 $1 $2", left, "===", right)) + val element = descriptor.psiElement + val (right, target) = when (element) { + is KtBinaryExpression -> element.right to element + is KtCallExpression -> { + val parent = element.parent + val argument = element.valueArguments.first().getArgumentExpression() + if (parent is KtDotQualifiedExpression) { + argument to parent as KtExpression + } + else { + argument to element + } + } + else -> return + } + if (right == null) return + target.replace(KtPsiFactory(project).createExpressionByPattern("this $0 $1", "===", right)) } } diff --git a/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveDirect.kt b/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveDirect.kt new file mode 100644 index 00000000000..a1c4b76da53 --- /dev/null +++ b/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveDirect.kt @@ -0,0 +1,6 @@ +class Test { + override fun equals(other: Any?): Boolean { + if (this.equals(other)) return true + return false + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveDirect.kt.after b/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveDirect.kt.after new file mode 100644 index 00000000000..03d3e0d76a4 --- /dev/null +++ b/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveDirect.kt.after @@ -0,0 +1,6 @@ +class Test { + override fun equals(other: Any?): Boolean { + if (this === other) return true + return false + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveDirectFake.kt b/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveDirectFake.kt new file mode 100644 index 00000000000..ec1cdb88b4b --- /dev/null +++ b/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveDirectFake.kt @@ -0,0 +1,9 @@ +// PROBLEM: none + +class Test { + override fun equals(other: Any?): Boolean { + val another = Test() + if (another.equals(other)) return true + return false + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveImplicit.kt b/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveImplicit.kt new file mode 100644 index 00000000000..e27c843bad3 --- /dev/null +++ b/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveImplicit.kt @@ -0,0 +1,6 @@ +class Test { + override fun equals(other: Any?): Boolean { + if (equals(other)) return true + return false + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveImplicit.kt.after b/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveImplicit.kt.after new file mode 100644 index 00000000000..03d3e0d76a4 --- /dev/null +++ b/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveImplicit.kt.after @@ -0,0 +1,6 @@ +class Test { + override fun equals(other: Any?): Boolean { + if (this === other) return true + return false + } +} \ 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 8601b5109d3..bc23e6d2dd5 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -1514,11 +1514,29 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { doTest(fileName); } + @TestMetadata("recursiveDirect.kt") + public void testRecursiveDirect() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveDirect.kt"); + doTest(fileName); + } + + @TestMetadata("recursiveDirectFake.kt") + public void testRecursiveDirectFake() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveDirectFake.kt"); + doTest(fileName); + } + @TestMetadata("recursiveFake.kt") public void testRecursiveFake() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveFake.kt"); doTest(fileName); } + + @TestMetadata("recursiveImplicit.kt") + public void testRecursiveImplicit() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveImplicit.kt"); + doTest(fileName); + } } @TestMetadata("idea/testData/inspectionsLocal/redundantExplicitType")