From 96b8929d4141dca694acf76629fa27c07e0bd999 Mon Sep 17 00:00:00 2001 From: Maksim Zuev Date: Fri, 11 Oct 2024 18:17:04 +0000 Subject: [PATCH] [kotlin] KTIJ-31612 Generate equals() with primitive fields come first Merge-request: IJ-MR-146523 Merged-by: Maksim Zuev GitOrigin-RevId: ed92f3474ee18e280297f1e6c935975496a36b8c --- .../GenerateEqualsAndHashCodeUtils.kt | 31 ++++++++++++++-- .../KotlinGenerateEqualsAndHashcodeAction.kt | 9 +++-- .../equalsWithHashCode/arrays.kt.after | 6 ++-- .../equalsWithHashCode/customAccessors.kt | 7 ++-- .../customAccessors.kt.after | 20 ++++++----- .../customAccessors.kt.k2.after | 35 +++++++++++++++++++ .../equalsWithHashCode/keepQuotes.kt.after | 6 ++-- .../equalsWithHashCode/multipleVars.kt.after | 4 +-- .../multipleVarsCommon.kt.after | 4 +-- .../multipleVarsJS.kt.after | 4 +-- .../multipleVarsNullable.kt.after | 4 +-- .../multipleVarsWithSuperClass.kt.after | 4 +-- .../nullableArrays.kt.after | 6 ++-- .../nullableArrays.kt.k2.after | 6 ++-- .../arrayInDataClass/mixedParameters.kt.after | 6 ++-- 15 files changed, 111 insertions(+), 41 deletions(-) create mode 100644 plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/customAccessors.kt.k2.after diff --git a/plugins/kotlin/code-insight/kotlin.code-insight.k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/generate/GenerateEqualsAndHashCodeUtils.kt b/plugins/kotlin/code-insight/kotlin.code-insight.k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/generate/GenerateEqualsAndHashCodeUtils.kt index eef350d7e687..e496e2a42943 100644 --- a/plugins/kotlin/code-insight/kotlin.code-insight.k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/generate/GenerateEqualsAndHashCodeUtils.kt +++ b/plugins/kotlin/code-insight/kotlin.code-insight.k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/generate/GenerateEqualsAndHashCodeUtils.kt @@ -14,6 +14,7 @@ import org.jetbrains.kotlin.analysis.api.symbols.KaNamedFunctionSymbol import org.jetbrains.kotlin.analysis.api.symbols.KaPropertySymbol import org.jetbrains.kotlin.analysis.api.symbols.KaSymbolModality import org.jetbrains.kotlin.analysis.api.symbols.KaSymbolOrigin +import org.jetbrains.kotlin.analysis.api.symbols.KaValueParameterSymbol import org.jetbrains.kotlin.analysis.api.types.symbol import org.jetbrains.kotlin.idea.base.resources.KotlinBundle import org.jetbrains.kotlin.idea.codeinsight.utils.isNonNullableBooleanType @@ -164,8 +165,9 @@ object GenerateEqualsAndHashCodeUtils { contextMap[SUPER_HAS_EQUALS] = equalsFunction != null && (equalsFunction.containingSymbol as? KaClassSymbol)?.classId != StandardClassIds.Any contextMap[CHECK_PARAMETER_WITH_INSTANCEOF] = CodeInsightSettings.getInstance().USE_INSTANCEOF_ON_EQUALS_PARAMETER + val sortedVariables = info.variablesForEquals.sortedWithPrimitiveFirst() val methodText = VelocityGeneratorHelper - .velocityGenerateCode(klass, info.variablesForEquals, contextMap, + .velocityGenerateCode(klass, sortedVariables, contextMap, KotlinEqualsHashCodeTemplatesManager.getInstance().defaultEqualsTemplate.template, false) ?: return null @@ -185,8 +187,10 @@ object GenerateEqualsAndHashCodeUtils { val hashCodeFunction = findHashCodeMethodForClass(klass.symbol as KaClassSymbol) contextMap[SUPER_HAS_HASHCODE] = hashCodeFunction != null && (hashCodeFunction.containingSymbol as? KaClassSymbol)?.classId != StandardClassIds.Any + // Sort variables in `hashCode()` to preserve the same order as in `equals()` + val sortedVariables = info.variablesForHashCode.sortedWithPrimitiveFirst() val methodText = VelocityGeneratorHelper - .velocityGenerateCode(klass, info.variablesForHashCode, + .velocityGenerateCode(klass, sortedVariables, contextMap, KotlinEqualsHashCodeTemplatesManager.getInstance().defaultHashcodeTemplate.template, false) ?: return null @@ -266,4 +270,27 @@ object GenerateEqualsAndHashCodeUtils { ) == Messages.YES } +} + +context(KaSession) +private fun List.sortedWithPrimitiveFirst(): List = sortedWith(object : Comparator { + override fun compare(o1: KtNamedDeclaration, o2: KtNamedDeclaration): Int { + val isBacking1 = o1.propertyHasBackingField() + val isBacking2 = o2.propertyHasBackingField() + val fieldCompare = -isBacking1.compareTo(isBacking2) + if (fieldCompare != 0) return fieldCompare + return -o1.returnType.isPrimitive.compareTo(o2.returnType.isPrimitive) + } +}) + +context(KaSession) +private fun KtNamedDeclaration.propertyHasBackingField(): Boolean { + val symbol = symbol + return when (symbol) { + is KaPropertySymbol -> symbol.hasBackingField + is KaValueParameterSymbol -> { + symbol.generatedPrimaryConstructorProperty?.hasBackingField == true + } + else -> false + } } \ No newline at end of file diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateEqualsAndHashcodeAction.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateEqualsAndHashcodeAction.kt index d730abc7ed68..a9b6560c5d01 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateEqualsAndHashcodeAction.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateEqualsAndHashcodeAction.kt @@ -217,7 +217,7 @@ class KotlinGenerateEqualsAndHashcodeAction : KotlinGenerateMemberActionBase "super.hashCode()" propertyIterator.hasNext() -> propertyIterator.next().genVariableHashCode(false) @@ -326,3 +327,7 @@ class KotlinGenerateEqualsAndHashcodeAction : KotlinGenerateMemberActionBase.sortedWithPrimitivesFirst(): List = sortedBy { + !KotlinBuiltIns.isPrimitiveTypeOrNullablePrimitiveType(it.type) +} diff --git a/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/arrays.kt.after b/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/arrays.kt.after index f4cf115647e7..01549f856952 100644 --- a/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/arrays.kt.after +++ b/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/arrays.kt.after @@ -11,17 +11,17 @@ class A(val n: IntArray, val s: Array) { other as A + if (f != other.f) return false if (!n.contentEquals(other.n)) return false if (!s.contentEquals(other.s)) return false - if (f != other.f) return false return true } override fun hashCode(): Int { - var result = n.contentHashCode() + var result = f.hashCode() + result = 31 * result + n.contentHashCode() result = 31 * result + s.contentHashCode() - result = 31 * result + f.hashCode() return result } } \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/customAccessors.kt b/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/customAccessors.kt index 458a61fda6d5..7de4b0e10baa 100644 --- a/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/customAccessors.kt +++ b/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/customAccessors.kt @@ -1,11 +1,12 @@ class Test { + val age by lazy { 15 + 10 } + val color: String + get() = "Purple" var serial: String = "" set(value) { field = value.toUpperCase() } var name: String = "" - val age by lazy { 15 + 10 } - val color: String - get() = "Purple" + var id = 42 } \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/customAccessors.kt.after b/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/customAccessors.kt.after index 387399d4bcea..9635c3befcd0 100644 --- a/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/customAccessors.kt.after +++ b/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/customAccessors.kt.after @@ -1,32 +1,34 @@ class Test { + val age by lazy { 15 + 10 } + val color: String + get() = "Purple" var serial: String = "" set(value) { field = value.toUpperCase() } var name: String = "" - val age by lazy { 15 + 10 } - val color: String - get() = "Purple" - + var id = 42 override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as Test + if (age != other.age) return false + if (id != other.id) return false + if (color != other.color) return false if (serial != other.serial) return false if (name != other.name) return false - if (age != other.age) return false - if (color != other.color) return false return true } override fun hashCode(): Int { - var result = serial.hashCode() - result = 31 * result + name.hashCode() - result = 31 * result + age + var result = age + result = 31 * result + id result = 31 * result + color.hashCode() + result = 31 * result + serial.hashCode() + result = 31 * result + name.hashCode() return result } diff --git a/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/customAccessors.kt.k2.after b/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/customAccessors.kt.k2.after new file mode 100644 index 000000000000..54ffe03a2f62 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/customAccessors.kt.k2.after @@ -0,0 +1,35 @@ +class Test { + val age by lazy { 15 + 10 } + val color: String + get() = "Purple" + var serial: String = "" + set(value) { + field = value.toUpperCase() + } + var name: String = "" + var id = 42 + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Test + + if (id != other.id) return false + if (serial != other.serial) return false + if (name != other.name) return false + if (age != other.age) return false + if (color != other.color) return false + + return true + } + + override fun hashCode(): Int { + var result = id + result = 31 * result + serial.hashCode() + result = 31 * result + name.hashCode() + result = 31 * result + age + result = 31 * result + color.hashCode() + return result + } + +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/keepQuotes.kt.after b/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/keepQuotes.kt.after index 84718fc80c77..a1a966957920 100644 --- a/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/keepQuotes.kt.after +++ b/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/keepQuotes.kt.after @@ -5,17 +5,17 @@ class Example(val `in`: String, val `cl ass`: String, val `valid`: Boolean) { other as Example + if (`valid` != other.`valid`) return false if (`in` != other.`in`) return false if (`cl ass` != other.`cl ass`) return false - if (`valid` != other.`valid`) return false return true } override fun hashCode(): Int { - var result = `in`.hashCode() + var result = `valid`.hashCode() + result = 31 * result + `in`.hashCode() result = 31 * result + `cl ass`.hashCode() - result = 31 * result + `valid`.hashCode() return result } } \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/multipleVars.kt.after b/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/multipleVars.kt.after index 312ecc38549b..9a826fa137b1 100644 --- a/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/multipleVars.kt.after +++ b/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/multipleVars.kt.after @@ -12,16 +12,16 @@ class A(val n: Int, val s: String) { other as A if (n != other.n) return false - if (s != other.s) return false if (f != other.f) return false + if (s != other.s) return false return true } override fun hashCode(): Int { var result = n - result = 31 * result + s.hashCode() result = 31 * result + f.hashCode() + result = 31 * result + s.hashCode() return result } } \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/multipleVarsCommon.kt.after b/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/multipleVarsCommon.kt.after index f9875704b25f..087b0b6881ec 100644 --- a/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/multipleVarsCommon.kt.after +++ b/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/multipleVarsCommon.kt.after @@ -13,16 +13,16 @@ class A(val n: Int, val s: String) { other as A if (n != other.n) return false - if (s != other.s) return false if (f != other.f) return false + if (s != other.s) return false return true } override fun hashCode(): Int { var result = n - result = 31 * result + s.hashCode() result = 31 * result + f.hashCode() + result = 31 * result + s.hashCode() return result } } \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/multipleVarsJS.kt.after b/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/multipleVarsJS.kt.after index 5f4cb0bfe34c..f3e567488ef3 100644 --- a/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/multipleVarsJS.kt.after +++ b/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/multipleVarsJS.kt.after @@ -13,16 +13,16 @@ class A(val n: Int, val s: String) { other as A if (n != other.n) return false - if (s != other.s) return false if (f != other.f) return false + if (s != other.s) return false return true } override fun hashCode(): Int { var result = n - result = 31 * result + s.hashCode() result = 31 * result + f.hashCode() + result = 31 * result + s.hashCode() return result } } \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/multipleVarsNullable.kt.after b/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/multipleVarsNullable.kt.after index d6cb1fd147ed..902760602454 100644 --- a/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/multipleVarsNullable.kt.after +++ b/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/multipleVarsNullable.kt.after @@ -12,16 +12,16 @@ class A(val n: Int?, val s: String) { other as A if (n != other.n) return false - if (s != other.s) return false if (f != other.f) return false + if (s != other.s) return false return true } override fun hashCode(): Int { var result = n ?: 0 - result = 31 * result + s.hashCode() result = 31 * result + (f?.hashCode() ?: 0) + result = 31 * result + s.hashCode() return result } } \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/multipleVarsWithSuperClass.kt.after b/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/multipleVarsWithSuperClass.kt.after index 695ec214503a..513f707575f7 100644 --- a/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/multipleVarsWithSuperClass.kt.after +++ b/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/multipleVarsWithSuperClass.kt.after @@ -18,8 +18,8 @@ class A(val n: Int, val s: String) : X() { other as A if (n != other.n) return false - if (s != other.s) return false if (f != other.f) return false + if (s != other.s) return false return true } @@ -27,8 +27,8 @@ class A(val n: Int, val s: String) : X() { override fun hashCode(): Int { var result = super.hashCode() result = 31 * result + n - result = 31 * result + s.hashCode() result = 31 * result + f.hashCode() + result = 31 * result + s.hashCode() return result } } \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/nullableArrays.kt.after b/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/nullableArrays.kt.after index 751332e33d2a..c7e62d6bcb86 100644 --- a/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/nullableArrays.kt.after +++ b/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/nullableArrays.kt.after @@ -11,6 +11,7 @@ class A(val n: IntArray?, val s: Array?) { other as A + if (f != other.f) return false if (n != null) { if (other.n == null) return false if (!n.contentEquals(other.n)) return false @@ -19,15 +20,14 @@ class A(val n: IntArray?, val s: Array?) { if (other.s == null) return false if (!s.contentEquals(other.s)) return false } else if (other.s != null) return false - if (f != other.f) return false return true } override fun hashCode(): Int { - var result = n?.contentHashCode() ?: 0 + var result = f.hashCode() + result = 31 * result + (n?.contentHashCode() ?: 0) result = 31 * result + (s?.contentHashCode() ?: 0) - result = 31 * result + f.hashCode() return result } } \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/nullableArrays.kt.k2.after b/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/nullableArrays.kt.k2.after index 0e9aef5dfc72..c41d1b39aa9b 100644 --- a/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/nullableArrays.kt.k2.after +++ b/plugins/kotlin/idea/tests/testData/codeInsight/generate/equalsWithHashCode/nullableArrays.kt.k2.after @@ -11,17 +11,17 @@ class A(val n: IntArray?, val s: Array?) { other as A + if (f != other.f) return false if (!n.contentEquals(other.n)) return false if (!s.contentEquals(other.s)) return false - if (f != other.f) return false return true } override fun hashCode(): Int { - var result = n?.contentHashCode() ?: 0 + var result = f.hashCode() + result = 31 * result + (n?.contentHashCode() ?: 0) result = 31 * result + (s?.contentHashCode() ?: 0) - result = 31 * result + f.hashCode() return result } } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/mixedParameters.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/mixedParameters.kt.after index 0a14b34041fe..1e40d6281637 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/mixedParameters.kt.after +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/mixedParameters.kt.after @@ -15,20 +15,20 @@ data class A( other as A + if (d != other.d) return false if (!a.contentEquals(other.a)) return false if (!b.contentEquals(other.b)) return false if (c != other.c) return false - if (d != other.d) return false if (e != other.e) return false return true } override fun hashCode(): Int { - var result = a.contentHashCode() + var result = d + result = 31 * result + a.contentHashCode() result = 31 * result + b.contentHashCode() result = 31 * result + c.hashCode() - result = 31 * result + d result = 31 * result + e.hashCode() return result }