Support "recursive equals" for direct equals call (related to KT-13702)
This commit is contained in:
@@ -29,29 +29,47 @@ import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
|||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ThisClassReceiver
|
import org.jetbrains.kotlin.resolve.scopes.receivers.ThisClassReceiver
|
||||||
|
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||||
|
|
||||||
class RecursiveEqualsCallInspection : AbstractKotlinInspection() {
|
class RecursiveEqualsCallInspection : AbstractKotlinInspection() {
|
||||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
|
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
|
||||||
return object : KtVisitorVoid() {
|
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<KtNamedFunction>()?.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) {
|
override fun visitBinaryExpression(expr: KtBinaryExpression) {
|
||||||
super.visitBinaryExpression(expr)
|
super.visitBinaryExpression(expr)
|
||||||
if (expr.operationToken != KtTokens.EQEQ) return
|
if (expr.operationToken != KtTokens.EQEQ) return
|
||||||
|
|
||||||
val context = expr.analyze(BodyResolveMode.PARTIAL)
|
if (!expr.isRecursiveEquals()) return
|
||||||
val resolvedCall = expr.getResolvedCall(context)
|
expr.reportRecursiveEquals()
|
||||||
val dispatchReceiver = resolvedCall?.dispatchReceiver as? ThisClassReceiver ?: return
|
}
|
||||||
val calledFunctionDescriptor = resolvedCall.resultingDescriptor as? FunctionDescriptor
|
|
||||||
if (calledFunctionDescriptor?.isAnyEquals() != true) return
|
|
||||||
|
|
||||||
val containingFunctionDescriptor = expr.getNonStrictParentOfType<KtNamedFunction>()?.descriptor as? FunctionDescriptor
|
override fun visitCallExpression(expr: KtCallExpression) {
|
||||||
if (calledFunctionDescriptor != containingFunctionDescriptor) return
|
super.visitCallExpression(expr)
|
||||||
if (dispatchReceiver.classDescriptor != containingFunctionDescriptor.containingDeclaration) return
|
val calleeExpression = expr.calleeExpression as? KtSimpleNameExpression ?: return
|
||||||
|
if (calleeExpression.getReferencedNameAsName() != OperatorNameConventions.EQUALS) return
|
||||||
|
if (expr.parent is KtSafeQualifiedExpression) return
|
||||||
|
|
||||||
holder.registerProblem(expr,
|
if (!expr.isRecursiveEquals()) return
|
||||||
"Recursive equals call",
|
expr.reportRecursiveEquals()
|
||||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
|
|
||||||
ReplaceWithReferentialEqualityFix())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,10 +81,23 @@ private class ReplaceWithReferentialEqualityFix : LocalQuickFix {
|
|||||||
override fun getFamilyName() = name
|
override fun getFamilyName() = name
|
||||||
|
|
||||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||||
val expr = descriptor.psiElement as? KtBinaryExpression ?: return
|
val element = descriptor.psiElement
|
||||||
val left = expr.left ?: return
|
val (right, target) = when (element) {
|
||||||
val right = expr.right ?: return
|
is KtBinaryExpression -> element.right to element
|
||||||
expr.replace(KtPsiFactory(project).createExpressionByPattern("$0 $1 $2", left, "===", right))
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
class Test {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this.equals<caret>(other)) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
class Test {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
|
||||||
|
class Test {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
val another = Test()
|
||||||
|
if (another.equals<caret>(other)) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
class Test {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (equals<caret>(other)) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
class Test {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1514,11 +1514,29 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("recursiveFake.kt")
|
||||||
public void testRecursiveFake() throws Exception {
|
public void testRecursiveFake() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveFake.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveFake.kt");
|
||||||
doTest(fileName);
|
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")
|
@TestMetadata("idea/testData/inspectionsLocal/redundantExplicitType")
|
||||||
|
|||||||
Reference in New Issue
Block a user