From e3e4c447fa845f40af4bcff01bc63ea2550a5e52 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Thu, 22 Jun 2017 20:17:23 +0300 Subject: [PATCH] Generate equals/hashCode(): Use class literals when possible #KT-18683 Fixed --- .../KotlinGenerateEqualsAndHashcodeAction.kt | 42 ++++++++++++++++--- .../equalsWithHashCode/multipleVarsCommon.kt | 8 ++++ .../multipleVarsCommon.kt.after | 28 +++++++++++++ .../equalsWithHashCode/multipleVarsJS.kt | 8 ++++ .../multipleVarsJS.kt.after | 28 +++++++++++++ .../equalsWithHashCode/noVarsCommon.kt | 6 +++ .../equalsWithHashCode/noVarsCommon.kt.after | 16 +++++++ .../generate/equalsWithHashCode/noVarsJS.kt | 6 +++ .../equalsWithHashCode/noVarsJS.kt.after | 16 +++++++ .../generate/AbstractCodeInsightActionTest.kt | 21 +++++++++- ...eHashCodeAndEqualsActionTestGenerated.java | 24 +++++++++++ 11 files changed, 196 insertions(+), 7 deletions(-) create mode 100644 idea/testData/codeInsight/generate/equalsWithHashCode/multipleVarsCommon.kt create mode 100644 idea/testData/codeInsight/generate/equalsWithHashCode/multipleVarsCommon.kt.after create mode 100644 idea/testData/codeInsight/generate/equalsWithHashCode/multipleVarsJS.kt create mode 100644 idea/testData/codeInsight/generate/equalsWithHashCode/multipleVarsJS.kt.after create mode 100644 idea/testData/codeInsight/generate/equalsWithHashCode/noVarsCommon.kt create mode 100644 idea/testData/codeInsight/generate/equalsWithHashCode/noVarsCommon.kt.after create mode 100644 idea/testData/codeInsight/generate/equalsWithHashCode/noVarsJS.kt create mode 100644 idea/testData/codeInsight/generate/equalsWithHashCode/noVarsJS.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateEqualsAndHashcodeAction.kt b/idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateEqualsAndHashcodeAction.kt index 7dae0fa21e1..33cb9f586be 100644 --- a/idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateEqualsAndHashcodeAction.kt +++ b/idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateEqualsAndHashcodeAction.kt @@ -24,6 +24,7 @@ import com.intellij.openapi.project.Project import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.util.IncorrectOperationException import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor 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.insertMembersAfter 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.application.runWriteAction +import org.jetbrains.kotlin.js.resolve.JsPlatform import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext 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.getSuperClassOrAny import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode @@ -134,7 +139,27 @@ class KotlinGenerateEqualsAndHashcodeAction : KotlinGenerateMemberActionBase "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) { if (!needEquals) return null @@ -149,7 +174,12 @@ class KotlinGenerateEqualsAndHashcodeAction : KotlinGenerateMemberActionBase "super.hashCode()" propertyIterator.hasNext() -> propertyIterator.next().genVariableHashCode(false) - else -> "javaClass.hashCode()" + else -> generateClassLiteral(targetClass) + ".hashCode()" } val bodyText = if (propertyIterator.hasNext()) { @@ -249,8 +279,8 @@ class KotlinGenerateEqualsAndHashcodeAction : KotlinGenerateMemberActionBase(2) .apply { - addIfNotNull(generateEquals(project, info)) - addIfNotNull(generateHashCode(project, info)) + addIfNotNull(generateEquals(project, info, targetClass)) + addIfNotNull(generateHashCode(project, info, targetClass)) } val anchor = with(targetClass.declarations) { lastIsInstanceOrNull() ?: lastOrNull() } return insertMembersAfter(editor, targetClass, prototypes, anchor) diff --git a/idea/testData/codeInsight/generate/equalsWithHashCode/multipleVarsCommon.kt b/idea/testData/codeInsight/generate/equalsWithHashCode/multipleVarsCommon.kt new file mode 100644 index 00000000000..c1716aeb07e --- /dev/null +++ b/idea/testData/codeInsight/generate/equalsWithHashCode/multipleVarsCommon.kt @@ -0,0 +1,8 @@ +// PLATFORM: Common +class A(val n: Int, val s: String) { + val f: Float = 1.0f + + fun foo() { + + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/generate/equalsWithHashCode/multipleVarsCommon.kt.after b/idea/testData/codeInsight/generate/equalsWithHashCode/multipleVarsCommon.kt.after new file mode 100644 index 00000000000..5c3b2292543 --- /dev/null +++ b/idea/testData/codeInsight/generate/equalsWithHashCode/multipleVarsCommon.kt.after @@ -0,0 +1,28 @@ +// PLATFORM: Common +class A(val n: Int, val s: String) { + val f: Float = 1.0f + + fun foo() { + + } + + 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 + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/generate/equalsWithHashCode/multipleVarsJS.kt b/idea/testData/codeInsight/generate/equalsWithHashCode/multipleVarsJS.kt new file mode 100644 index 00000000000..71b19bf6e47 --- /dev/null +++ b/idea/testData/codeInsight/generate/equalsWithHashCode/multipleVarsJS.kt @@ -0,0 +1,8 @@ +// PLATFORM: JavaScript +class A(val n: Int, val s: String) { + val f: Float = 1.0f + + fun foo() { + + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/generate/equalsWithHashCode/multipleVarsJS.kt.after b/idea/testData/codeInsight/generate/equalsWithHashCode/multipleVarsJS.kt.after new file mode 100644 index 00000000000..76e1d350814 --- /dev/null +++ b/idea/testData/codeInsight/generate/equalsWithHashCode/multipleVarsJS.kt.after @@ -0,0 +1,28 @@ +// PLATFORM: JavaScript +class A(val n: Int, val s: String) { + val f: Float = 1.0f + + fun foo() { + + } + + 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 + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/generate/equalsWithHashCode/noVarsCommon.kt b/idea/testData/codeInsight/generate/equalsWithHashCode/noVarsCommon.kt new file mode 100644 index 00000000000..d96465e531b --- /dev/null +++ b/idea/testData/codeInsight/generate/equalsWithHashCode/noVarsCommon.kt @@ -0,0 +1,6 @@ +// PLATFORM: Common +class A { + fun foo() { + + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/generate/equalsWithHashCode/noVarsCommon.kt.after b/idea/testData/codeInsight/generate/equalsWithHashCode/noVarsCommon.kt.after new file mode 100644 index 00000000000..92cacc96628 --- /dev/null +++ b/idea/testData/codeInsight/generate/equalsWithHashCode/noVarsCommon.kt.after @@ -0,0 +1,16 @@ +// PLATFORM: Common +class A { + fun foo() { + + } + + 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() + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/generate/equalsWithHashCode/noVarsJS.kt b/idea/testData/codeInsight/generate/equalsWithHashCode/noVarsJS.kt new file mode 100644 index 00000000000..93b5032e161 --- /dev/null +++ b/idea/testData/codeInsight/generate/equalsWithHashCode/noVarsJS.kt @@ -0,0 +1,6 @@ +// PLATFORM: JavaScript +class A { + fun foo() { + + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/generate/equalsWithHashCode/noVarsJS.kt.after b/idea/testData/codeInsight/generate/equalsWithHashCode/noVarsJS.kt.after new file mode 100644 index 00000000000..3372a58291d --- /dev/null +++ b/idea/testData/codeInsight/generate/equalsWithHashCode/noVarsJS.kt.after @@ -0,0 +1,16 @@ +// PLATFORM: JavaScript +class A { + fun foo() { + + } + + 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() + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/generate/AbstractCodeInsightActionTest.kt b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/generate/AbstractCodeInsightActionTest.kt index 8485f533612..f15316f675b 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/generate/AbstractCodeInsightActionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/generate/AbstractCodeInsightActionTest.kt @@ -28,6 +28,11 @@ import junit.framework.TestCase import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase 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.KotlinTestUtils import java.io.File @@ -61,6 +66,8 @@ abstract class AbstractCodeInsightActionTest : KotlinLightCodeInsightFixtureTest val conflictFile = File("$path.messages") val afterFile = File("$path.after") + var mainPsiFile: KtFile? = null + try { ConfigLibraryUtil.configureLibrariesByDirective(myModule, PlatformTestUtil.getCommunityPath(), fileText) @@ -76,7 +83,18 @@ abstract class AbstractCodeInsightActionTest : KotlinLightCodeInsightFixtureTest myFixture.configureByFile(File(rootDir, it).path.replace(File.separator, "/")) } 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) @@ -103,6 +121,7 @@ abstract class AbstractCodeInsightActionTest : KotlinLightCodeInsightFixtureTest KotlinTestUtils.assertEqualsToFile(conflictFile, e.message!!) } finally { + mainPsiFile?.targetPlatform = null ConfigLibraryUtil.unconfigureLibrariesByDirective(myModule, fileText) } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/generate/GenerateHashCodeAndEqualsActionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/generate/GenerateHashCodeAndEqualsActionTestGenerated.java index 911973bfcc1..b363a1f91e9 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/generate/GenerateHashCodeAndEqualsActionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/generate/GenerateHashCodeAndEqualsActionTestGenerated.java @@ -108,6 +108,18 @@ public class GenerateHashCodeAndEqualsActionTestGenerated extends AbstractGenera 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") public void testMultipleVarsNullable() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/multipleVarsNullable.kt"); @@ -132,6 +144,18 @@ public class GenerateHashCodeAndEqualsActionTestGenerated extends AbstractGenera 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") public void testNoVarsWithSuperClass() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/noVarsWithSuperClass.kt");