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
@@ -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> {