Allow data classes to implement equals/hashCode/toString from base classes

#KT-11306 Fixed
This commit is contained in:
Alexander Udalov
2016-09-14 13:18:09 +03:00
parent 98f6ea577a
commit fea116f14e
10 changed files with 205 additions and 11 deletions
@@ -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,
@@ -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
}
@@ -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
}
@@ -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");
@@ -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
@@ -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