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:
Alexander Udalov
2016-04-13 16:04:38 +03:00
parent 2200bfcc85
commit 1c8272d3f1
5 changed files with 80 additions and 67 deletions
@@ -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
@@ -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
}
}
} }
@@ -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");