[Kotlin] Consider supertypes in 'array in data class' quickfix

KTIJ-28464

GitOrigin-RevId: 35c20e8401be48af0cc166b38f7603b4fbf62f73
This commit is contained in:
Pavel Kirpichenkov
2024-07-11 11:41:35 +00:00
committed by intellij-monorepo-bot
parent cf4c3f5d8b
commit d710a02263
9 changed files with 205 additions and 11 deletions
@@ -231,25 +231,32 @@ object GenerateEqualsAndHashCodeUtils {
context(KaSession)
private fun findEqualsMethodForClass(classSymbol: KaClassSymbol): KaCallableSymbol? =
findMethodInMemberScopeOrInAny(classSymbol, EQUALS) { matchesEqualsMethodSignature(it) }
findNonGeneratedMethodInSelfOrSuperclass(classSymbol, EQUALS) { matchesEqualsMethodSignature(it) }
context(KaSession)
private fun findHashCodeMethodForClass(classSymbol: KaClassSymbol): KaCallableSymbol? =
findMethodInMemberScopeOrInAny(classSymbol, HASH_CODE) { matchesHashCodeMethodSignature(it) }
findNonGeneratedMethodInSelfOrSuperclass(classSymbol, HASH_CODE) { matchesHashCodeMethodSignature(it) }
/**
* Searches for a callable member symbol with the given [methodName] that matches the [signatureFilter].
* If the found symbol is generated, the search is done for one more time in the superclass' scope
*/
context(KaSession)
private fun findMethodInMemberScopeOrInAny(
private fun findNonGeneratedMethodInSelfOrSuperclass(
classSymbol: KaClassSymbol,
methodName: Name,
signatureFilter: (KaNamedFunctionSymbol) -> Boolean
): KaCallableSymbol? {
findMethod(classSymbol, methodName) { callableSymbol ->
if (callableSymbol !is KaNamedFunctionSymbol) return@findMethod false
signatureFilter(callableSymbol) && callableSymbol.origin != KaSymbolOrigin.SOURCE_MEMBER_GENERATED
}?.let { return it }
val methodSymbol = findMethod(classSymbol, methodName, signatureFilter)
val anySuperClassSymbol = classSymbol.superTypes.find { it.isAnyType }?.symbol as? KaClassSymbol ?: return null
return anySuperClassSymbol.memberScope.callables(methodName).singleOrNull()
// We are not interested in synthetic members of data classes here
// They won't be generated by the compiler after the explicit members are created (see the Kotlin Specification, 4.1.2)
if (methodSymbol?.origin != KaSymbolOrigin.SOURCE_MEMBER_GENERATED) return methodSymbol
// Instead, if a generated member was found, we search for a member again in its parent class' scope to find a relevant member
val directSuperclassSymbol = findExplicitSuperclassOrAny(classSymbol) ?: return null
return findMethod(directSuperclassSymbol, methodName, signatureFilter)
}
/**
@@ -258,8 +265,22 @@ object GenerateEqualsAndHashCodeUtils {
*/
context(KaSession)
private fun findMethod(
classSymbol: KaClassSymbol, methodName: Name, condition: (KaCallableSymbol) -> Boolean
): KaCallableSymbol? = classSymbol.memberScope.callables(methodName).filter(condition).singleOrNull()
classSymbol: KaClassSymbol, methodName: Name, condition: (KaNamedFunctionSymbol) -> Boolean
): KaCallableSymbol? = classSymbol.memberScope.callables(methodName).filter {
it is KaNamedFunctionSymbol && condition(it)
}.singleOrNull()
/**
* Searches for the direct superclass symbol of this [classSymbol] (ignoring interfaces).
* Because currently `kotlin.Any` is not listed in symbol's supertypes when all declared supertypes are interfaces,
* this case is handled separately.
*/
context(KaSession)
private fun findExplicitSuperclassOrAny(classSymbol: KaClassSymbol): KaClassSymbol? {
val supertypes = classSymbol.superTypes
return supertypes.map { it.symbol }.filterIsInstance<KaClassSymbol>().singleOrNull { it.classKind == KaClassKind.CLASS }
?: supertypes.first().allSupertypes.singleOrNull { it.isAnyType }?.symbol as? KaClassSymbol
}
/**
* A function to generate the "not equals" comparison between the class of `this` and the class of the parameter.
@@ -6661,11 +6661,26 @@ public abstract class K2LocalInspectionTestGenerated extends AbstractK2LocalInsp
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("blankSupertypes.kt")
public void testBlankSupertypes() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/arrayInDataClass/blankSupertypes.kt");
}
@TestMetadata("changedParameterNameInParent.kt")
public void testChangedParameterNameInParent() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/arrayInDataClass/changedParameterNameInParent.kt");
}
@TestMetadata("genericArray.kt")
public void testGenericArray() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/arrayInDataClass/genericArray.kt");
}
@TestMetadata("indirectOverride.kt")
public void testIndirectOverride() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/arrayInDataClass/indirectOverride.kt");
}
@TestMetadata("intArray.kt")
public void testIntArray() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/arrayInDataClass/intArray.kt");
@@ -82,11 +82,26 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("blankSupertypes.kt")
public void testBlankSupertypes() throws Exception {
runTest("testData/inspectionsLocal/arrayInDataClass/blankSupertypes.kt");
}
@TestMetadata("changedParameterNameInParent.kt")
public void testChangedParameterNameInParent() throws Exception {
runTest("testData/inspectionsLocal/arrayInDataClass/changedParameterNameInParent.kt");
}
@TestMetadata("genericArray.kt")
public void testGenericArray() throws Exception {
runTest("testData/inspectionsLocal/arrayInDataClass/genericArray.kt");
}
@TestMetadata("indirectOverride.kt")
public void testIndirectOverride() throws Exception {
runTest("testData/inspectionsLocal/arrayInDataClass/indirectOverride.kt");
}
@TestMetadata("intArray.kt")
public void testIntArray() throws Exception {
runTest("testData/inspectionsLocal/arrayInDataClass/intArray.kt");
@@ -0,0 +1,10 @@
// WITH_STDLIB
interface I1
interface I2
interface I3 : I2
open class C
open class D : C()
data class A(val <caret>a: IntArray): D(), I1, I3
@@ -0,0 +1,25 @@
// WITH_STDLIB
interface I1
interface I2
interface I3 : I2
open class C
open class D : C()
data class A(val a: IntArray): D(), I1, I3 {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as A
if (!a.contentEquals(other.a)) return false
return true
}
override fun hashCode(): Int {
return a.contentHashCode()
}
}
@@ -0,0 +1,26 @@
// WITH_STDLIB
data class A(val <caret>a: IntArray) : Parent("some", intArrayOf(1, 2, 3))
open class Parent(
val some: String,
val array: IntArray,
) {
override fun equals(renamed: Any?): Boolean {
if (this === renamed) return true
if (javaClass != renamed?.javaClass) return false
renamed as Parent
if (some != renamed.some) return false
if (!array.contentEquals(renamed.array)) return false
return true
}
override fun hashCode(): Int {
var result = some.hashCode()
result = 31 * result + array.contentHashCode()
return result
}
}
@@ -0,0 +1,44 @@
// WITH_STDLIB
data class A(val a: IntArray) : Parent("some", intArrayOf(1, 2, 3)) {
override fun equals(renamed: Any?): Boolean {
if (this === renamed) return true
if (javaClass != renamed?.javaClass) return false
if (!super.equals(renamed)) return false
renamed as A
if (!a.contentEquals(renamed.a)) return false
return true
}
override fun hashCode(): Int {
var result = super.hashCode()
result = 31 * result + a.contentHashCode()
return result
}
}
open class Parent(
val some: String,
val array: IntArray,
) {
override fun equals(renamed: Any?): Boolean {
if (this === renamed) return true
if (javaClass != renamed?.javaClass) return false
renamed as Parent
if (some != renamed.some) return false
if (!array.contentEquals(renamed.array)) return false
return true
}
override fun hashCode(): Int {
var result = some.hashCode()
result = 31 * result + array.contentHashCode()
return result
}
}
@@ -0,0 +1,11 @@
// WITH_STDLIB
open class GrandParent {
override fun equals(renamed: Any?): Boolean {
return super.equals(renamed)
}
}
open class Parent() : GrandParent()
data class A(val <caret>a: IntArray) : Parent()
@@ -0,0 +1,27 @@
// WITH_STDLIB
open class GrandParent {
override fun equals(renamed: Any?): Boolean {
return super.equals(renamed)
}
}
open class Parent() : GrandParent()
data class A(val a: IntArray) : Parent() {
override fun equals(renamed: Any?): Boolean {
if (this === renamed) return true
if (javaClass != renamed?.javaClass) return false
if (!super.equals(renamed)) return false
renamed as A
if (!a.contentEquals(renamed.a)) return false
return true
}
override fun hashCode(): Int {
return a.contentHashCode()
}
}