[kotlin] KTIJ-31612 Generate equals() with primitive fields come first

Merge-request: IJ-MR-146523
Merged-by: Maksim Zuev <Maksim.Zuev@jetbrains.com>

GitOrigin-RevId: ed92f3474ee18e280297f1e6c935975496a36b8c
This commit is contained in:
Maksim Zuev
2024-10-11 18:17:04 +00:00
committed by intellij-monorepo-bot
parent 4855ba7c77
commit 96b8929d41
15 changed files with 111 additions and 41 deletions
@@ -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<KtNamedDeclaration>.sortedWithPrimitiveFirst(): List<KtNamedDeclaration> = sortedWith(object : Comparator<KtNamedDeclaration> {
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
}
}
@@ -217,7 +217,7 @@ class KotlinGenerateEqualsAndHashcodeAction : KotlinGenerateMemberActionBase<Kot
append('\n')
variablesForEquals.forEach {
variablesForEquals.sortedWithPrimitivesFirst().forEach {
val isNullable = TypeUtils.isNullableType(it.type)
val isArray = KotlinBuiltIns.isArrayOrPrimitiveArray(it.type)
val canUseArrayContentFunctions = targetClass.canUseArrayContentFunctions()
@@ -292,7 +292,8 @@ class KotlinGenerateEqualsAndHashcodeAction : KotlinGenerateMemberActionBase<Kot
val hashCodeFun = generateFunctionSkeleton(superHashCode, targetClass)
val builtins = superHashCode.builtIns
val propertyIterator = variablesForHashCode.iterator()
// Sort variables in `hashCode()` to preserve the same order as in `equals()`
val propertyIterator = variablesForEquals.sortedWithPrimitivesFirst().iterator()
val initialValue = when {
!builtins.isMemberOfAny(superHashCode) -> "super.hashCode()"
propertyIterator.hasNext() -> propertyIterator.next().genVariableHashCode(false)
@@ -326,3 +327,7 @@ class KotlinGenerateEqualsAndHashcodeAction : KotlinGenerateMemberActionBase<Kot
return insertMembersAfterAndReformat(editor, targetClass, prototypes, anchor)
}
}
private fun List<VariableDescriptor>.sortedWithPrimitivesFirst(): List<VariableDescriptor> = sortedBy {
!KotlinBuiltIns.isPrimitiveTypeOrNullablePrimitiveType(it.type)
}
@@ -11,17 +11,17 @@ class A(val n: IntArray, val s: Array<String>) {
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
}
}
@@ -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
<caret>
}
@@ -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
<caret>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
}
@@ -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
<caret>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
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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
}
}
@@ -11,6 +11,7 @@ class A(val n: IntArray?, val s: Array<String>?) {
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<String>?) {
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
}
}
@@ -11,17 +11,17 @@ class A(val n: IntArray?, val s: Array<String>?) {
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
}
}
@@ -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
}