diff --git a/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/CodegenUtil.kt b/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/CodegenUtil.kt index b199d206432..4737a3d8875 100644 --- a/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/CodegenUtil.kt +++ b/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/CodegenUtil.kt @@ -20,12 +20,10 @@ import com.intellij.psi.PsiElement import org.jetbrains.kotlin.backend.common.bridges.findTraitImplementation import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.incremental.components.NoLookupLocation -import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.MemberComparator -import org.jetbrains.kotlin.resolve.calls.callResolverUtil.isOrOverridesSynthesized import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns import org.jetbrains.kotlin.types.KotlinType @@ -78,21 +76,6 @@ object CodegenUtil { } } - @JvmStatic - fun getDeclaredFunctionByRawSignature( - owner: ClassDescriptor, - name: Name, - returnedClassifier: ClassifierDescriptor, - vararg valueParameterClassifiers: ClassifierDescriptor - ): FunctionDescriptor? { - return owner.defaultType.memberScope.getContributedFunctions(name, NoLookupLocation.FROM_BACKEND).firstOrNull { function -> - !isOrOverridesSynthesized(function) && - function.typeParameters.isEmpty() && - valueParameterClassesMatch(function.valueParameters, valueParameterClassifiers.toList()) && - rawTypeMatches(function.returnType!!, returnedClassifier) - } - } - @JvmStatic fun getDelegatePropertyIfAny( expression: KtExpression, classDescriptor: ClassDescriptor, bindingContext: BindingContext @@ -170,22 +153,6 @@ object CodegenUtil { ?: error("ClassDescriptor of superType should not be null: ${specifier.text}") } - private fun valueParameterClassesMatch( - parameters: List, - classifiers: List - ): Boolean { - if (parameters.size != classifiers.size) return false - for ((parameterDescriptor, classDescriptor) in parameters.zip(classifiers)) { - if (!rawTypeMatches(parameterDescriptor.type, classDescriptor)) { - return false - } - } - return true - } - - private fun rawTypeMatches(type: KotlinType, classifier: ClassifierDescriptor): Boolean = - type.constructor == classifier.typeConstructor - @JvmStatic fun isEnumValueOfMethod(functionDescriptor: FunctionDescriptor): Boolean { val methodTypeParameters = functionDescriptor.valueParameters 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 5d345f52951..ee69f0284a9 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 @@ -16,15 +16,16 @@ package org.jetbrains.kotlin.backend.common +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtParameter import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContextUtils -import org.jetbrains.kotlin.resolve.OverrideResolver -import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns +import org.jetbrains.kotlin.types.KotlinType /** * A platform-independent logic for generating data class synthetic methods. @@ -34,8 +35,6 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns abstract class DataClassMethodGenerator(private val declaration: KtClassOrObject, private val bindingContext: BindingContext) { protected val classDescriptor: ClassDescriptor = BindingContextUtils.getNotNull(bindingContext, BindingContext.CLASS, declaration) - private val builtIns = classDescriptor.builtIns - fun generate() { generateComponentFunctionsForDataClasses() @@ -78,24 +77,20 @@ abstract class DataClassMethodGenerator(private val declaration: KtClassOrObject } private fun generateDataClassToStringIfNeeded(properties: List) { - val function = getDeclaredMember("toString", builtIns.string) - if (function != null && isTrivial(function)) { - generateToStringMethod(function, properties) - } + val function = getMemberToGenerate("toString", KotlinBuiltIns::isString, List::isEmpty) ?: return + generateToStringMethod(function, properties) } private fun generateDataClassHashCodeIfNeeded(properties: List) { - val function = getDeclaredMember("hashCode", builtIns.int) - if (function != null && isTrivial(function)) { - generateHashCodeMethod(function, properties) - } + val function = getMemberToGenerate("hashCode", KotlinBuiltIns::isInt, List::isEmpty) ?: return + generateHashCodeMethod(function, properties) } private fun generateDataClassEqualsIfNeeded(properties: List) { - val function = getDeclaredMember("equals", builtIns.boolean, builtIns.any) - if (function != null && isTrivial(function)) { - generateEqualsMethod(function, properties) - } + val function = getMemberToGenerate("equals", KotlinBuiltIns::isBoolean) { parameters -> + parameters.size == 1 && KotlinBuiltIns.isNullableAny(parameters.first().type) + } ?: return + generateEqualsMethod(function, properties) } private val dataProperties: List @@ -106,23 +101,21 @@ abstract class DataClassMethodGenerator(private val declaration: KtClassOrObject private val primaryConstructorParameters: List get() = (declaration as? KtClass)?.getPrimaryConstructorParameters().orEmpty() - private fun getDeclaredMember( + // Returns the descriptor for a function (whose parameters match the given predicate) which should be generated in the data class. + // Note that we always generate equals/hashCode/toString in data classes, unless that would lead to a JVM signature clash with + // another method, which can only happen if the method is declared in the data class (manually or via delegation). + // Also there are no hard asserts or assumptions because such methods are generated for erroneous code as well (in light classes mode). + private fun getMemberToGenerate( name: String, - returnedClassifier: ClassDescriptor, - vararg valueParameterClassifiers: ClassDescriptor - ): FunctionDescriptor? = CodegenUtil.getDeclaredFunctionByRawSignature( - classDescriptor, Name.identifier(name), returnedClassifier, *valueParameterClassifiers - ) - - /** - * @return true if the member is an inherited implementation of a method from Any - */ - private fun isTrivial(function: FunctionDescriptor): Boolean { - return function.kind != CallableMemberDescriptor.Kind.DECLARATION && - OverrideResolver.getOverriddenDeclarations(function).none { overridden -> - overridden is CallableMemberDescriptor && - overridden.kind == CallableMemberDescriptor.Kind.DECLARATION && - overridden.containingDeclaration != builtIns.any - } - } + isReturnTypeOk: (KotlinType) -> Boolean, + areParametersOk: (List) -> Boolean + ): FunctionDescriptor? = + classDescriptor.unsubstitutedMemberScope.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BACKEND) + .singleOrNull { function -> + !function.kind.isReal && + function.modality != Modality.FINAL && + areParametersOk(function.valueParameters) && + function.returnType != null && + isReturnTypeOk(function.returnType!!) + } } diff --git a/compiler/testData/codegen/box/dataClasses/nonTrivialFinalMemberInSuperClass.kt b/compiler/testData/codegen/box/dataClasses/nonTrivialFinalMemberInSuperClass.kt new file mode 100644 index 00000000000..0a0383c03b7 --- /dev/null +++ b/compiler/testData/codegen/box/dataClasses/nonTrivialFinalMemberInSuperClass.kt @@ -0,0 +1,20 @@ +// TODO: remove the suppression once data classes can have supertypes +@file:Suppress("DATA_CLASS_CANNOT_HAVE_CLASS_SUPERTYPES") + +abstract class Base { + final override fun toString() = "OK" + final override fun hashCode() = 42 + final override fun equals(other: Any?) = false +} + +data class DataClass(val field: String) : Base() + +fun box(): String { + val d = DataClass("x") + + if (d.toString() != "OK") return "Fail toString" + if (d.hashCode() != 42) return "Fail hashCode" + if (d.equals(d) != false) return "Fail equals" + + return "OK" +} diff --git a/compiler/testData/codegen/box/dataClasses/nonTrivialMemberInSuperClass.kt b/compiler/testData/codegen/box/dataClasses/nonTrivialMemberInSuperClass.kt new file mode 100644 index 00000000000..65e77eae779 --- /dev/null +++ b/compiler/testData/codegen/box/dataClasses/nonTrivialMemberInSuperClass.kt @@ -0,0 +1,21 @@ +// See KT-6206 Always generate hashCode() and equals() for data classes even if base classes have non-trivial analogs +// TODO: remove the suppression once data classes can have supertypes +@file:Suppress("DATA_CLASS_CANNOT_HAVE_CLASS_SUPERTYPES") + +abstract class Base { + override fun toString() = "Fail" + override fun hashCode() = -42 + override fun equals(other: Any?) = false +} + +data class DataClass(val field: String) : Base() + +fun box(): String { + val d = DataClass("x") + + if (d.toString() != "DataClass(field=x)") return "Fail toString" + if (d.hashCode() != "x".hashCode()) return "Fail hashCode" + if (d.equals(d) == false) return "Fail equals" + + return "OK" +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index e0874ff1fca..d926647934c 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -4123,6 +4123,18 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("nonTrivialFinalMemberInSuperClass.kt") + public void testNonTrivialFinalMemberInSuperClass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/dataClasses/nonTrivialFinalMemberInSuperClass.kt"); + doTest(fileName); + } + + @TestMetadata("nonTrivialMemberInSuperClass.kt") + public void testNonTrivialMemberInSuperClass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/dataClasses/nonTrivialMemberInSuperClass.kt"); + doTest(fileName); + } + @TestMetadata("privateValParams.kt") public void testPrivateValParams() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/dataClasses/privateValParams.kt");