diff --git a/plugins/kotlin/code-insight/impl-base/src/org/jetbrains/kotlin/idea/codeinsights/impl/base/intentions/GenerateEqualsAndHashCodeUtils.kt b/plugins/kotlin/code-insight/impl-base/src/org/jetbrains/kotlin/idea/codeinsights/impl/base/intentions/GenerateEqualsAndHashCodeUtils.kt index ba2877677bcd..ba1ef2d2577d 100644 --- a/plugins/kotlin/code-insight/impl-base/src/org/jetbrains/kotlin/idea/codeinsights/impl/base/intentions/GenerateEqualsAndHashCodeUtils.kt +++ b/plugins/kotlin/code-insight/impl-base/src/org/jetbrains/kotlin/idea/codeinsights/impl/base/intentions/GenerateEqualsAndHashCodeUtils.kt @@ -7,6 +7,7 @@ import org.jetbrains.kotlin.analysis.api.KaSession import org.jetbrains.kotlin.analysis.api.analyze import org.jetbrains.kotlin.analysis.api.symbols.* import org.jetbrains.kotlin.analysis.api.types.KaType +import org.jetbrains.kotlin.analysis.api.types.symbol import org.jetbrains.kotlin.builtins.StandardNames import org.jetbrains.kotlin.config.ApiVersion import org.jetbrains.kotlin.config.LanguageFeature @@ -118,7 +119,7 @@ fun generateEqualsHeaderAndBodyTexts(targetClass: KtClass): Pair append('\n') variablesForEquals.forEach { - val variableType = it.expressionType ?: return@forEach + val variableType = it.returnType val isNullableType = variableType.isMarkedNullable val isArray = variableType.isArrayOrPrimitiveArray val canUseArrayContentFunctions = targetClass.canUseArrayContentFunctions() @@ -229,9 +230,26 @@ fun generateHashCodeHeaderAndBodyTexts(targetClass: KtClass): Pair - (callableSymbol as? KaNamedFunctionSymbol)?.let { matchesEqualsMethodSignature(it) } == true - } + findMethodInMemberScopeOrInAny(classSymbol, EQUALS) { matchesEqualsMethodSignature(it) } + +context(KaSession) +private fun findHashCodeMethodForClass(classSymbol: KaClassSymbol): KaCallableSymbol? = + findMethodInMemberScopeOrInAny(classSymbol, HASH_CODE) { matchesHashCodeMethodSignature(it) } + +context(KaSession) +private fun findMethodInMemberScopeOrInAny( + 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 anySuperClassSymbol = classSymbol.superTypes.find { it.isAnyType }?.symbol as? KaClassSymbol ?: return null + return anySuperClassSymbol.memberScope.callables(methodName).singleOrNull() +} /** * Finds methods whose name is [methodName] not only from the class [classSymbol] but also its parent classes, @@ -242,12 +260,6 @@ private fun findMethod( classSymbol: KaClassSymbol, methodName: Name, condition: (KaCallableSymbol) -> Boolean ): KaCallableSymbol? = classSymbol.memberScope.callables(methodName).filter(condition).singleOrNull() -context(KaSession) -private fun findHashCodeMethodForClass(classSymbol: KaClassSymbol): KaCallableSymbol? = - findMethod(classSymbol, HASH_CODE) { callableSymbol -> - (callableSymbol as? KaNamedFunctionSymbol)?.let { matchesHashCodeMethodSignature(it) } == true - } - /** * A function to generate the "not equals" comparison between the class of `this` and the class of the parameter. */ diff --git a/plugins/kotlin/code-insight/inspections-k2/resources/kotlin.code-insight.inspections.k2.xml b/plugins/kotlin/code-insight/inspections-k2/resources/kotlin.code-insight.inspections.k2.xml index c781cae5979d..0a46f8c8867c 100644 --- a/plugins/kotlin/code-insight/inspections-k2/resources/kotlin.code-insight.inspections.k2.xml +++ b/plugins/kotlin/code-insight/inspections-k2/resources/kotlin.code-insight.inspections.k2.xml @@ -415,5 +415,14 @@ language="kotlin" editorAttributes="NOT_USED_ELEMENT_ATTRIBUTES" key="inspection.can.be.parameter.display.name" bundle="messages.KotlinBundle"/> + + diff --git a/plugins/kotlin/code-insight/inspections-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/inspections/declarations/ArrayInDataClassInspection.kt b/plugins/kotlin/code-insight/inspections-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/inspections/declarations/ArrayInDataClassInspection.kt new file mode 100644 index 000000000000..180d4eca4669 --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/inspections/declarations/ArrayInDataClassInspection.kt @@ -0,0 +1,142 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. + +package org.jetbrains.kotlin.idea.k2.codeinsight.inspections.declarations + +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.modcommand.ModPsiUpdater +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.analysis.api.KaSession +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.analysis.api.symbols.KaFunctionSymbol +import org.jetbrains.kotlin.idea.base.analysis.api.utils.shortenReferences +import org.jetbrains.kotlin.idea.base.resources.KotlinBundle +import org.jetbrains.kotlin.idea.codeinsight.api.applicable.inspections.KotlinApplicableInspectionBase +import org.jetbrains.kotlin.idea.codeinsight.api.applicable.inspections.KotlinModCommandQuickFix +import org.jetbrains.kotlin.idea.codeinsight.utils.isNullableAnyType +import org.jetbrains.kotlin.idea.codeinsights.impl.base.intentions.generateEqualsHeaderAndBodyTexts +import org.jetbrains.kotlin.idea.codeinsights.impl.base.intentions.generateHashCodeHeaderAndBodyTexts +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.containingClass +import org.jetbrains.kotlin.util.OperatorNameConventions +import org.jetbrains.kotlin.utils.addToStdlib.safeAs + +class ArrayInDataClassInspection : KotlinApplicableInspectionBase.Simple() { + class Context( + val equalsHeader: String?, + val equalsBody: String?, + val hashCodeHeader: String?, + val hashCodeBody: String?, + ) { + init { + check((equalsHeader == null) == (equalsBody == null)) + check((hashCodeHeader == null) == (hashCodeBody == null)) + } + } + + override fun getProblemDescription(element: KtParameter, context: Context): String { + return KotlinBundle.message("array.property.in.data.class.it.s.recommended.to.override.equals.hashcode") + } + + override fun createQuickFix(element: KtParameter, context: Context): KotlinModCommandQuickFix { + return object : KotlinModCommandQuickFix() { + override fun getFamilyName(): String = + KotlinBundle.message("generate.equals.and.hashcode.fix.text") + + override fun applyFix(project: Project, element: KtParameter, updater: ModPsiUpdater): Unit = with(context) { + val psiFactory = KtPsiFactory(project, markGenerated = true) + val containingClass = element.containingClass() ?: return + if (equalsHeader != null && equalsBody != null) { + generateFunctionDeclarationInClass(psiFactory, containingClass, equalsHeader, equalsBody) + } + if (hashCodeHeader != null && hashCodeBody != null) { + generateFunctionDeclarationInClass(psiFactory, containingClass, hashCodeHeader, hashCodeBody) + } + } + + private fun generateFunctionDeclarationInClass(factory: KtPsiFactory, containingClass: KtClass, header: String, body: String) { + val function = factory.createFunction(header) + shortenReferences(function) + if (body.isNotEmpty()) function.bodyExpression?.replace(factory.createBlock(body)) + containingClass.addDeclaration(function) + } + } + } + + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): KtVisitor<*, *> { + return classVisitor { klass -> + if (!klass.isData()) return@classVisitor + val constructor = klass.primaryConstructor ?: return@classVisitor + + for (parameter in constructor.valueParameters) { + visitTargetElement(parameter, holder, isOnTheFly) + } + } + } + + override fun isApplicableByPsi(element: KtParameter): Boolean { + return element.hasValOrVar() + } + + context(KaSession) + override fun prepareContext(element: KtParameter): Context? { + val parameterType = element.symbol.returnType + if (!parameterType.isArrayOrPrimitiveArray) return null + val containingClass = element.containingClass() ?: return null + + return when (checkOverriddenEqualsAndHashCode(containingClass)) { + EqualsHashCodeOverrides.HAS_EQUALS_AND_HASHCODE -> null + EqualsHashCodeOverrides.HAS_EQUALS -> { + val (hashCodeHeader, hashCodeBody) = generateHashCodeHeaderAndBodyTexts(containingClass) + Context(equalsHeader = null, equalsBody = null, hashCodeHeader, hashCodeBody) + } + EqualsHashCodeOverrides.HAS_HASHCODE -> { + val (equalsHeader, equalsBody) = generateEqualsHeaderAndBodyTexts(containingClass) + Context(equalsHeader, equalsBody, hashCodeHeader = null, hashCodeBody = null) + } + EqualsHashCodeOverrides.HAS_NONE -> { + val (equalsHeader, equalsBody) = generateEqualsHeaderAndBodyTexts(containingClass) + val (hashCodeHeader, hashCodeBody) = generateHashCodeHeaderAndBodyTexts(containingClass) + Context(equalsHeader, equalsBody, hashCodeHeader, hashCodeBody) + } + } + } + + private fun checkOverriddenEqualsAndHashCode(klass: KtClass): EqualsHashCodeOverrides { + var overriddenEquals = false + var overriddenHashCode = false + for (declaration in klass.declarations) { + if (declaration !is KtFunction) continue + if (!declaration.hasModifier(KtTokens.OVERRIDE_KEYWORD)) continue + if (declaration.nameAsName == OperatorNameConventions.EQUALS && declaration.valueParameters.size == 1) { + analyze(declaration) { + val parameterType = declaration.symbol.safeAs()?.valueParameters?.singleOrNull()?.returnType + if (parameterType?.isNullableAnyType() == true) { + overriddenEquals = true + } + } + } + if (declaration.nameAsName == OperatorNameConventions.HASH_CODE && declaration.valueParameters.size == 0) { + overriddenHashCode = true + } + } + + return EqualsHashCodeOverrides.of(overriddenEquals, overriddenHashCode) + } + + private enum class EqualsHashCodeOverrides { + HAS_EQUALS_AND_HASHCODE, + HAS_EQUALS, + HAS_HASHCODE, + HAS_NONE; + + companion object { + fun of(hasEquals: Boolean, hasHashCode: Boolean): EqualsHashCodeOverrides = when { + hasEquals && hasHashCode -> HAS_EQUALS_AND_HASHCODE + hasEquals -> HAS_EQUALS + hasHashCode -> HAS_HASHCODE + else -> HAS_NONE + } + } + } +} diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2InspectionTestGenerated.java b/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2InspectionTestGenerated.java index 4038d954fa62..a64c81d861e0 100644 --- a/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2InspectionTestGenerated.java +++ b/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2InspectionTestGenerated.java @@ -373,4 +373,27 @@ public abstract class K2InspectionTestGenerated extends AbstractK2InspectionTest } } } + + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../../idea/tests/testData/inspections/arrayInDataClass") + public abstract static class ArrayInDataClass extends AbstractK2InspectionTest { + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../../idea/tests/testData/inspections/arrayInDataClass/inspectionData") + public static class InspectionData extends AbstractK2InspectionTest { + @java.lang.Override + @org.jetbrains.annotations.NotNull + public final KotlinPluginMode getPluginMode() { + return KotlinPluginMode.K2; + } + + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + @TestMetadata("inspections.test") + public void testInspections_test() throws Exception { + runTest("../../../idea/tests/testData/inspections/arrayInDataClass/inspectionData/inspections.test"); + } + } + } } diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java b/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java index 5ed4e3899183..cec9c4b1cd62 100644 --- a/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java +++ b/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java @@ -6648,6 +6648,65 @@ public abstract class K2LocalInspectionTestGenerated extends AbstractK2LocalInsp } } + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../../idea/tests/testData/inspectionsLocal/arrayInDataClass") + public static class ArrayInDataClass extends AbstractK2LocalInspectionTest { + @java.lang.Override + @org.jetbrains.annotations.NotNull + public final KotlinPluginMode getPluginMode() { + return KotlinPluginMode.K2; + } + + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + @TestMetadata("genericArray.kt") + public void testGenericArray() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/arrayInDataClass/genericArray.kt"); + } + + @TestMetadata("intArray.kt") + public void testIntArray() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/arrayInDataClass/intArray.kt"); + } + + @TestMetadata("justEquals.kt") + public void testJustEquals() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/arrayInDataClass/justEquals.kt"); + } + + @TestMetadata("justHashCode.kt") + public void testJustHashCode() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/arrayInDataClass/justHashCode.kt"); + } + + @TestMetadata("mixedParameters.kt") + public void testMixedParameters() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/arrayInDataClass/mixedParameters.kt"); + } + + @TestMetadata("negativeEqualsHashCodeOverrides.kt") + public void testNegativeEqualsHashCodeOverrides() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/arrayInDataClass/negativeEqualsHashCodeOverrides.kt"); + } + + @TestMetadata("negativeNonArray.kt") + public void testNegativeNonArray() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/arrayInDataClass/negativeNonArray.kt"); + } + + @TestMetadata("nonOverrideEquals.kt") + public void testNonOverrideEquals() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/arrayInDataClass/nonOverrideEquals.kt"); + } + + @TestMetadata("nonOverrideHashCode.kt") + public void testNonOverrideHashCode() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/arrayInDataClass/nonOverrideHashCode.kt"); + } + } + @RunWith(JUnit3RunnerWithInners.class) @TestMetadata("testData/inspectionsLocal") public abstract static class InspectionsLocal extends AbstractK2LocalInspectionTest { diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 32ab7d743471..67a3cc476bf7 100644 --- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -82,9 +82,49 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); } - @TestMetadata("test.kt") - public void testTest() throws Exception { - runTest("testData/inspectionsLocal/arrayInDataClass/test.kt"); + @TestMetadata("genericArray.kt") + public void testGenericArray() throws Exception { + runTest("testData/inspectionsLocal/arrayInDataClass/genericArray.kt"); + } + + @TestMetadata("intArray.kt") + public void testIntArray() throws Exception { + runTest("testData/inspectionsLocal/arrayInDataClass/intArray.kt"); + } + + @TestMetadata("justEquals.kt") + public void testJustEquals() throws Exception { + runTest("testData/inspectionsLocal/arrayInDataClass/justEquals.kt"); + } + + @TestMetadata("justHashCode.kt") + public void testJustHashCode() throws Exception { + runTest("testData/inspectionsLocal/arrayInDataClass/justHashCode.kt"); + } + + @TestMetadata("mixedParameters.kt") + public void testMixedParameters() throws Exception { + runTest("testData/inspectionsLocal/arrayInDataClass/mixedParameters.kt"); + } + + @TestMetadata("negativeEqualsHashCodeOverrides.kt") + public void testNegativeEqualsHashCodeOverrides() throws Exception { + runTest("testData/inspectionsLocal/arrayInDataClass/negativeEqualsHashCodeOverrides.kt"); + } + + @TestMetadata("negativeNonArray.kt") + public void testNegativeNonArray() throws Exception { + runTest("testData/inspectionsLocal/arrayInDataClass/negativeNonArray.kt"); + } + + @TestMetadata("nonOverrideEquals.kt") + public void testNonOverrideEquals() throws Exception { + runTest("testData/inspectionsLocal/arrayInDataClass/nonOverrideEquals.kt"); + } + + @TestMetadata("nonOverrideHashCode.kt") + public void testNonOverrideHashCode() throws Exception { + runTest("testData/inspectionsLocal/arrayInDataClass/nonOverrideHashCode.kt"); } } diff --git a/plugins/kotlin/idea/tests/testData/inspections/arrayInDataClass/inspectionData/inspections.test b/plugins/kotlin/idea/tests/testData/inspections/arrayInDataClass/inspectionData/inspections.test index 7ec96680af7b..156c2284b06d 100644 --- a/plugins/kotlin/idea/tests/testData/inspections/arrayInDataClass/inspectionData/inspections.test +++ b/plugins/kotlin/idea/tests/testData/inspections/arrayInDataClass/inspectionData/inspections.test @@ -1 +1,2 @@ -// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.ArrayInDataClassInspection \ No newline at end of file +// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.ArrayInDataClassInspection +// K2_INSPECTION_CLASS: org.jetbrains.kotlin.idea.k2.codeinsight.inspections.declarations.ArrayInDataClassInspection \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/.k2Inspection b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/.k2Inspection new file mode 100644 index 000000000000..2c84c584efd2 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/.k2Inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.k2.codeinsight.inspections.declarations.ArrayInDataClassInspection diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/genericArray.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/genericArray.kt new file mode 100644 index 000000000000..e5c28c2a092e --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/genericArray.kt @@ -0,0 +1,3 @@ +// WITH_STDLIB + +data class A(val a: Array) diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/genericArray.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/genericArray.kt.after new file mode 100644 index 000000000000..658d2b51d8f6 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/genericArray.kt.after @@ -0,0 +1,18 @@ +// WITH_STDLIB + +data class A(val a: Array) { + 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() + } +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/intArray.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/intArray.kt new file mode 100644 index 000000000000..bab0705c46a5 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/intArray.kt @@ -0,0 +1,3 @@ +// WITH_STDLIB + +data class A(val a: IntArray) diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/test.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/intArray.kt.after similarity index 99% rename from plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/test.kt.after rename to plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/intArray.kt.after index 57cd035a4791..286d5bbd8dcc 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/test.kt.after +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/intArray.kt.after @@ -15,4 +15,4 @@ data class A(val a: IntArray) { override fun hashCode(): Int { return a.contentHashCode() } -} \ No newline at end of file +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/justEquals.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/justEquals.kt new file mode 100644 index 000000000000..d862d4945d6a --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/justEquals.kt @@ -0,0 +1,14 @@ +// WITH_STDLIB + +data class A(val a: IntArray) { + 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 + } +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/justEquals.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/justEquals.kt.after new file mode 100644 index 000000000000..286d5bbd8dcc --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/justEquals.kt.after @@ -0,0 +1,18 @@ +// WITH_STDLIB + +data class A(val a: IntArray) { + 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() + } +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/justHashCode.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/justHashCode.kt new file mode 100644 index 000000000000..efe00850f660 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/justHashCode.kt @@ -0,0 +1,7 @@ +// WITH_STDLIB + +data class A(val a: IntArray) { + override fun hashCode(): Int { + return a.contentHashCode() + } +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/justHashCode.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/justHashCode.kt.after new file mode 100644 index 000000000000..113920f4ecbf --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/justHashCode.kt.after @@ -0,0 +1,18 @@ +// WITH_STDLIB + +data class A(val a: IntArray) { + override fun hashCode(): Int { + return a.contentHashCode() + } + + 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 + } +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/mixedParameters.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/mixedParameters.kt new file mode 100644 index 000000000000..fdeb5506f68d --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/mixedParameters.kt @@ -0,0 +1,11 @@ +// WITH_STDLIB + +class MyClass + +data class A( + val a: IntArray, + val b: Array, + val c: String, + val d: Int, + val e: MyClass, +) diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/mixedParameters.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/mixedParameters.kt.after new file mode 100644 index 000000000000..0a14b34041fe --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/mixedParameters.kt.after @@ -0,0 +1,35 @@ +// WITH_STDLIB + +class MyClass + +data class A( + val a: IntArray, + val b: Array, + val c: String, + val d: Int, + val e: MyClass, +) { + 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 + 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() + result = 31 * result + b.contentHashCode() + result = 31 * result + c.hashCode() + result = 31 * result + d + result = 31 * result + e.hashCode() + return result + } +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/negativeEqualsHashCodeOverrides.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/negativeEqualsHashCodeOverrides.kt new file mode 100644 index 000000000000..524ce98fa92f --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/negativeEqualsHashCodeOverrides.kt @@ -0,0 +1,19 @@ +// WITH_STDLIB +// PROBLEM: none + +data class A(val a: IntArray) { + 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() + } +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/negativeNonArray.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/negativeNonArray.kt new file mode 100644 index 000000000000..2fbccbb37522 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/negativeNonArray.kt @@ -0,0 +1,4 @@ +// WITH_STDLIB +// PROBLEM: none + +data class A(val str: String) diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/nonOverrideEquals.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/nonOverrideEquals.kt new file mode 100644 index 000000000000..858f73b865c0 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/nonOverrideEquals.kt @@ -0,0 +1,11 @@ +// WITH_STDLIB + +data class A(val a: IntArray) { + fun equals(other: Any?, excessive: Any?): Boolean { + return true + } + + override fun hashCode(): Int { + return a.contentHashCode() + } +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/nonOverrideEquals.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/nonOverrideEquals.kt.after new file mode 100644 index 000000000000..1585089c719f --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/nonOverrideEquals.kt.after @@ -0,0 +1,22 @@ +// WITH_STDLIB + +data class A(val a: IntArray) { + fun equals(other: Any?, excessive: Any?): Boolean { + return true + } + + override fun hashCode(): Int { + return a.contentHashCode() + } + + 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 + } +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/nonOverrideHashCode.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/nonOverrideHashCode.kt new file mode 100644 index 000000000000..9108b5ce324b --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/nonOverrideHashCode.kt @@ -0,0 +1,18 @@ +// WITH_STDLIB + +data class A(val a: IntArray) { + 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 + } + + fun hashCode(seed: Int): Int { + return 42 + } +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/nonOverrideHashCode.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/nonOverrideHashCode.kt.after new file mode 100644 index 000000000000..27baf265249b --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/nonOverrideHashCode.kt.after @@ -0,0 +1,22 @@ +// WITH_STDLIB + +data class A(val a: IntArray) { + 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 + } + + fun hashCode(seed: Int): Int { + return 42 + } + + override fun hashCode(): Int { + return a.contentHashCode() + } +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/test.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/test.kt deleted file mode 100644 index c09d851968f6..000000000000 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/arrayInDataClass/test.kt +++ /dev/null @@ -1,3 +0,0 @@ -// WITH_STDLIB - -data class A(val a: IntArray) \ No newline at end of file diff --git a/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2InspectionTests.kt b/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2InspectionTests.kt index afb98c66d17d..44aa8b098590 100644 --- a/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2InspectionTests.kt +++ b/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2InspectionTests.kt @@ -58,6 +58,7 @@ internal fun MutableTWorkspace.generateK2InspectionTests() { model("${idea}/inspectionsLocal/usePropertyAccessSyntax") model("${idea}/inspectionsLocal/redundantUnitReturnType") model("${idea}/inspectionsLocal/canBeParameter") + model("${idea}/inspectionsLocal/arrayInDataClass") model("code-insight/inspections-k2/tests/testData/inspectionsLocal", pattern = pattern) } /** @@ -79,6 +80,7 @@ internal fun MutableTWorkspace.generateK2InspectionTests() { model("${idea}/inspections/protectedInFinal", pattern = pattern) model("${idea}/intentions/convertToStringTemplate", pattern = pattern) model("${idea}/inspections/unusedSymbol", pattern = pattern) + model("${idea}/inspections/arrayInDataClass", pattern = pattern) } testClass {