Add "'equals()' between objects of inconvertible types" inspection

#KT-25006 Fixed
This commit is contained in:
Toshiaki Kameyama
2019-04-04 11:55:39 +03:00
committed by Mikhail Glukhikh
parent a1979677d0
commit a2205cfc98
29 changed files with 331 additions and 0 deletions
@@ -3389,6 +3389,15 @@
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"/>
<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>
@@ -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()
}
}
}
@@ -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.resolveToDescriptorIfAny
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.conventionNameCalls.isAnyEquals
import org.jetbrains.kotlin.idea.intentions.isOperatorOrCompatible
@@ -155,6 +156,11 @@ class ReplaceCallWithBinaryOperatorInspection : AbstractApplicabilityBasedInspec
return when (identifier) {
OperatorNameConventions.EQUALS -> {
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()
if (prefixExpression != null && prefixExpression.operationToken == KtTokens.EXCL) KtTokens.EXCLEQ
else KtTokens.EQEQ
@@ -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
@@ -0,0 +1,7 @@
// FIX: none
fun test(x: E1, y: String): Boolean {
return x.<caret>equals(y)
}
enum class E1
@@ -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)
}
@@ -0,0 +1,7 @@
// PROBLEM: none
fun test(x: Int, y: Foo): Boolean {
return x.<caret>equals(y)
}
class Foo
@@ -0,0 +1,6 @@
// FIX: none
enum class E1
fun test(x: E1?, y: String): Boolean? {
return x?.<caret>equals(y)
}
@@ -0,0 +1,4 @@
// FIX: none
fun test(x: Int?, y: String): Boolean? {
return x?.<caret>equals(y)
}
@@ -0,0 +1,4 @@
// FIX: none
fun test(x: String?, y: Int): Boolean? {
return x?.<caret>equals(y)
}
@@ -0,0 +1,4 @@
// PROBLEM: none
fun test() {
System.getProperty("some")?.<caret>equals("true")
}
@@ -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)
}
@@ -0,0 +1,6 @@
// FIX: none
enum class E1
fun test(x: String, y: E1?): Boolean? {
return x.<caret>equals(y)
}
@@ -0,0 +1,4 @@
// FIX: none
fun test(x: String, y: Int?): Boolean? {
return x.<caret>equals(y)
}
@@ -0,0 +1,4 @@
// PROBLEM: none
fun test(x: String, y: String?): Boolean? {
return x.<caret>equals(y)
}
@@ -0,0 +1,5 @@
// PROBLEM: none
fun test(x: String, y: String): Boolean {
return x.<caret>equals(y)
}
@@ -0,0 +1,7 @@
// PROBLEM: none
fun test(x: String, y: Foo): Boolean {
return x.<caret>equals(y)
}
class Foo
@@ -0,0 +1,7 @@
// PROBLEM: none
fun test(x: Foo, y: Int): Boolean {
return x.<caret>equals(y)
}
class Foo
@@ -2492,6 +2492,11 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
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")
public void testEqualsCompareTo() throws Exception {
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")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)