Allow data classes to implement equals/hashCode/toString from base classes
#KT-11306 Fixed
This commit is contained in:
+1
-2
@@ -112,8 +112,7 @@ abstract class DataClassMethodGenerator(private val declaration: KtClassOrObject
|
|||||||
): FunctionDescriptor? =
|
): FunctionDescriptor? =
|
||||||
classDescriptor.unsubstitutedMemberScope.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BACKEND)
|
classDescriptor.unsubstitutedMemberScope.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BACKEND)
|
||||||
.singleOrNull { function ->
|
.singleOrNull { function ->
|
||||||
!function.kind.isReal &&
|
function.kind == CallableMemberDescriptor.Kind.SYNTHESIZED &&
|
||||||
function.modality != Modality.FINAL &&
|
|
||||||
areParametersOk(function.valueParameters) &&
|
areParametersOk(function.valueParameters) &&
|
||||||
function.returnType != null &&
|
function.returnType != null &&
|
||||||
isReturnTypeOk(function.returnType!!)
|
isReturnTypeOk(function.returnType!!)
|
||||||
|
|||||||
@@ -20,9 +20,16 @@ import org.jetbrains.kotlin.descriptors.*
|
|||||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||||
import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl
|
import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl
|
||||||
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
|
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
|
||||||
|
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
|
||||||
|
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||||
|
|
||||||
object DataClassDescriptorResolver {
|
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")
|
val COPY_METHOD_NAME = Name.identifier("copy")
|
||||||
|
|
||||||
private val COMPONENT_FUNCTION_NAME_PREFIX = "component"
|
private val COMPONENT_FUNCTION_NAME_PREFIX = "component"
|
||||||
@@ -49,6 +56,36 @@ object DataClassDescriptorResolver {
|
|||||||
return true
|
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(
|
fun createComponentFunctionDescriptor(
|
||||||
parameterIndex: Int,
|
parameterIndex: Int,
|
||||||
property: PropertyDescriptor,
|
property: PropertyDescriptor,
|
||||||
|
|||||||
+30
-4
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.resolve.lazy.descriptors
|
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.*
|
||||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.DELEGATION
|
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.DELEGATION
|
||||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.FAKE_OVERRIDE
|
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.FAKE_OVERRIDE
|
||||||
@@ -129,16 +129,21 @@ open class LazyClassMemberScope(
|
|||||||
override fun getNonDeclaredFunctions(name: Name, result: MutableSet<SimpleFunctionDescriptor>) {
|
override fun getNonDeclaredFunctions(name: Name, result: MutableSet<SimpleFunctionDescriptor>) {
|
||||||
val location = NoLookupLocation.FOR_ALREADY_TRACKED
|
val location = NoLookupLocation.FOR_ALREADY_TRACKED
|
||||||
|
|
||||||
val fromSupertypes = Lists.newArrayList<SimpleFunctionDescriptor>()
|
val fromSupertypes = arrayListOf<SimpleFunctionDescriptor>()
|
||||||
for (supertype in thisDescriptor.typeConstructor.supertypes) {
|
for (supertype in thisDescriptor.typeConstructor.supertypes) {
|
||||||
fromSupertypes.addAll(supertype.memberScope.getContributedFunctions(name, location))
|
fromSupertypes.addAll(supertype.memberScope.getContributedFunctions(name, location))
|
||||||
}
|
}
|
||||||
result.addAll(generateDelegatingDescriptors(name, EXTRACT_FUNCTIONS, result))
|
result.addAll(generateDelegatingDescriptors(name, EXTRACT_FUNCTIONS, result))
|
||||||
generateDataClassMethods(result, name, location)
|
generateDataClassMethods(result, name, location, fromSupertypes)
|
||||||
generateFakeOverrides(name, fromSupertypes, result, SimpleFunctionDescriptor::class.java)
|
generateFakeOverrides(name, fromSupertypes, result, SimpleFunctionDescriptor::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateDataClassMethods(result: MutableCollection<SimpleFunctionDescriptor>, name: Name, location: LookupLocation) {
|
private fun generateDataClassMethods(
|
||||||
|
result: MutableCollection<SimpleFunctionDescriptor>,
|
||||||
|
name: Name,
|
||||||
|
location: LookupLocation,
|
||||||
|
fromSupertypes: List<SimpleFunctionDescriptor>
|
||||||
|
) {
|
||||||
if (!thisDescriptor.isData) return
|
if (!thisDescriptor.isData) return
|
||||||
|
|
||||||
val constructor = getPrimaryConstructor() ?: return
|
val constructor = getPrimaryConstructor() ?: return
|
||||||
@@ -177,6 +182,27 @@ open class LazyClassMemberScope(
|
|||||||
|
|
||||||
result.add(DataClassDescriptorResolver.createCopyFunctionDescriptor(constructor.valueParameters, thisDescriptor, trace))
|
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<PropertyDescriptor> {
|
override fun getContributedVariables(name: Name, location: LookupLocation): Collection<PropertyDescriptor> {
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
}
|
||||||
+25
@@ -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 {
|
||||||
|
<!NOTHING_TO_OVERRIDE!>override<!> fun equals(other: WrongSignatures) = false
|
||||||
|
override fun hashCode(): <!RETURN_TYPE_MISMATCH_ON_OVERRIDE!>Boolean<!> = true
|
||||||
|
}
|
||||||
+48
@@ -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
|
||||||
|
}
|
||||||
@@ -4320,6 +4320,18 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("implementTraitWhichHasComponent1.kt")
|
||||||
public void testImplementTraitWhichHasComponent1() throws Exception {
|
public void testImplementTraitWhichHasComponent1() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/dataClasses/implementTraitWhichHasComponent1.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/dataClasses/implementTraitWhichHasComponent1.kt");
|
||||||
|
|||||||
-3
@@ -1,9 +1,6 @@
|
|||||||
-InheritedSynthesizedFromDataClass.kt
|
-InheritedSynthesizedFromDataClass.kt
|
||||||
-Derived
|
-Derived
|
||||||
a: Int location=→TestData
|
a: Int location=→TestData
|
||||||
equals(Any?): Boolean location=→Any
|
|
||||||
field: Int
|
field: Int
|
||||||
hashCode(): Int location=→Any
|
|
||||||
some(): Unit location=→TestData
|
some(): Unit location=→TestData
|
||||||
toString(): String location=→Any
|
|
||||||
TestData
|
TestData
|
||||||
|
|||||||
+2
-2
@@ -5,9 +5,9 @@ changes in test/AnnotationFlagAdded: CLASS_SIGNATURE
|
|||||||
changes in test/AnnotationFlagRemoved: CLASS_SIGNATURE
|
changes in test/AnnotationFlagRemoved: CLASS_SIGNATURE
|
||||||
changes in test/AnnotationFlagUnchanged: NONE
|
changes in test/AnnotationFlagUnchanged: NONE
|
||||||
changes in test/DataFlagAdded: CLASS_SIGNATURE, MEMBERS
|
changes in test/DataFlagAdded: CLASS_SIGNATURE, MEMBERS
|
||||||
[component1, copy]
|
[component1, copy, equals, hashCode, toString]
|
||||||
changes in test/DataFlagRemoved: CLASS_SIGNATURE, MEMBERS
|
changes in test/DataFlagRemoved: CLASS_SIGNATURE, MEMBERS
|
||||||
[component1, copy]
|
[component1, copy, equals, hashCode, toString]
|
||||||
changes in test/DataFlagUnchanged: NONE
|
changes in test/DataFlagUnchanged: NONE
|
||||||
changes in test/EnumFlagAdded: CLASS_SIGNATURE
|
changes in test/EnumFlagAdded: CLASS_SIGNATURE
|
||||||
changes in test/EnumFlagRemoved: CLASS_SIGNATURE
|
changes in test/EnumFlagRemoved: CLASS_SIGNATURE
|
||||||
|
|||||||
Reference in New Issue
Block a user