KT-6807 J2K: Convert "==" to "===" if class has equals() method
#KT-6807 Fixed
This commit is contained in:
@@ -349,8 +349,7 @@ class Converter private(
|
||||
if (specifyAlways) return convertType()
|
||||
|
||||
val initializer = variable.getInitializer()
|
||||
if (initializer == null) return convertType()
|
||||
if (initializer is PsiLiteralExpression && initializer.getType() == PsiType.NULL) return convertType()
|
||||
if (initializer == null || initializer.isNullLiteral()) return convertType()
|
||||
|
||||
if (canChangeType) return null
|
||||
|
||||
|
||||
@@ -16,12 +16,11 @@
|
||||
|
||||
package org.jetbrains.kotlin.j2k
|
||||
|
||||
import com.intellij.codeInsight.generation.GenerateEqualsHelper
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.CommonClassNames.JAVA_LANG_DOUBLE
|
||||
import com.intellij.psi.CommonClassNames.JAVA_LANG_FLOAT
|
||||
import com.intellij.psi.CommonClassNames.JAVA_LANG_INTEGER
|
||||
import com.intellij.psi.CommonClassNames.JAVA_LANG_LONG
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.tree.IElementType
|
||||
import com.intellij.psi.util.MethodSignatureUtil
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import org.jetbrains.kotlin.asJava.KotlinLightField
|
||||
import org.jetbrains.kotlin.asJava.KotlinLightMethod
|
||||
@@ -32,7 +31,6 @@ import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isExtensionDeclaration
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
|
||||
import org.jetbrains.kotlin.types.expressions.OperatorConventions
|
||||
|
||||
trait ExpressionConverter {
|
||||
fun convertExpression(expression: PsiExpression, codeConverter: CodeConverter): Expression
|
||||
@@ -105,21 +103,53 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter {
|
||||
}
|
||||
|
||||
override fun visitBinaryExpression(expression: PsiBinaryExpression) {
|
||||
var lhs = codeConverter.convertExpression(expression.getLOperand(), null)
|
||||
var rhs = codeConverter.convertExpression(expression.getROperand(), null)
|
||||
val left = expression.getLOperand()
|
||||
val right = expression.getROperand()
|
||||
var leftConverted = codeConverter.convertExpression(left, null)
|
||||
var rightConverted = codeConverter.convertExpression(right, null)
|
||||
|
||||
if (expression.getOperationTokenType() in NON_NULL_OPERAND_OPS) {
|
||||
lhs = BangBangExpression.surroundIfNullable(lhs)
|
||||
rhs = BangBangExpression.surroundIfNullable(rhs)
|
||||
val operationTokenType = expression.getOperationTokenType()
|
||||
if (operationTokenType in NON_NULL_OPERAND_OPS) {
|
||||
leftConverted = BangBangExpression.surroundIfNullable(leftConverted)
|
||||
rightConverted = BangBangExpression.surroundIfNullable(rightConverted)
|
||||
}
|
||||
if (expression.getOperationTokenType() == JavaTokenType.GTGTGT) {
|
||||
result = MethodCallExpression.buildNotNull(lhs, "ushr", listOf(rhs))
|
||||
|
||||
if (operationTokenType == JavaTokenType.GTGTGT) {
|
||||
result = MethodCallExpression.buildNotNull(leftConverted, "ushr", listOf(rightConverted))
|
||||
}
|
||||
else {
|
||||
result = BinaryExpression(lhs, rhs, getOperatorString(expression.getOperationSign().getTokenType()))
|
||||
var operatorString = getOperatorString(operationTokenType)
|
||||
if (operationTokenType == JavaTokenType.EQEQ || operationTokenType == JavaTokenType.NE) {
|
||||
if (!canKeepEqEq(left, right)) {
|
||||
operatorString += "="
|
||||
}
|
||||
}
|
||||
result = BinaryExpression(leftConverted, rightConverted, operatorString)
|
||||
}
|
||||
}
|
||||
|
||||
private fun canKeepEqEq(left: PsiExpression, right: PsiExpression?): Boolean {
|
||||
if (left.isNullLiteral() || (right?.isNullLiteral() ?: false)) return true
|
||||
val type = left.getType()
|
||||
when (type) {
|
||||
is PsiPrimitiveType, is PsiArrayType -> return true
|
||||
|
||||
is PsiClassType -> {
|
||||
val psiClass = type.resolve() ?: return false
|
||||
if (!psiClass.hasModifierProperty(PsiModifier.FINAL)) return false
|
||||
|
||||
val equalsSignature = GenerateEqualsHelper.getEqualsSignature(converter.project, GlobalSearchScope.allScope(converter.project))
|
||||
val equalsMethod = MethodSignatureUtil.findMethodBySignature(psiClass, equalsSignature, true)
|
||||
if (equalsMethod != null && equalsMethod.getContainingClass()?.getQualifiedName() != CommonClassNames.JAVA_LANG_OBJECT) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
else -> return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private val NON_NULL_OPERAND_OPS = setOf(
|
||||
JavaTokenType.ANDAND,
|
||||
JavaTokenType.OROR,
|
||||
|
||||
@@ -291,7 +291,7 @@ class TypeConverter(val converter: Converter) {
|
||||
val operationType = parent.getOperationTokenType()
|
||||
if (operationType == JavaTokenType.EQEQ || operationType == JavaTokenType.NE) {
|
||||
val otherOperand = if (usage == parent.getLOperand()) parent.getROperand() else parent.getLOperand()
|
||||
return otherOperand is PsiLiteralExpression && otherOperand.getType() == PsiType.NULL
|
||||
return otherOperand?.isNullLiteral() ?: false
|
||||
}
|
||||
}
|
||||
else if (parent is PsiVariable && usage == parent.getInitializer() && parent.isEffectivelyFinal()) {
|
||||
|
||||
@@ -141,3 +141,4 @@ fun PsiMember.isImported(file: PsiJavaFile): Boolean {
|
||||
}
|
||||
}
|
||||
|
||||
fun PsiExpression.isNullLiteral() = this is PsiLiteralExpression && getType() == PsiType.NULL
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
interface I{}
|
||||
|
||||
final class C{}
|
||||
|
||||
class O{}
|
||||
|
||||
final class E {
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return super.equals(o);
|
||||
}
|
||||
}
|
||||
|
||||
class B {
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return super.equals(o);
|
||||
}
|
||||
}
|
||||
|
||||
final class BB extends B {}
|
||||
|
||||
class X {
|
||||
void foo(I i1, I i2, String s1, String s2, C c1, C c2, int i, O o1, O o2, E e1, E e2, BB bb1, BB bb2, int[] arr1, int[] arr2) {
|
||||
if (i1 == i2) return;
|
||||
if (s1 == s2) return;
|
||||
if (c1 == c2) return;
|
||||
if (i1 == null) return;
|
||||
if (null == i2) return;
|
||||
if (i == 0) return;
|
||||
if (o1 == o2) return;
|
||||
if (e1 == e2) return;
|
||||
if (bb1 == bb2) return;
|
||||
if (arr1 == arr2) return;
|
||||
|
||||
if (s1 != s2) return;
|
||||
if (c1 != c2) return;
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
interface I
|
||||
|
||||
class C
|
||||
|
||||
class O
|
||||
|
||||
class E {
|
||||
override fun equals(o: Any?): Boolean {
|
||||
return super.equals(o)
|
||||
}
|
||||
}
|
||||
|
||||
open class B {
|
||||
override fun equals(o: Any?): Boolean {
|
||||
return super.equals(o)
|
||||
}
|
||||
}
|
||||
|
||||
class BB : B()
|
||||
|
||||
class X {
|
||||
fun foo(i1: I?, i2: I?, s1: String, s2: String, c1: C, c2: C, i: Int, o1: O, o2: O, e1: E, e2: E, bb1: BB, bb2: BB, arr1: IntArray, arr2: IntArray) {
|
||||
if (i1 === i2) return
|
||||
if (s1 === s2) return
|
||||
if (c1 == c2) return
|
||||
if (i1 == null) return
|
||||
if (null == i2) return
|
||||
if (i == 0) return
|
||||
if (o1 === o2) return
|
||||
if (e1 === e2) return
|
||||
if (bb1 === bb2) return
|
||||
if (arr1 == arr2) return
|
||||
|
||||
if (s1 !== s2) return
|
||||
if (c1 != c2) return
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -3,7 +3,7 @@ test@ for (i in 0..max) {
|
||||
var j = i
|
||||
var k = 0
|
||||
while (n-- != 0) {
|
||||
if (searchMe.charAt(j++) != substring.charAt(k++)) {
|
||||
if (searchMe.charAt(j++) !== substring.charAt(k++)) {
|
||||
continue@test
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1729,6 +1729,12 @@ public class JavaToKotlinConverterForWebDemoTestGenerated extends AbstractJavaTo
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("j2k/testData/fileOrElement/equals"), Pattern.compile("^(.+)\\.java$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("EqOperator.java")
|
||||
public void testEqOperator() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/equals/EqOperator.java");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("Equals1.java")
|
||||
public void testEquals1() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/equals/Equals1.java");
|
||||
|
||||
@@ -1729,6 +1729,12 @@ public class JavaToKotlinConverterSingleFileTestGenerated extends AbstractJavaTo
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("j2k/testData/fileOrElement/equals"), Pattern.compile("^(.+)\\.java$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("EqOperator.java")
|
||||
public void testEqOperator() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/equals/EqOperator.java");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("Equals1.java")
|
||||
public void testEquals1() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/equals/Equals1.java");
|
||||
|
||||
Reference in New Issue
Block a user