Generate equals/hashCode(): Use class literals when possible
#KT-18683 Fixed
This commit is contained in:
+36
-6
@@ -24,6 +24,7 @@ import com.intellij.openapi.project.Project
|
|||||||
import com.intellij.psi.PsiNameIdentifierOwner
|
import com.intellij.psi.PsiNameIdentifierOwner
|
||||||
import com.intellij.util.IncorrectOperationException
|
import com.intellij.util.IncorrectOperationException
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
|
import org.jetbrains.kotlin.config.LanguageFeature
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
||||||
@@ -34,11 +35,15 @@ import org.jetbrains.kotlin.idea.core.CollectingNameValidator
|
|||||||
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
|
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
|
||||||
import org.jetbrains.kotlin.idea.core.insertMembersAfter
|
import org.jetbrains.kotlin.idea.core.insertMembersAfter
|
||||||
import org.jetbrains.kotlin.idea.core.quoteIfNeeded
|
import org.jetbrains.kotlin.idea.core.quoteIfNeeded
|
||||||
|
import org.jetbrains.kotlin.idea.project.languageVersionSettings
|
||||||
|
import org.jetbrains.kotlin.idea.project.platform
|
||||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||||
|
import org.jetbrains.kotlin.js.resolve.JsPlatform
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
|
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.TargetPlatform
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
|
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny
|
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
@@ -134,7 +139,27 @@ class KotlinGenerateEqualsAndHashcodeAction : KotlinGenerateMemberActionBase<Kot
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateEquals(project: Project, info: Info): KtNamedFunction? {
|
private fun generateClassLiteralsNotEqual(paramName: String, targetClass: KtClassOrObject): String {
|
||||||
|
val defaultExpression = "$paramName?.javaClass != javaClass"
|
||||||
|
if (!targetClass.languageVersionSettings.supportsFeature(LanguageFeature.BoundCallableReferences)) return defaultExpression
|
||||||
|
return when (targetClass.platform) {
|
||||||
|
is JsPlatform -> "other == null || $paramName::class.js != this::class.js"
|
||||||
|
is TargetPlatform.Default -> "other == null || $paramName::class != this::class"
|
||||||
|
else -> defaultExpression
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateClassLiteral(targetClass: KtClassOrObject): String {
|
||||||
|
val defaultExpression = "javaClass"
|
||||||
|
if (!targetClass.languageVersionSettings.supportsFeature(LanguageFeature.BoundCallableReferences)) return defaultExpression
|
||||||
|
return when (targetClass.platform) {
|
||||||
|
is JsPlatform -> "this::class.js"
|
||||||
|
is TargetPlatform.Default -> "this::class"
|
||||||
|
else -> defaultExpression
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateEquals(project: Project, info: Info, targetClass: KtClassOrObject): KtNamedFunction? {
|
||||||
with(info) {
|
with(info) {
|
||||||
if (!needEquals) return null
|
if (!needEquals) return null
|
||||||
|
|
||||||
@@ -149,7 +174,12 @@ class KotlinGenerateEqualsAndHashcodeAction : KotlinGenerateMemberActionBase<Kot
|
|||||||
}
|
}
|
||||||
|
|
||||||
val useIsCheck = CodeInsightSettings.getInstance().USE_INSTANCEOF_ON_EQUALS_PARAMETER
|
val useIsCheck = CodeInsightSettings.getInstance().USE_INSTANCEOF_ON_EQUALS_PARAMETER
|
||||||
val isNotInstanceCondition = if (useIsCheck) "$paramName !is $typeForCast" else "$paramName?.javaClass != javaClass"
|
val isNotInstanceCondition = if (useIsCheck) {
|
||||||
|
"$paramName !is $typeForCast"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
generateClassLiteralsNotEqual(paramName, targetClass)
|
||||||
|
}
|
||||||
val bodyText = StringBuilder().apply {
|
val bodyText = StringBuilder().apply {
|
||||||
append("if (this === $paramName) return true\n")
|
append("if (this === $paramName) return true\n")
|
||||||
append("if ($isNotInstanceCondition) return false\n")
|
append("if ($isNotInstanceCondition) return false\n")
|
||||||
@@ -189,7 +219,7 @@ class KotlinGenerateEqualsAndHashcodeAction : KotlinGenerateMemberActionBase<Kot
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateHashCode(project: Project, info: Info): KtNamedFunction? {
|
private fun generateHashCode(project: Project, info: Info, targetClass: KtClassOrObject): KtNamedFunction? {
|
||||||
fun VariableDescriptor.genVariableHashCode(parenthesesNeeded: Boolean): String {
|
fun VariableDescriptor.genVariableHashCode(parenthesesNeeded: Boolean): String {
|
||||||
val ref = (DescriptorToSourceUtilsIde.getAnyDeclaration(project, this) as PsiNameIdentifierOwner).nameIdentifier!!.text
|
val ref = (DescriptorToSourceUtilsIde.getAnyDeclaration(project, this) as PsiNameIdentifierOwner).nameIdentifier!!.text
|
||||||
val isNullable = TypeUtils.isNullableType(type)
|
val isNullable = TypeUtils.isNullableType(type)
|
||||||
@@ -226,7 +256,7 @@ class KotlinGenerateEqualsAndHashcodeAction : KotlinGenerateMemberActionBase<Kot
|
|||||||
val initialValue = when {
|
val initialValue = when {
|
||||||
!builtins.isMemberOfAny(superHashCode) -> "super.hashCode()"
|
!builtins.isMemberOfAny(superHashCode) -> "super.hashCode()"
|
||||||
propertyIterator.hasNext() -> propertyIterator.next().genVariableHashCode(false)
|
propertyIterator.hasNext() -> propertyIterator.next().genVariableHashCode(false)
|
||||||
else -> "javaClass.hashCode()"
|
else -> generateClassLiteral(targetClass) + ".hashCode()"
|
||||||
}
|
}
|
||||||
|
|
||||||
val bodyText = if (propertyIterator.hasNext()) {
|
val bodyText = if (propertyIterator.hasNext()) {
|
||||||
@@ -249,8 +279,8 @@ class KotlinGenerateEqualsAndHashcodeAction : KotlinGenerateMemberActionBase<Kot
|
|||||||
val targetClass = info.classDescriptor.source.getPsi() as KtClass
|
val targetClass = info.classDescriptor.source.getPsi() as KtClass
|
||||||
val prototypes = ArrayList<KtDeclaration>(2)
|
val prototypes = ArrayList<KtDeclaration>(2)
|
||||||
.apply {
|
.apply {
|
||||||
addIfNotNull(generateEquals(project, info))
|
addIfNotNull(generateEquals(project, info, targetClass))
|
||||||
addIfNotNull(generateHashCode(project, info))
|
addIfNotNull(generateHashCode(project, info, targetClass))
|
||||||
}
|
}
|
||||||
val anchor = with(targetClass.declarations) { lastIsInstanceOrNull<KtNamedFunction>() ?: lastOrNull() }
|
val anchor = with(targetClass.declarations) { lastIsInstanceOrNull<KtNamedFunction>() ?: lastOrNull() }
|
||||||
return insertMembersAfter(editor, targetClass, prototypes, anchor)
|
return insertMembersAfter(editor, targetClass, prototypes, anchor)
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// PLATFORM: Common
|
||||||
|
class A(val n: Int, val s: String) {<caret>
|
||||||
|
val f: Float = 1.0f
|
||||||
|
|
||||||
|
fun foo() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
// PLATFORM: Common
|
||||||
|
class A(val n: Int, val s: String) {
|
||||||
|
val f: Float = 1.0f
|
||||||
|
|
||||||
|
fun foo() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
<caret>override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other == null || other::class != this::class) return false
|
||||||
|
|
||||||
|
other as A
|
||||||
|
|
||||||
|
if (n != other.n) return false
|
||||||
|
if (s != other.s) return false
|
||||||
|
if (f != other.f) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = n
|
||||||
|
result = 31 * result + s.hashCode()
|
||||||
|
result = 31 * result + f.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// PLATFORM: JavaScript
|
||||||
|
class A(val n: Int, val s: String) {<caret>
|
||||||
|
val f: Float = 1.0f
|
||||||
|
|
||||||
|
fun foo() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
// PLATFORM: JavaScript
|
||||||
|
class A(val n: Int, val s: String) {
|
||||||
|
val f: Float = 1.0f
|
||||||
|
|
||||||
|
fun foo() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
<caret>override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other == null || other::class.js != this::class.js) return false
|
||||||
|
|
||||||
|
other as A
|
||||||
|
|
||||||
|
if (n != other.n) return false
|
||||||
|
if (s != other.s) return false
|
||||||
|
if (f != other.f) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = n
|
||||||
|
result = 31 * result + s.hashCode()
|
||||||
|
result = 31 * result + f.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// PLATFORM: Common
|
||||||
|
class A {<caret>
|
||||||
|
fun foo() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// PLATFORM: Common
|
||||||
|
class A {
|
||||||
|
fun foo() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
<caret>override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other == null || other::class != this::class) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return this::class.hashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// PLATFORM: JavaScript
|
||||||
|
class A {<caret>
|
||||||
|
fun foo() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// PLATFORM: JavaScript
|
||||||
|
class A {
|
||||||
|
fun foo() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
<caret>override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other == null || other::class.js != this::class.js) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return this::class.js.hashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
-1
@@ -28,6 +28,11 @@ import junit.framework.TestCase
|
|||||||
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
|
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
|
||||||
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
||||||
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
|
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
|
||||||
|
import org.jetbrains.kotlin.js.resolve.JsPlatform
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
import org.jetbrains.kotlin.psi.targetPlatform
|
||||||
|
import org.jetbrains.kotlin.resolve.TargetPlatform
|
||||||
|
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
|
||||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -61,6 +66,8 @@ abstract class AbstractCodeInsightActionTest : KotlinLightCodeInsightFixtureTest
|
|||||||
val conflictFile = File("$path.messages")
|
val conflictFile = File("$path.messages")
|
||||||
val afterFile = File("$path.after")
|
val afterFile = File("$path.after")
|
||||||
|
|
||||||
|
var mainPsiFile: KtFile? = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ConfigLibraryUtil.configureLibrariesByDirective(myModule, PlatformTestUtil.getCommunityPath(), fileText)
|
ConfigLibraryUtil.configureLibrariesByDirective(myModule, PlatformTestUtil.getCommunityPath(), fileText)
|
||||||
|
|
||||||
@@ -76,7 +83,18 @@ abstract class AbstractCodeInsightActionTest : KotlinLightCodeInsightFixtureTest
|
|||||||
myFixture.configureByFile(File(rootDir, it).path.replace(File.separator, "/"))
|
myFixture.configureByFile(File(rootDir, it).path.replace(File.separator, "/"))
|
||||||
}
|
}
|
||||||
configureExtra(path, fileText)
|
configureExtra(path, fileText)
|
||||||
myFixture.configureByFile(path)
|
mainPsiFile = myFixture.configureByFile(path) as KtFile
|
||||||
|
|
||||||
|
val targetPlatformName = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// PLATFORM: ")
|
||||||
|
if (targetPlatformName != null) {
|
||||||
|
val targetPlatform = when (targetPlatformName) {
|
||||||
|
"JVM" -> JvmPlatform
|
||||||
|
"JavaScript" -> JsPlatform
|
||||||
|
"Common" -> TargetPlatform.Default
|
||||||
|
else -> error("Unexpected platform name: $targetPlatformName")
|
||||||
|
}
|
||||||
|
mainPsiFile.targetPlatform = targetPlatform
|
||||||
|
}
|
||||||
|
|
||||||
val action = createAction(fileText)
|
val action = createAction(fileText)
|
||||||
|
|
||||||
@@ -103,6 +121,7 @@ abstract class AbstractCodeInsightActionTest : KotlinLightCodeInsightFixtureTest
|
|||||||
KotlinTestUtils.assertEqualsToFile(conflictFile, e.message!!)
|
KotlinTestUtils.assertEqualsToFile(conflictFile, e.message!!)
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
mainPsiFile?.targetPlatform = null
|
||||||
ConfigLibraryUtil.unconfigureLibrariesByDirective(myModule, fileText)
|
ConfigLibraryUtil.unconfigureLibrariesByDirective(myModule, fileText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+24
@@ -108,6 +108,18 @@ public class GenerateHashCodeAndEqualsActionTestGenerated extends AbstractGenera
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("multipleVarsCommon.kt")
|
||||||
|
public void testMultipleVarsCommon() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/multipleVarsCommon.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("multipleVarsJS.kt")
|
||||||
|
public void testMultipleVarsJS() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/multipleVarsJS.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("multipleVarsNullable.kt")
|
@TestMetadata("multipleVarsNullable.kt")
|
||||||
public void testMultipleVarsNullable() throws Exception {
|
public void testMultipleVarsNullable() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/multipleVarsNullable.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/multipleVarsNullable.kt");
|
||||||
@@ -132,6 +144,18 @@ public class GenerateHashCodeAndEqualsActionTestGenerated extends AbstractGenera
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("noVarsCommon.kt")
|
||||||
|
public void testNoVarsCommon() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/noVarsCommon.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("noVarsJS.kt")
|
||||||
|
public void testNoVarsJS() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/noVarsJS.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("noVarsWithSuperClass.kt")
|
@TestMetadata("noVarsWithSuperClass.kt")
|
||||||
public void testNoVarsWithSuperClass() throws Exception {
|
public void testNoVarsWithSuperClass() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/noVarsWithSuperClass.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/noVarsWithSuperClass.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user