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? =
|
||||
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!!)
|
||||
|
||||
@@ -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,
|
||||
|
||||
+30
-4
@@ -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<SimpleFunctionDescriptor>) {
|
||||
val location = NoLookupLocation.FOR_ALREADY_TRACKED
|
||||
|
||||
val fromSupertypes = Lists.newArrayList<SimpleFunctionDescriptor>()
|
||||
val fromSupertypes = arrayListOf<SimpleFunctionDescriptor>()
|
||||
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<SimpleFunctionDescriptor>, name: Name, location: LookupLocation) {
|
||||
private fun generateDataClassMethods(
|
||||
result: MutableCollection<SimpleFunctionDescriptor>,
|
||||
name: Name,
|
||||
location: LookupLocation,
|
||||
fromSupertypes: List<SimpleFunctionDescriptor>
|
||||
) {
|
||||
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<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);
|
||||
}
|
||||
|
||||
@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");
|
||||
|
||||
-3
@@ -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
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user