Simplify data class function generation and signature lookup code
- change prerequisites for generating equals/hashCode/toString in a data class: previously they were generated if the corresponding method is trivial (i.e. it comes from kotlin.Any), now we're generating it always unless it'll cause a JVM signature clash error (see KT-6206) - use static KotlinBuiltIns.isXxx methods to compare types instead of checking against descriptors loaded from certain built-ins instance, this is quicker and more correct in environments where several built-ins are possible - don't use isOrOverridesSynthesized, it's not relevant for equals/hashCode/toString because functions with these names are never synthesized #KT-6206 Fixed
This commit is contained in:
@@ -20,12 +20,10 @@ import com.intellij.psi.PsiElement
|
|||||||
import org.jetbrains.kotlin.backend.common.bridges.findTraitImplementation
|
import org.jetbrains.kotlin.backend.common.bridges.findTraitImplementation
|
||||||
import org.jetbrains.kotlin.descriptors.*
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||||
import org.jetbrains.kotlin.name.Name
|
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||||
import org.jetbrains.kotlin.resolve.MemberComparator
|
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.calls.callUtil.getResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
|
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
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
|
@JvmStatic
|
||||||
fun getDelegatePropertyIfAny(
|
fun getDelegatePropertyIfAny(
|
||||||
expression: KtExpression, classDescriptor: ClassDescriptor, bindingContext: BindingContext
|
expression: KtExpression, classDescriptor: ClassDescriptor, bindingContext: BindingContext
|
||||||
@@ -170,22 +153,6 @@ object CodegenUtil {
|
|||||||
?: error("ClassDescriptor of superType should not be null: ${specifier.text}")
|
?: error("ClassDescriptor of superType should not be null: ${specifier.text}")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun valueParameterClassesMatch(
|
|
||||||
parameters: List<ValueParameterDescriptor>,
|
|
||||||
classifiers: List<ClassifierDescriptor>
|
|
||||||
): 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
|
@JvmStatic
|
||||||
fun isEnumValueOfMethod(functionDescriptor: FunctionDescriptor): Boolean {
|
fun isEnumValueOfMethod(functionDescriptor: FunctionDescriptor): Boolean {
|
||||||
val methodTypeParameters = functionDescriptor.valueParameters
|
val methodTypeParameters = functionDescriptor.valueParameters
|
||||||
|
|||||||
+27
-34
@@ -16,15 +16,16 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.backend.common
|
package org.jetbrains.kotlin.backend.common
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
import org.jetbrains.kotlin.descriptors.*
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
|
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.psi.KtClass
|
import org.jetbrains.kotlin.psi.KtClass
|
||||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||||
import org.jetbrains.kotlin.psi.KtParameter
|
import org.jetbrains.kotlin.psi.KtParameter
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.BindingContextUtils
|
import org.jetbrains.kotlin.resolve.BindingContextUtils
|
||||||
import org.jetbrains.kotlin.resolve.OverrideResolver
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A platform-independent logic for generating data class synthetic methods.
|
* 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) {
|
abstract class DataClassMethodGenerator(private val declaration: KtClassOrObject, private val bindingContext: BindingContext) {
|
||||||
protected val classDescriptor: ClassDescriptor = BindingContextUtils.getNotNull(bindingContext, BindingContext.CLASS, declaration)
|
protected val classDescriptor: ClassDescriptor = BindingContextUtils.getNotNull(bindingContext, BindingContext.CLASS, declaration)
|
||||||
|
|
||||||
private val builtIns = classDescriptor.builtIns
|
|
||||||
|
|
||||||
fun generate() {
|
fun generate() {
|
||||||
generateComponentFunctionsForDataClasses()
|
generateComponentFunctionsForDataClasses()
|
||||||
|
|
||||||
@@ -78,24 +77,20 @@ abstract class DataClassMethodGenerator(private val declaration: KtClassOrObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun generateDataClassToStringIfNeeded(properties: List<PropertyDescriptor>) {
|
private fun generateDataClassToStringIfNeeded(properties: List<PropertyDescriptor>) {
|
||||||
val function = getDeclaredMember("toString", builtIns.string)
|
val function = getMemberToGenerate("toString", KotlinBuiltIns::isString, List<ValueParameterDescriptor>::isEmpty) ?: return
|
||||||
if (function != null && isTrivial(function)) {
|
generateToStringMethod(function, properties)
|
||||||
generateToStringMethod(function, properties)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateDataClassHashCodeIfNeeded(properties: List<PropertyDescriptor>) {
|
private fun generateDataClassHashCodeIfNeeded(properties: List<PropertyDescriptor>) {
|
||||||
val function = getDeclaredMember("hashCode", builtIns.int)
|
val function = getMemberToGenerate("hashCode", KotlinBuiltIns::isInt, List<ValueParameterDescriptor>::isEmpty) ?: return
|
||||||
if (function != null && isTrivial(function)) {
|
generateHashCodeMethod(function, properties)
|
||||||
generateHashCodeMethod(function, properties)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateDataClassEqualsIfNeeded(properties: List<PropertyDescriptor>) {
|
private fun generateDataClassEqualsIfNeeded(properties: List<PropertyDescriptor>) {
|
||||||
val function = getDeclaredMember("equals", builtIns.boolean, builtIns.any)
|
val function = getMemberToGenerate("equals", KotlinBuiltIns::isBoolean) { parameters ->
|
||||||
if (function != null && isTrivial(function)) {
|
parameters.size == 1 && KotlinBuiltIns.isNullableAny(parameters.first().type)
|
||||||
generateEqualsMethod(function, properties)
|
} ?: return
|
||||||
}
|
generateEqualsMethod(function, properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val dataProperties: List<PropertyDescriptor>
|
private val dataProperties: List<PropertyDescriptor>
|
||||||
@@ -106,23 +101,21 @@ abstract class DataClassMethodGenerator(private val declaration: KtClassOrObject
|
|||||||
private val primaryConstructorParameters: List<KtParameter>
|
private val primaryConstructorParameters: List<KtParameter>
|
||||||
get() = (declaration as? KtClass)?.getPrimaryConstructorParameters().orEmpty()
|
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,
|
name: String,
|
||||||
returnedClassifier: ClassDescriptor,
|
isReturnTypeOk: (KotlinType) -> Boolean,
|
||||||
vararg valueParameterClassifiers: ClassDescriptor
|
areParametersOk: (List<ValueParameterDescriptor>) -> Boolean
|
||||||
): FunctionDescriptor? = CodegenUtil.getDeclaredFunctionByRawSignature(
|
): FunctionDescriptor? =
|
||||||
classDescriptor, Name.identifier(name), returnedClassifier, *valueParameterClassifiers
|
classDescriptor.unsubstitutedMemberScope.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BACKEND)
|
||||||
)
|
.singleOrNull { function ->
|
||||||
|
!function.kind.isReal &&
|
||||||
/**
|
function.modality != Modality.FINAL &&
|
||||||
* @return true if the member is an inherited implementation of a method from Any
|
areParametersOk(function.valueParameters) &&
|
||||||
*/
|
function.returnType != null &&
|
||||||
private fun isTrivial(function: FunctionDescriptor): Boolean {
|
isReturnTypeOk(function.returnType!!)
|
||||||
return function.kind != CallableMemberDescriptor.Kind.DECLARATION &&
|
}
|
||||||
OverrideResolver.getOverriddenDeclarations(function).none { overridden ->
|
|
||||||
overridden is CallableMemberDescriptor &&
|
|
||||||
overridden.kind == CallableMemberDescriptor.Kind.DECLARATION &&
|
|
||||||
overridden.containingDeclaration != builtIns.any
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+20
@@ -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"
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -4123,6 +4123,18 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("privateValParams.kt")
|
||||||
public void testPrivateValParams() throws Exception {
|
public void testPrivateValParams() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/dataClasses/privateValParams.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/dataClasses/privateValParams.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user