diff --git a/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/DataClassMethodGenerator.kt b/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/DataClassMethodGenerator.kt index ee69f0284a9..a0cf4fbcc6c 100644 --- a/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/DataClassMethodGenerator.kt +++ b/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/DataClassMethodGenerator.kt @@ -112,8 +112,7 @@ abstract class DataClassMethodGenerator(private val declaration: KtClassOrObject ): FunctionDescriptor? = classDescriptor.unsubstitutedMemberScope.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BACKEND) .singleOrNull { function -> - !function.kind.isReal && - function.modality != Modality.FINAL && + function.kind == CallableMemberDescriptor.Kind.SYNTHESIZED && areParametersOk(function.valueParameters) && function.returnType != null && isReturnTypeOk(function.returnType!!) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DataClassDescriptorResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DataClassDescriptorResolver.kt index 5163391d179..d1252d6dcea 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DataClassDescriptorResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DataClassDescriptorResolver.kt @@ -20,9 +20,16 @@ import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl +import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns +import org.jetbrains.kotlin.util.OperatorNameConventions object DataClassDescriptorResolver { + val EQUALS_METHOD_NAME = OperatorNameConventions.EQUALS + val HASH_CODE_METHOD_NAME = Name.identifier("hashCode") + val TO_STRING_METHOD_NAME = Name.identifier("toString") + val COPY_METHOD_NAME = Name.identifier("copy") private val COMPONENT_FUNCTION_NAME_PREFIX = "component" @@ -49,6 +56,36 @@ object DataClassDescriptorResolver { return true } + fun createEqualsFunctionDescriptor(classDescriptor: ClassDescriptor): SimpleFunctionDescriptor = + doCreateFunctionFromAny(classDescriptor, EQUALS_METHOD_NAME) + + fun createHashCodeFunctionDescriptor(classDescriptor: ClassDescriptor): SimpleFunctionDescriptor = + doCreateFunctionFromAny(classDescriptor, HASH_CODE_METHOD_NAME) + + fun createToStringFunctionDescriptor(classDescriptor: ClassDescriptor): SimpleFunctionDescriptor = + doCreateFunctionFromAny(classDescriptor, TO_STRING_METHOD_NAME) + + private fun doCreateFunctionFromAny(classDescriptor: ClassDescriptor, name: Name): SimpleFunctionDescriptor { + val functionDescriptor = SimpleFunctionDescriptorImpl.create( + classDescriptor, Annotations.EMPTY, name, CallableMemberDescriptor.Kind.SYNTHESIZED, classDescriptor.source + ) + + val functionFromAny = classDescriptor.builtIns.any.getMemberScope(emptyList()) + .getContributedFunctions(name, NoLookupLocation.FROM_BUILTINS).single() + + functionDescriptor.initialize( + null, + classDescriptor.thisAsReceiverParameter, + functionFromAny.typeParameters, + functionFromAny.valueParameters.map { it.copy(functionDescriptor, it.name, it.index) }, + functionFromAny.returnType, + Modality.OPEN, + Visibilities.PUBLIC + ) + + return functionDescriptor + } + fun createComponentFunctionDescriptor( parameterIndex: Int, property: PropertyDescriptor, diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/descriptors/LazyClassMemberScope.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/descriptors/LazyClassMemberScope.kt index 50dbe98318b..55d1553a0fc 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/descriptors/LazyClassMemberScope.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/descriptors/LazyClassMemberScope.kt @@ -16,7 +16,7 @@ package org.jetbrains.kotlin.resolve.lazy.descriptors -import com.google.common.collect.Lists +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.DELEGATION import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.FAKE_OVERRIDE @@ -129,16 +129,21 @@ open class LazyClassMemberScope( override fun getNonDeclaredFunctions(name: Name, result: MutableSet) { val location = NoLookupLocation.FOR_ALREADY_TRACKED - val fromSupertypes = Lists.newArrayList() + val fromSupertypes = arrayListOf() for (supertype in thisDescriptor.typeConstructor.supertypes) { fromSupertypes.addAll(supertype.memberScope.getContributedFunctions(name, location)) } result.addAll(generateDelegatingDescriptors(name, EXTRACT_FUNCTIONS, result)) - generateDataClassMethods(result, name, location) + generateDataClassMethods(result, name, location, fromSupertypes) generateFakeOverrides(name, fromSupertypes, result, SimpleFunctionDescriptor::class.java) } - private fun generateDataClassMethods(result: MutableCollection, name: Name, location: LookupLocation) { + private fun generateDataClassMethods( + result: MutableCollection, + name: Name, + location: LookupLocation, + fromSupertypes: List + ) { if (!thisDescriptor.isData) return val constructor = getPrimaryConstructor() ?: return @@ -177,6 +182,27 @@ open class LazyClassMemberScope( result.add(DataClassDescriptorResolver.createCopyFunctionDescriptor(constructor.valueParameters, thisDescriptor, trace)) } + + fun shouldAddFunctionFromAny(checkParameters: (FunctionDescriptor) -> Boolean): Boolean { + // Add 'equals', 'hashCode', 'toString' iff there is no such declared member AND there is no such final member in supertypes + return result.none(checkParameters) && + fromSupertypes.none { checkParameters(it) && it.modality == Modality.FINAL } + } + + if (name == DataClassDescriptorResolver.EQUALS_METHOD_NAME && shouldAddFunctionFromAny { function -> + val parameters = function.valueParameters + parameters.size == 1 && KotlinBuiltIns.isNullableAny(parameters.first().type) + }) { + result.add(DataClassDescriptorResolver.createEqualsFunctionDescriptor(thisDescriptor)) + } + + if (name == DataClassDescriptorResolver.HASH_CODE_METHOD_NAME && shouldAddFunctionFromAny { it.valueParameters.isEmpty() }) { + result.add(DataClassDescriptorResolver.createHashCodeFunctionDescriptor(thisDescriptor)) + } + + if (name == DataClassDescriptorResolver.TO_STRING_METHOD_NAME && shouldAddFunctionFromAny { it.valueParameters.isEmpty() }) { + result.add(DataClassDescriptorResolver.createToStringFunctionDescriptor(thisDescriptor)) + } } override fun getContributedVariables(name: Name, location: LookupLocation): Collection { diff --git a/compiler/testData/diagnostics/tests/dataClasses/finalMembersInBaseClass.kt b/compiler/testData/diagnostics/tests/dataClasses/finalMembersInBaseClass.kt new file mode 100644 index 00000000000..1969596d600 --- /dev/null +++ b/compiler/testData/diagnostics/tests/dataClasses/finalMembersInBaseClass.kt @@ -0,0 +1,16 @@ +abstract class Base { + final override fun equals(other: Any?) = false + final override fun hashCode() = 42 + + open override fun toString() = "OK" +} + +data class Data1(val field: String) : Base() + +interface AbstractAnyMembers { + abstract override fun equals(other: Any?): Boolean + abstract override fun hashCode(): Int + abstract override fun toString(): String +} + +data class Data2(val field: String): Base(), AbstractAnyMembers diff --git a/compiler/testData/diagnostics/tests/dataClasses/finalMembersInBaseClass.txt b/compiler/testData/diagnostics/tests/dataClasses/finalMembersInBaseClass.txt new file mode 100644 index 00000000000..f7fb9be524e --- /dev/null +++ b/compiler/testData/diagnostics/tests/dataClasses/finalMembersInBaseClass.txt @@ -0,0 +1,34 @@ +package + +public interface AbstractAnyMembers { + public abstract override /*1*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public abstract override /*1*/ fun hashCode(): kotlin.Int + public abstract override /*1*/ fun toString(): kotlin.String +} + +public abstract class Base { + public constructor Base() + public final override /*1*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ fun hashCode(): kotlin.Int + public open override /*1*/ fun toString(): kotlin.String +} + +public final data class Data1 : Base { + public constructor Data1(/*0*/ field: kotlin.String) + public final val field: kotlin.String + public final operator /*synthesized*/ fun component1(): kotlin.String + public final /*synthesized*/ fun copy(/*0*/ field: kotlin.String = ...): Data1 + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String +} + +public final data class Data2 : Base, AbstractAnyMembers { + public constructor Data2(/*0*/ field: kotlin.String) + public final val field: kotlin.String + public final operator /*synthesized*/ fun component1(): kotlin.String + public final /*synthesized*/ fun copy(/*0*/ field: kotlin.String = ...): Data2 + public final override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*2*/ /*synthesized*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/dataClasses/implementMethodsFromInterface.kt b/compiler/testData/diagnostics/tests/dataClasses/implementMethodsFromInterface.kt new file mode 100644 index 00000000000..e6aadc77bd6 --- /dev/null +++ b/compiler/testData/diagnostics/tests/dataClasses/implementMethodsFromInterface.kt @@ -0,0 +1,25 @@ +// KT-11306 ABSTRACT_MEMBER_NOT_IMPLEMENTED for data class should inheriting interfaces requiring equals(), hashCode(), or toString() + +interface Foo { + override fun equals(other: Any?): Boolean + override fun hashCode(): Int + override fun toString(): String +} + +data class FooImpl(val num: Int) : Foo + +data class FooImplSome(val num: Int) : Foo { + override fun hashCode() = 42 +} + +data class FooImplAll(val num: Int) : Foo { + override fun equals(other: Any?) = false + override fun hashCode() = 42 + override fun toString() = "OK" +} + + +data class WrongSignatures(val num: Int) : Foo { + override fun equals(other: WrongSignatures) = false + override fun hashCode(): Boolean = true +} diff --git a/compiler/testData/diagnostics/tests/dataClasses/implementMethodsFromInterface.txt b/compiler/testData/diagnostics/tests/dataClasses/implementMethodsFromInterface.txt new file mode 100644 index 00000000000..27c6529f832 --- /dev/null +++ b/compiler/testData/diagnostics/tests/dataClasses/implementMethodsFromInterface.txt @@ -0,0 +1,48 @@ +package + +public interface Foo { + public abstract override /*1*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public abstract override /*1*/ fun hashCode(): kotlin.Int + public abstract override /*1*/ fun toString(): kotlin.String +} + +public final data class FooImpl : Foo { + public constructor FooImpl(/*0*/ num: kotlin.Int) + public final val num: kotlin.Int + public final operator /*synthesized*/ fun component1(): kotlin.Int + public final /*synthesized*/ fun copy(/*0*/ num: kotlin.Int = ...): FooImpl + public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String +} + +public final data class FooImplAll : Foo { + public constructor FooImplAll(/*0*/ num: kotlin.Int) + public final val num: kotlin.Int + public final operator /*synthesized*/ fun component1(): kotlin.Int + public final /*synthesized*/ fun copy(/*0*/ num: kotlin.Int = ...): FooImplAll + public open override /*1*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ fun hashCode(): kotlin.Int + public open override /*1*/ fun toString(): kotlin.String +} + +public final data class FooImplSome : Foo { + public constructor FooImplSome(/*0*/ num: kotlin.Int) + public final val num: kotlin.Int + public final operator /*synthesized*/ fun component1(): kotlin.Int + public final /*synthesized*/ fun copy(/*0*/ num: kotlin.Int = ...): FooImplSome + public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String +} + +public final data class WrongSignatures : Foo { + public constructor WrongSignatures(/*0*/ num: kotlin.Int) + public final val num: kotlin.Int + public final operator /*synthesized*/ fun component1(): kotlin.Int + public final /*synthesized*/ fun copy(/*0*/ num: kotlin.Int = ...): WrongSignatures + public open fun equals(/*0*/ other: WrongSignatures): kotlin.Boolean + public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ fun hashCode(): kotlin.Boolean + public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index cade3c306b4..c912a77a73e 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -4320,6 +4320,18 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("finalMembersInBaseClass.kt") + public void testFinalMembersInBaseClass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/dataClasses/finalMembersInBaseClass.kt"); + doTest(fileName); + } + + @TestMetadata("implementMethodsFromInterface.kt") + public void testImplementMethodsFromInterface() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/dataClasses/implementMethodsFromInterface.kt"); + doTest(fileName); + } + @TestMetadata("implementTraitWhichHasComponent1.kt") public void testImplementTraitWhichHasComponent1() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/dataClasses/implementTraitWhichHasComponent1.kt"); diff --git a/idea/testData/structureView/fileStructure/InheritedSynthesizedFromDataClass.after b/idea/testData/structureView/fileStructure/InheritedSynthesizedFromDataClass.after index a4637815f06..e6cfa960d3f 100644 --- a/idea/testData/structureView/fileStructure/InheritedSynthesizedFromDataClass.after +++ b/idea/testData/structureView/fileStructure/InheritedSynthesizedFromDataClass.after @@ -1,9 +1,6 @@ -InheritedSynthesizedFromDataClass.kt -Derived a: Int location=→TestData - equals(Any?): Boolean location=→Any field: Int - hashCode(): Int location=→Any some(): Unit location=→TestData - toString(): String location=→Any TestData diff --git a/jps-plugin/testData/comparison/classSignatureChange/classFlagsChanged/result.out b/jps-plugin/testData/comparison/classSignatureChange/classFlagsChanged/result.out index df8bc66d450..4e874a01529 100644 --- a/jps-plugin/testData/comparison/classSignatureChange/classFlagsChanged/result.out +++ b/jps-plugin/testData/comparison/classSignatureChange/classFlagsChanged/result.out @@ -5,9 +5,9 @@ changes in test/AnnotationFlagAdded: CLASS_SIGNATURE changes in test/AnnotationFlagRemoved: CLASS_SIGNATURE changes in test/AnnotationFlagUnchanged: NONE changes in test/DataFlagAdded: CLASS_SIGNATURE, MEMBERS - [component1, copy] + [component1, copy, equals, hashCode, toString] changes in test/DataFlagRemoved: CLASS_SIGNATURE, MEMBERS - [component1, copy] + [component1, copy, equals, hashCode, toString] changes in test/DataFlagUnchanged: NONE changes in test/EnumFlagAdded: CLASS_SIGNATURE changes in test/EnumFlagRemoved: CLASS_SIGNATURE