Add "'equals()' between objects of inconvertible types" inspection
#KT-25006 Fixed
This commit is contained in:
committed by
Mikhail Glukhikh
parent
a1979677d0
commit
a2205cfc98
@@ -3389,6 +3389,15 @@
|
|||||||
language="kotlin"
|
language="kotlin"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.KotlinEqualsBetweenInconvertibleTypesInspection"
|
||||||
|
displayName="'equals()' between objects of inconvertible types"
|
||||||
|
groupPath="Kotlin"
|
||||||
|
groupName="Probable bugs"
|
||||||
|
enabledByDefault="true"
|
||||||
|
level="WARNING"
|
||||||
|
language="kotlin"
|
||||||
|
/>
|
||||||
|
|
||||||
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
||||||
|
|
||||||
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
This inspection reports <b>equals()</b> between objects of inconvertible primitive / enum / string types.
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.inspections
|
||||||
|
|
||||||
|
import com.intellij.codeInspection.ProblemsHolder
|
||||||
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
|
import org.jetbrains.kotlin.psi.KtExpression
|
||||||
|
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
||||||
|
import org.jetbrains.kotlin.psi.callExpressionVisitor
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
import org.jetbrains.kotlin.types.typeUtil.isEnum
|
||||||
|
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
|
||||||
|
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||||
|
|
||||||
|
class KotlinEqualsBetweenInconvertibleTypesInspection : AbstractKotlinInspection() {
|
||||||
|
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = callExpressionVisitor(fun(call) {
|
||||||
|
val callee = call.calleeExpression as? KtSimpleNameExpression ?: return
|
||||||
|
val identifier = callee.getReferencedNameAsName()
|
||||||
|
if (identifier != OperatorNameConventions.EQUALS) return
|
||||||
|
val receiver = call.getQualifiedExpressionForSelector()?.receiverExpression ?: return
|
||||||
|
val argument = call.valueArguments.singleOrNull()?.getArgumentExpression() ?: return
|
||||||
|
if (call.analyze(BodyResolveMode.PARTIAL).isInconvertibleTypes(receiver, argument)) {
|
||||||
|
holder.registerProblem(callee, "'equals()' between objects of inconvertible types")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun BindingContext.isInconvertibleTypes(expr1: KtExpression?, expr2: KtExpression?): Boolean {
|
||||||
|
val type1 = expr1?.getTargetType(this) ?: return false
|
||||||
|
val type2 = expr2?.getTargetType(this) ?: return false
|
||||||
|
return type1 != type2
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtExpression.getTargetType(context: BindingContext): KotlinType? {
|
||||||
|
return context.getType(this)?.takeIf {
|
||||||
|
KotlinBuiltIns.isPrimitiveTypeOrNullablePrimitiveType(it) || KotlinBuiltIns.isStringOrNullableString(it) || it.isEnum()
|
||||||
|
}?.makeNotNullable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
@@ -28,6 +28,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
|||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||||
import org.jetbrains.kotlin.idea.inspections.AbstractApplicabilityBasedInspection
|
import org.jetbrains.kotlin.idea.inspections.AbstractApplicabilityBasedInspection
|
||||||
|
import org.jetbrains.kotlin.idea.inspections.KotlinEqualsBetweenInconvertibleTypesInspection
|
||||||
import org.jetbrains.kotlin.idea.intentions.callExpression
|
import org.jetbrains.kotlin.idea.intentions.callExpression
|
||||||
import org.jetbrains.kotlin.idea.intentions.conventionNameCalls.isAnyEquals
|
import org.jetbrains.kotlin.idea.intentions.conventionNameCalls.isAnyEquals
|
||||||
import org.jetbrains.kotlin.idea.intentions.isOperatorOrCompatible
|
import org.jetbrains.kotlin.idea.intentions.isOperatorOrCompatible
|
||||||
@@ -155,6 +156,11 @@ class ReplaceCallWithBinaryOperatorInspection : AbstractApplicabilityBasedInspec
|
|||||||
return when (identifier) {
|
return when (identifier) {
|
||||||
OperatorNameConventions.EQUALS -> {
|
OperatorNameConventions.EQUALS -> {
|
||||||
if (!dotQualified.isAnyEquals()) return null
|
if (!dotQualified.isAnyEquals()) return null
|
||||||
|
with(KotlinEqualsBetweenInconvertibleTypesInspection) {
|
||||||
|
val receiver = dotQualified.receiverExpression
|
||||||
|
val argument = dotQualified.callExpression?.valueArguments?.singleOrNull()?.getArgumentExpression()
|
||||||
|
if (dotQualified.analyze(BodyResolveMode.PARTIAL).isInconvertibleTypes(receiver, argument)) return null
|
||||||
|
}
|
||||||
val prefixExpression = dotQualified.getWrappingPrefixExpressionIfAny()
|
val prefixExpression = dotQualified.getWrappingPrefixExpressionIfAny()
|
||||||
if (prefixExpression != null && prefixExpression.operationToken == KtTokens.EXCL) KtTokens.EXCLEQ
|
if (prefixExpression != null && prefixExpression.operationToken == KtTokens.EXCL) KtTokens.EXCLEQ
|
||||||
else KtTokens.EQEQ
|
else KtTokens.EQEQ
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
|
||||||
|
val x = 2.<caret>equals("")
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
org.jetbrains.kotlin.idea.inspections.KotlinEqualsBetweenInconvertibleTypesInspection
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
|
||||||
|
fun test(x: E1, y: E1): Boolean {
|
||||||
|
return x.<caret>equals(y)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class E1
|
||||||
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// FIX: none
|
||||||
|
|
||||||
|
fun test(x: E1, y: E2): Boolean {
|
||||||
|
return x.<caret>equals(y)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class E1
|
||||||
|
|
||||||
|
enum class E2
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// FIX: none
|
||||||
|
|
||||||
|
fun test(x: E1, y: Int): Boolean {
|
||||||
|
return x.<caret>equals(y)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class E1
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// FIX: none
|
||||||
|
|
||||||
|
fun test(x: E1, y: String): Boolean {
|
||||||
|
return x.<caret>equals(y)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class E1
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
|
||||||
|
fun test(x: E1, y: Foo): Boolean {
|
||||||
|
return x.<caret>equals(y)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Foo
|
||||||
|
|
||||||
|
enum class E1
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// FIX: none
|
||||||
|
|
||||||
|
fun test(x: Int, y: E1): Boolean {
|
||||||
|
return x.<caret>equals(y)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class E1
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
|
||||||
|
fun test(x: Int, y: Int): Boolean {
|
||||||
|
return x.<caret>equals(y)
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// FIX: none
|
||||||
|
|
||||||
|
fun test(x: Int, y: Long): Boolean {
|
||||||
|
return x.<caret>equals(y)
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// FIX: none
|
||||||
|
|
||||||
|
fun test(x: Int, y: String): Boolean {
|
||||||
|
return x.<caret>equals(y)
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
|
||||||
|
fun test(x: Int, y: Foo): Boolean {
|
||||||
|
return x.<caret>equals(y)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Foo
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
// FIX: none
|
||||||
|
enum class E1
|
||||||
|
|
||||||
|
fun test(x: E1?, y: String): Boolean? {
|
||||||
|
return x?.<caret>equals(y)
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
// FIX: none
|
||||||
|
fun test(x: Int?, y: String): Boolean? {
|
||||||
|
return x?.<caret>equals(y)
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
// FIX: none
|
||||||
|
fun test(x: String?, y: Int): Boolean? {
|
||||||
|
return x?.<caret>equals(y)
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
fun test() {
|
||||||
|
System.getProperty("some")?.<caret>equals("true")
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// FIX: none
|
||||||
|
|
||||||
|
fun test(x: String, y: E1): Boolean {
|
||||||
|
return x.<caret>equals(y)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class E1
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// FIX: none
|
||||||
|
|
||||||
|
fun test(x: String, y: Int): Boolean {
|
||||||
|
return x.<caret>equals(y)
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
// FIX: none
|
||||||
|
enum class E1
|
||||||
|
|
||||||
|
fun test(x: String, y: E1?): Boolean? {
|
||||||
|
return x.<caret>equals(y)
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
// FIX: none
|
||||||
|
fun test(x: String, y: Int?): Boolean? {
|
||||||
|
return x.<caret>equals(y)
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
fun test(x: String, y: String?): Boolean? {
|
||||||
|
return x.<caret>equals(y)
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
|
||||||
|
fun test(x: String, y: String): Boolean {
|
||||||
|
return x.<caret>equals(y)
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
|
||||||
|
fun test(x: String, y: Foo): Boolean {
|
||||||
|
return x.<caret>equals(y)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Foo
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
|
||||||
|
fun test(x: Foo, y: Int): Boolean {
|
||||||
|
return x.<caret>equals(y)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Foo
|
||||||
+128
@@ -2492,6 +2492,11 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
|
|||||||
runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equals.kt");
|
runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equals.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("equalsBetweenInconvertibleTypes.kt")
|
||||||
|
public void testEqualsBetweenInconvertibleTypes() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsBetweenInconvertibleTypes.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("equalsCompareTo.kt")
|
@TestMetadata("equalsCompareTo.kt")
|
||||||
public void testEqualsCompareTo() throws Exception {
|
public void testEqualsCompareTo() throws Exception {
|
||||||
runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsCompareTo.kt");
|
runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsCompareTo.kt");
|
||||||
@@ -3608,6 +3613,129 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class EqualsBetweenInconvertibleTypes extends AbstractLocalInspectionTest {
|
||||||
|
private void runTest(String testDataFilePath) throws Exception {
|
||||||
|
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAllFilesPresentInEqualsBetweenInconvertibleTypes() throws Exception {
|
||||||
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("enumEqEnum.kt")
|
||||||
|
public void testEnumEqEnum() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/enumEqEnum.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("enumEqEnum2.kt")
|
||||||
|
public void testEnumEqEnum2() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/enumEqEnum2.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("enumEqInt.kt")
|
||||||
|
public void testEnumEqInt() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/enumEqInt.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("enumEqString.kt")
|
||||||
|
public void testEnumEqString() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/enumEqString.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("enumEqUserType.kt")
|
||||||
|
public void testEnumEqUserType() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/enumEqUserType.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("intEqEnum.kt")
|
||||||
|
public void testIntEqEnum() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/intEqEnum.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("intEqInt.kt")
|
||||||
|
public void testIntEqInt() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/intEqInt.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("intEqLong.kt")
|
||||||
|
public void testIntEqLong() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/intEqLong.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("intEqString.kt")
|
||||||
|
public void testIntEqString() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/intEqString.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("intEqUserType.kt")
|
||||||
|
public void testIntEqUserType() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/intEqUserType.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("nullableEnumEqString.kt")
|
||||||
|
public void testNullableEnumEqString() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/nullableEnumEqString.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("nullableIntEqString.kt")
|
||||||
|
public void testNullableIntEqString() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/nullableIntEqString.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("nullableStringEqInt.kt")
|
||||||
|
public void testNullableStringEqInt() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/nullableStringEqInt.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("nullableStringEqString.kt")
|
||||||
|
public void testNullableStringEqString() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/nullableStringEqString.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("stringEqEnum.kt")
|
||||||
|
public void testStringEqEnum() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/stringEqEnum.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("stringEqInt.kt")
|
||||||
|
public void testStringEqInt() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/stringEqInt.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("stringEqNullableEnum.kt")
|
||||||
|
public void testStringEqNullableEnum() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/stringEqNullableEnum.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("stringEqNullableInt.kt")
|
||||||
|
public void testStringEqNullableInt() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/stringEqNullableInt.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("stringEqNullableString.kt")
|
||||||
|
public void testStringEqNullableString() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/stringEqNullableString.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("stringEqString.kt")
|
||||||
|
public void testStringEqString() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/stringEqString.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("stringEqUserType.kt")
|
||||||
|
public void testStringEqUserType() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/stringEqUserType.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("userTypeEqInt.kt")
|
||||||
|
public void testUserTypeEqInt() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/userTypeEqInt.kt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("idea/testData/inspectionsLocal/explicitThis")
|
@TestMetadata("idea/testData/inspectionsLocal/explicitThis")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user