Implement proper contract for generated java.lang.Annotation.hashCode()
#KT-48606 Fixed
This commit is contained in:
+55
-14
@@ -13,19 +13,19 @@ import org.jetbrains.kotlin.backend.common.ir.copyTo
|
||||
import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.ir.IrStatement
|
||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.builders.*
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.*
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrGetValue
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrDelegatingConstructorCallImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrSetFieldImpl
|
||||
import org.jetbrains.kotlin.ir.types.IrType
|
||||
import org.jetbrains.kotlin.ir.types.classOrNull
|
||||
import org.jetbrains.kotlin.ir.types.isArray
|
||||
import org.jetbrains.kotlin.ir.types.isKClass
|
||||
import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||
@@ -183,18 +183,13 @@ open class AnnotationImplementationTransformer(val context: BackendContext, val
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
fun implementEqualsAndHashCode(annotationClass: IrClass, implClass: IrClass, originalProps: List<IrProperty>, childProps: List<IrProperty>) {
|
||||
val creator = MethodsFromAnyGeneratorForLowerings(context, implClass, ANNOTATION_IMPLEMENTATION)
|
||||
val generator =
|
||||
creator.LoweringDataClassMemberGenerator(
|
||||
nameForToString = "@" + annotationClass.fqNameWhenAvailable!!.asString(),
|
||||
typeForEquals = annotationClass.defaultType
|
||||
) { type, a, b ->
|
||||
generatedEquals(this, type, a, b)
|
||||
}
|
||||
val generator = AnnotationImplementationMemberGenerator(
|
||||
context, implClass,
|
||||
nameForToString = "@" + annotationClass.fqNameWhenAvailable!!.asString(),
|
||||
) { type, a, b ->
|
||||
generatedEquals(this, type, a, b)
|
||||
}
|
||||
|
||||
// Manual implementation of equals is required for two reasons:
|
||||
// 1. `other` should be casted to interface instead of implementation
|
||||
// 2. Properties should be retrieved using getters without accessing backing fields
|
||||
// (DataClassMembersGenerator typically tries to access fields)
|
||||
val eqFun = creator.createEqualsMethodDeclaration()
|
||||
generator.generateEqualsUsingGetters(eqFun, annotationClass.defaultType, originalProps)
|
||||
|
||||
@@ -208,3 +203,49 @@ open class AnnotationImplementationTransformer(val context: BackendContext, val
|
||||
open fun implementPlatformSpecificParts(annotationClass: IrClass, implClass: IrClass) {}
|
||||
}
|
||||
|
||||
class AnnotationImplementationMemberGenerator(
|
||||
backendContext: BackendContext,
|
||||
irClass: IrClass,
|
||||
val nameForToString: String,
|
||||
val selectEquals: IrBlockBodyBuilder.(IrType, IrExpression, IrExpression) -> IrExpression,
|
||||
) : LoweringDataClassMemberGenerator(backendContext, irClass, ANNOTATION_IMPLEMENTATION) {
|
||||
|
||||
override fun IrClass.classNameForToString(): String = nameForToString
|
||||
|
||||
// From https://docs.oracle.com/javase/8/docs/api/java/lang/annotation/Annotation.html#equals-java.lang.Object-
|
||||
// ---
|
||||
// The hash code of an annotation is the sum of the hash codes of its members (including those with default values), as defined below:
|
||||
// The hash code of an annotation member is (127 times the hash code of the member-name as computed by String.hashCode()) XOR the hash code of the member-value
|
||||
override fun IrBuilderWithScope.shiftResultOfHashCode(irResultVar: IrVariable): IrExpression = irGet(irResultVar) // no default (* 31)
|
||||
|
||||
override fun getHashCodeOf(builder: IrBuilderWithScope, property: IrProperty, irValue: IrExpression): IrExpression = with(builder) {
|
||||
val propertyValueHashCode = getHashCodeOf(property.backingField!!.type, irValue)
|
||||
val propertyNameHashCode = getHashCodeOf(backendContext.irBuiltIns.stringType, irString(property.name.toString()))
|
||||
val multiplied = irCallOp(context.irBuiltIns.intTimesSymbol, context.irBuiltIns.intType, propertyNameHashCode, irInt(127))
|
||||
return irCallOp(context.irBuiltIns.intXorSymbol, context.irBuiltIns.intType, multiplied, propertyValueHashCode)
|
||||
}
|
||||
|
||||
// Manual implementation of equals is required for two reasons:
|
||||
// 1. `other` should be casted to interface instead of implementation
|
||||
// 2. Properties should be retrieved using getters without accessing backing fields
|
||||
// (DataClassMembersGenerator typically tries to access fields)
|
||||
fun generateEqualsUsingGetters(equalsFun: IrSimpleFunction, typeForEquals: IrType, properties: List<IrProperty>) = equalsFun.apply {
|
||||
body = backendContext.createIrBuilder(symbol).irBlockBody {
|
||||
val irType = typeForEquals
|
||||
fun irOther() = irGet(valueParameters[0])
|
||||
fun irThis() = irGet(dispatchReceiverParameter!!)
|
||||
fun IrProperty.get(receiver: IrExpression) = irCall(getter!!).apply {
|
||||
dispatchReceiver = receiver
|
||||
}
|
||||
|
||||
+irIfThenReturnFalse(irNotIs(irOther(), irType))
|
||||
val otherWithCast = irTemporary(irAs(irOther(), irType), "other_with_cast")
|
||||
for (property in properties) {
|
||||
val arg1 = property.get(irThis())
|
||||
val arg2 = property.get(irGet(irType, otherWithCast.symbol))
|
||||
+irIfThenReturnFalse(irNot(selectEquals(property.getter?.returnType ?: property.backingField!!.type, arg1, arg2)))
|
||||
}
|
||||
+irReturnTrue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+50
-67
@@ -6,6 +6,7 @@
|
||||
package org.jetbrains.kotlin.backend.common.lower
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.BackendContext
|
||||
import org.jetbrains.kotlin.backend.common.lower.MethodsFromAnyGeneratorForLowerings.Companion.isHashCode
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
||||
@@ -38,71 +39,6 @@ class MethodsFromAnyGeneratorForLowerings(val context: BackendContext, val irCla
|
||||
addValueParameter("other", context.irBuiltIns.anyNType)
|
||||
}
|
||||
|
||||
inner class LoweringDataClassMemberGenerator(
|
||||
val nameForToString: String,
|
||||
val typeForEquals: IrType,
|
||||
val selectEquals: IrBlockBodyBuilder.(IrType, IrExpression, IrExpression) -> IrExpression,
|
||||
) :
|
||||
DataClassMembersGenerator(
|
||||
IrGeneratorContextBase(context.irBuiltIns),
|
||||
context.ir.symbols.externalSymbolTable,
|
||||
irClass,
|
||||
origin
|
||||
) {
|
||||
|
||||
override fun declareSimpleFunction(startOffset: Int, endOffset: Int, functionDescriptor: FunctionDescriptor): IrFunction {
|
||||
error("Descriptor API shouldn't be used in lowerings")
|
||||
}
|
||||
|
||||
override fun generateSyntheticFunctionParameterDeclarations(irFunction: IrFunction) {
|
||||
// no-op — irFunction from lowering should already have necessary parameters
|
||||
}
|
||||
|
||||
override fun getProperty(parameter: ValueParameterDescriptor?, irValueParameter: IrValueParameter?): IrProperty? {
|
||||
error("Descriptor API shouldn't be used in lowerings")
|
||||
}
|
||||
|
||||
override fun transform(typeParameterDescriptor: TypeParameterDescriptor): IrType {
|
||||
error("Descriptor API shouldn't be used in lowerings")
|
||||
}
|
||||
|
||||
override fun getHashCodeFunctionInfo(type: IrType): HashCodeFunctionInfo {
|
||||
val symbol = if (type.isArray() || type.isPrimitiveArray()) {
|
||||
context.irBuiltIns.dataClassArrayMemberHashCodeSymbol
|
||||
} else {
|
||||
type.classOrNull?.functions?.singleOrNull { it.owner.isHashCode() } ?:
|
||||
context.irBuiltIns.anyClass.functions.single { it.owner.name.asString() == "hashCode" }
|
||||
}
|
||||
return object : HashCodeFunctionInfo {
|
||||
override val symbol: IrSimpleFunctionSymbol = symbol
|
||||
|
||||
override fun commitSubstituted(irMemberAccessExpression: IrMemberAccessExpression<*>) {}
|
||||
}
|
||||
}
|
||||
|
||||
override fun IrClass.classNameForToString(): String = nameForToString
|
||||
|
||||
fun generateEqualsUsingGetters(equalsFun: IrSimpleFunction, typeForEquals: IrType, properties: List<IrProperty>) = equalsFun.apply {
|
||||
body = this@MethodsFromAnyGeneratorForLowerings.context.createIrBuilder(symbol).irBlockBody {
|
||||
val irType = typeForEquals
|
||||
fun irOther() = irGet(valueParameters[0])
|
||||
fun irThis() = irGet(dispatchReceiverParameter!!)
|
||||
fun IrProperty.get(receiver: IrExpression) = irCall(getter!!).apply {
|
||||
dispatchReceiver = receiver
|
||||
}
|
||||
|
||||
+irIfThenReturnFalse(irNotIs(irOther(), irType))
|
||||
val otherWithCast = irTemporary(irAs(irOther(), irType), "other_with_cast")
|
||||
for (property in properties) {
|
||||
val arg1 = property.get(irThis())
|
||||
val arg2 = property.get(irGet(irType, otherWithCast.symbol))
|
||||
+irIfThenReturnFalse(irNot(selectEquals(property.getter?.returnType ?: property.backingField!!.type, arg1, arg2)))
|
||||
}
|
||||
+irReturnTrue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun IrFunction.isToString(): Boolean =
|
||||
name.asString() == "toString" && extensionReceiverParameter == null && valueParameters.isEmpty()
|
||||
@@ -118,6 +54,53 @@ class MethodsFromAnyGeneratorForLowerings(val context: BackendContext, val irCla
|
||||
|
||||
fun IrClass.collectOverridenSymbols(predicate: (IrFunction) -> Boolean): List<IrSimpleFunctionSymbol> =
|
||||
superTypes.mapNotNull { it.getClass()?.functions?.singleOrNull(predicate)?.symbol }
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open class LoweringDataClassMemberGenerator(
|
||||
val backendContext: BackendContext,
|
||||
irClass: IrClass,
|
||||
origin: IrDeclarationOrigin
|
||||
) :
|
||||
DataClassMembersGenerator(
|
||||
IrGeneratorContextBase(backendContext.irBuiltIns),
|
||||
backendContext.ir.symbols.externalSymbolTable,
|
||||
irClass,
|
||||
origin
|
||||
) {
|
||||
|
||||
override fun declareSimpleFunction(startOffset: Int, endOffset: Int, functionDescriptor: FunctionDescriptor): IrFunction {
|
||||
error("Descriptor API shouldn't be used in lowerings")
|
||||
}
|
||||
|
||||
override fun generateSyntheticFunctionParameterDeclarations(irFunction: IrFunction) {
|
||||
// no-op — irFunction from lowering should already have necessary parameters
|
||||
}
|
||||
|
||||
override fun getProperty(parameter: ValueParameterDescriptor?, irValueParameter: IrValueParameter?): IrProperty? {
|
||||
error("Descriptor API shouldn't be used in lowerings")
|
||||
}
|
||||
|
||||
override fun transform(typeParameterDescriptor: TypeParameterDescriptor): IrType {
|
||||
error("Descriptor API shouldn't be used in lowerings")
|
||||
}
|
||||
|
||||
override fun getHashCodeFunctionInfo(type: IrType): HashCodeFunctionInfo {
|
||||
val symbol = if (type.isArray() || type.isPrimitiveArray()) {
|
||||
context.irBuiltIns.dataClassArrayMemberHashCodeSymbol
|
||||
} else {
|
||||
type.classOrNull?.functions?.singleOrNull { it.owner.isHashCode() }
|
||||
?: context.irBuiltIns.anyClass.functions.single { it.owner.name.asString() == "hashCode" }
|
||||
}
|
||||
return object : HashCodeFunctionInfo {
|
||||
override val symbol: IrSimpleFunctionSymbol = symbol
|
||||
|
||||
override fun commitSubstituted(irMemberAccessExpression: IrMemberAccessExpression<*>) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user