KT-6807 J2K: Convert "==" to "===" if class has equals() method

#KT-6807 Fixed
This commit is contained in:
Valentin Kipyatkov
2015-06-05 20:49:47 +03:00
parent dae7717a69
commit 0cb2348b2f
9 changed files with 135 additions and 17 deletions
@@ -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
+39
View File
@@ -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
View File
@@ -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
}
}
@@ -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");