diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedEqualsInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedEqualsInspection.kt index 39aaddfa483..70c34346de9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedEqualsInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedEqualsInspection.kt @@ -20,30 +20,44 @@ import com.intellij.codeInspection.ProblemHighlightType import com.intellij.codeInspection.ProblemsHolder import com.intellij.psi.PsiElementVisitor import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.intentions.conventionNameCalls.isAnyEquals import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.psi.KtBinaryExpression -import org.jetbrains.kotlin.psi.KtBlockExpression -import org.jetbrains.kotlin.psi.KtIfExpression -import org.jetbrains.kotlin.psi.KtVisitorVoid +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression +import org.jetbrains.kotlin.util.OperatorNameConventions class UnusedEqualsInspection : AbstractKotlinInspection() { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { return object : KtVisitorVoid() { + private fun reportIfNotUsedAsExpression(expression: KtExpression, target: KtExpression) { + val context = expression.analyze() + if (!expression.isUsedAsExpression(context)) { + holder.registerProblem(target, + "Unused equals expression", + ProblemHighlightType.LIKE_UNUSED_SYMBOL) + + } + } + override fun visitBinaryExpression(expression: KtBinaryExpression) { super.visitBinaryExpression(expression) if (expression.operationToken == KtTokens.EQEQ && (expression.parent is KtBlockExpression || expression.parent.parent is KtIfExpression)) { - val context = expression.analyze() - if (!expression.isUsedAsExpression(context)) { - holder.registerProblem(expression.operationReference, - "Unused equals expression", - ProblemHighlightType.LIKE_UNUSED_SYMBOL) - - } + reportIfNotUsedAsExpression(expression, expression.operationReference) } } + + override fun visitDotQualifiedExpression(expression: KtDotQualifiedExpression) { + super.visitDotQualifiedExpression(expression) + + val callExpression = expression.selectorExpression as? KtCallExpression ?: return + val calleeExpression = callExpression.calleeExpression as? KtSimpleNameExpression ?: return + if (calleeExpression.getReferencedNameAsName() != OperatorNameConventions.EQUALS) return + + if (!expression.isAnyEquals()) return + reportIfNotUsedAsExpression(expression, calleeExpression) + } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/conventionNameCalls/ConventionUtils.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/conventionNameCalls/ConventionUtils.kt new file mode 100644 index 00000000000..88d756967a3 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/conventionNameCalls/ConventionUtils.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.intentions.conventionNameCalls + +import org.jetbrains.kotlin.idea.intentions.toResolvedCall +import org.jetbrains.kotlin.psi.KtDotQualifiedExpression +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe +import org.jetbrains.kotlin.resolve.findOriginalTopMostOverriddenDescriptors +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode + +fun KtDotQualifiedExpression.isAnyEquals(): Boolean { + val resolvedCall = toResolvedCall(BodyResolveMode.PARTIAL) ?: return false + val overriddenDescriptors = resolvedCall.resultingDescriptor.findOriginalTopMostOverriddenDescriptors() + return overriddenDescriptors.any { it.fqNameUnsafe.asString() == "kotlin.Any.equals" } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/conventionNameCalls/ReplaceCallWithBinaryOperatorIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/conventionNameCalls/ReplaceCallWithBinaryOperatorIntention.kt index aaf3a8ce9fc..a339d56e4f7 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/conventionNameCalls/ReplaceCallWithBinaryOperatorIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/conventionNameCalls/ReplaceCallWithBinaryOperatorIntention.kt @@ -112,9 +112,7 @@ class ReplaceCallWithBinaryOperatorIntention : SelfTargetingRangeIntention { - val resolvedCall = dotQualified.toResolvedCall(BodyResolveMode.PARTIAL) ?: return null - val overriddenDescriptors = resolvedCall.resultingDescriptor.findOriginalTopMostOverriddenDescriptors() - if (overriddenDescriptors.none { it.fqNameUnsafe.asString() == "kotlin.Any.equals" }) return null + if (!dotQualified.isAnyEquals()) return null val prefixExpression = dotQualified.getWrappingPrefixExpressionIfAny() if (prefixExpression != null && prefixExpression.operationToken == KtTokens.EXCL) KtTokens.EXCLEQ diff --git a/idea/testData/inspections/unusedEquals/consumer/ConsumerVoid.java b/idea/testData/inspections/unusedEquals/consumer/ConsumerVoid.java new file mode 100644 index 00000000000..e96ef745863 --- /dev/null +++ b/idea/testData/inspections/unusedEquals/consumer/ConsumerVoid.java @@ -0,0 +1,5 @@ +package consumer; + +public interface ConsumerVoid { + void consumer(T t); +} \ No newline at end of file diff --git a/idea/testData/inspections/unusedEquals/inspectionData/expected.xml b/idea/testData/inspections/unusedEquals/inspectionData/expected.xml index d676859e2a6..48fdc0cdea1 100644 --- a/idea/testData/inspections/unusedEquals/inspectionData/expected.xml +++ b/idea/testData/inspections/unusedEquals/inspectionData/expected.xml @@ -79,4 +79,22 @@ Unused equals expression Unused equals expression + + + test.kt + 71 + light_idea_test_case + + Unused equals expression + Unused equals expression + + + + test.kt + 75 + light_idea_test_case + + Unused equals expression + Unused equals expression + diff --git a/idea/testData/inspections/unusedEquals/test.kt b/idea/testData/inspections/unusedEquals/test.kt index 21d786bf7de..abbbe67fc01 100644 --- a/idea/testData/inspections/unusedEquals/test.kt +++ b/idea/testData/inspections/unusedEquals/test.kt @@ -67,6 +67,14 @@ fun foo9() { consumer.Consumer { it == null } // used } +fun foo9a() { + consumer.ConsumerVoid { it == null } // not used +} + +fun foo9b() { + consumer.ConsumerVoid { it.equals(null) } // not used +} + class Test(val successCondition: (Int) -> Boolean) { fun pass(num: Int): Boolean = successCondition(num) }