diff --git a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/DataClassMembersGenerator.kt b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/DataClassMembersGenerator.kt index c928347c9f6..105b6b54a2c 100644 --- a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/DataClassMembersGenerator.kt +++ b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/DataClassMembersGenerator.kt @@ -31,11 +31,18 @@ import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.builders.IrGeneratorContextBase import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression +import org.jetbrains.kotlin.ir.symbols.IrClassSymbol +import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol +import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.types.classOrNull +import org.jetbrains.kotlin.ir.types.classifierOrNull import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.util.DataClassMembersGenerator import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.typeUtil.representativeUpperBound /** * A generator that generates synthetic members of data class as well as part of inline class. @@ -93,8 +100,47 @@ class DataClassMembersGenerator(val components: Fir2IrComponents) { return components.irBuiltIns.anyType } - override fun commitSubstituted(irMemberAccessExpression: IrMemberAccessExpression<*>, descriptor: CallableDescriptor) { - // TODO + inner class Fir2IrHashCodeFunctionInfo(override val symbol: IrSimpleFunctionSymbol) : HashCodeFunctionInfo { + override fun commitSubstituted(irMemberAccessExpression: IrMemberAccessExpression<*>) { + // TODO + } + } + + private fun getHashCodeFunction(klass: IrClass): IrSimpleFunctionSymbol = + klass.functions.singleOrNull { + it.name.asString() == "hashCode" && it.valueParameters.isEmpty() && it.extensionReceiverParameter == null + }?.symbol + ?: context.irBuiltIns.anyClass.functions.single { it.owner.name.asString() == "hashCode" } + + + val IrTypeParameter.erasedUpperBound: IrClass + get() { + // Pick the (necessarily unique) non-interface upper bound if it exists + for (type in superTypes) { + val irClass = type.classOrNull?.owner ?: continue + if (!irClass.isInterface && !irClass.isAnnotationClass) return irClass + } + + // Otherwise, choose either the first IrClass supertype or recurse. + // In the first case, all supertypes are interface types and the choice was arbitrary. + // In the second case, there is only a single supertype. + return when (val firstSuper = superTypes.first().classifierOrNull?.owner) { + is IrClass -> firstSuper + is IrTypeParameter -> firstSuper.erasedUpperBound + else -> error("unknown supertype kind $firstSuper") + } + } + + + override fun getHashCodeFunctionInfo(type: IrType): HashCodeFunctionInfo { + val classifier = type.classifierOrNull + val symbol = when { + classifier.isArrayOrPrimitiveArray -> context.irBuiltIns.dataClassArrayMemberHashCodeSymbol + classifier is IrClassSymbol -> getHashCodeFunction(classifier.owner) + classifier is IrTypeParameterSymbol -> getHashCodeFunction(classifier.owner.erasedUpperBound) + else -> error("Unknown classifier kind $classifier") + } + return Fir2IrHashCodeFunctionInfo(symbol) } } @@ -131,7 +177,6 @@ class DataClassMembersGenerator(val components: Fir2IrComponents) { val properties = irClass.declarations .filterIsInstance() .take(propertyParametersCount) - .map { it.descriptor } if (properties.isEmpty()) { return emptyList() } diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/DataClassMembersGenerator.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/DataClassMembersGenerator.kt index 0c9f09e84bb..8adfb284602 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/DataClassMembersGenerator.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/DataClassMembersGenerator.kt @@ -17,15 +17,23 @@ package org.jetbrains.kotlin.psi2ir.generators import org.jetbrains.kotlin.backend.common.DataClassMethodGenerator +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression +import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.types.toKotlinType import org.jetbrains.kotlin.ir.util.DataClassMembersGenerator import org.jetbrains.kotlin.ir.util.declareSimpleFunctionWithOverrides +import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtParameter import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.scopes.MemberScope +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.typeUtil.representativeUpperBound /** * A generator that generates synthetic members of data class as well as part of inline class. @@ -72,14 +80,57 @@ class DataClassMembersGenerator( override fun getProperty(parameter: ValueParameterDescriptor?, irValueParameter: IrValueParameter?): IrProperty? = parameter?.let { val property = getOrFail(BindingContext.VALUE_PARAMETER_AS_PROPERTY, parameter) - return getProperty(property) + return getIrProperty(property) } override fun transform(typeParameterDescriptor: TypeParameterDescriptor): IrType = typeParameterDescriptor.defaultType.toIrType() - override fun commitSubstituted(irMemberAccessExpression: IrMemberAccessExpression<*>, descriptor: CallableDescriptor) { - irMemberAccessExpression.commitSubstituted(descriptor) + private fun MemberScope.findHashCodeFunctionOrNull() = + getContributedFunctions(Name.identifier("hashCode"), NoLookupLocation.FROM_BACKEND) + .find { it.valueParameters.isEmpty() && it.extensionReceiverParameter == null } + + private fun getHashCodeFunction(type: KotlinType): FunctionDescriptor = + type.memberScope.findHashCodeFunctionOrNull() + ?: context.builtIns.any.unsubstitutedMemberScope.findHashCodeFunctionOrNull()!! + + private fun getHashCodeFunction( + type: KotlinType, + symbolResolve: (FunctionDescriptor) -> IrSimpleFunctionSymbol + ): IrSimpleFunctionSymbol = + when (val typeConstructorDescriptor = type.constructor.declarationDescriptor) { + is ClassDescriptor -> + if (KotlinBuiltIns.isArrayOrPrimitiveArray(typeConstructorDescriptor)) + context.irBuiltIns.dataClassArrayMemberHashCodeSymbol + else + symbolResolve(getHashCodeFunction(type)) + + is TypeParameterDescriptor -> + getHashCodeFunction(typeConstructorDescriptor.representativeUpperBound, symbolResolve) + + else -> + throw AssertionError("Unexpected type: $type") + } + + + inner class Psi2IrHashCodeFunctionInfo( + override val symbol: IrSimpleFunctionSymbol, + val substituted: CallableDescriptor + ) : HashCodeFunctionInfo { + + override fun commitSubstituted(irMemberAccessExpression: IrMemberAccessExpression<*>) { + irMemberAccessExpression.commitSubstituted(substituted) + } + + } + + override fun getHashCodeFunctionInfo(type: IrType): HashCodeFunctionInfo { + var substituted: CallableDescriptor? = null + val symbol = getHashCodeFunction(type.toKotlinType()) { hashCodeDescriptor -> + substituted = hashCodeDescriptor + symbolTable.referenceSimpleFunction(hashCodeDescriptor.original) + } + return Psi2IrHashCodeFunctionInfo(symbol, substituted ?: symbol.descriptor) } } diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/DataClassMembersGenerator.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/DataClassMembersGenerator.kt index b1cf4821498..07e0b362587 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/DataClassMembersGenerator.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/DataClassMembersGenerator.kt @@ -5,30 +5,26 @@ package org.jetbrains.kotlin.ir.util -import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* -import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.impl.IrVariableImpl -import org.jetbrains.kotlin.ir.descriptors.WrappedVariableDescriptor import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl import org.jetbrains.kotlin.ir.expressions.mapTypeParameters import org.jetbrains.kotlin.ir.expressions.mapValueParameters +import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol import org.jetbrains.kotlin.ir.symbols.impl.IrVariableSymbolImpl import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.types.classifierOrNull +import org.jetbrains.kotlin.ir.types.isNullable import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.resolve.scopes.MemberScope -import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.checker.KotlinTypeChecker -import org.jetbrains.kotlin.types.isNullable -import org.jetbrains.kotlin.types.typeUtil.representativeUpperBound /** * A platform-, frontend-independent logic for generating synthetic members of data class: equals, hashCode, toString, componentN, and copy. @@ -127,7 +123,7 @@ abstract class DataClassMembersGenerator( ) } - fun generateEqualsMethodBody(properties: List) { + fun generateEqualsMethodBody(properties: List) { val irType = irClass.defaultType if (!irClass.isInline) { @@ -136,9 +132,8 @@ abstract class DataClassMembersGenerator( +irIfThenReturnFalse(irNotIs(irOther(), irType)) val otherWithCast = irTemporary(irAs(irOther(), irType), "other_with_cast") for (property in properties) { - val irProperty = getProperty(property) - val arg1 = irGetProperty(irThis(), irProperty) - val arg2 = irGetProperty(irGet(irType, otherWithCast.symbol), irProperty) + val arg1 = irGetProperty(irThis(), property) + val arg2 = irGetProperty(irGet(irType, otherWithCast.symbol), property) +irIfThenReturnFalse(irNotEquals(arg1, arg2)) } +irReturnTrue() @@ -157,7 +152,7 @@ abstract class DataClassMembersGenerator( KotlinTypeChecker.DEFAULT.equalTypes(it.valueParameters[0].type, intType) }.let { symbolTable.referenceSimpleFunction(it) } - fun generateHashCodeMethodBody(properties: List) { + fun generateHashCodeMethodBody(properties: List) { if (properties.isEmpty()) { +irReturn(irInt(0)) return @@ -189,27 +184,23 @@ abstract class DataClassMembersGenerator( +irReturn(irGet(irResultVar)) } - private fun getHashCodeOfProperty(property: PropertyDescriptor): IrExpression { - val irProperty = getProperty(property) + private fun getHashCodeOfProperty(property: IrProperty): IrExpression { return when { - property.type.isNullable() -> + property.backingField!!.type.isNullable() -> irIfNull( context.irBuiltIns.intType, - irGetProperty(irThis(), irProperty), + irGetProperty(irThis(), property), irInt(0), - getHashCodeOf(property, irGetProperty(irThis(), irProperty)) + getHashCodeOf(property, irGetProperty(irThis(), property)) ) else -> - getHashCodeOf(property, irGetProperty(irThis(), irProperty)) + getHashCodeOf(property, irGetProperty(irThis(), property)) } } - private fun getHashCodeOf(property: PropertyDescriptor, irValue: IrExpression): IrExpression { - var substituted: FunctionDescriptor? = null - val hashCodeFunctionSymbol = getHashCodeFunction(property) { - substituted = it - symbolTable.referenceSimpleFunction(it.original) - } + private fun getHashCodeOf(property: IrProperty, irValue: IrExpression): IrExpression { + val hashCodeFunctionInfo = getHashCodeFunctionInfo(property.backingField!!.type) + val hashCodeFunctionSymbol = hashCodeFunctionInfo.symbol val hasDispatchReceiver = hashCodeFunctionSymbol.descriptor.dispatchReceiverParameter != null return irCall( @@ -223,26 +214,24 @@ abstract class DataClassMembersGenerator( } else { putValueArgument(0, irValue) } - commitSubstituted(this, substituted ?: hashCodeFunctionSymbol.descriptor) + hashCodeFunctionInfo.commitSubstituted(this) } } - fun generateToStringMethodBody(properties: List) { + fun generateToStringMethodBody(properties: List) { val irConcat = irConcat() - irConcat.addArgument(irString(irClass.descriptor.name.asString() + "(")) + irConcat.addArgument(irString(irClass.name.asString() + "(")) var first = true for (property in properties) { if (!first) irConcat.addArgument(irString(", ")) irConcat.addArgument(irString(property.name.asString() + "=")) - val irPropertyValue = irGetProperty(irThis(), getProperty(property)) + val irPropertyValue = irGetProperty(irThis(), property) - val typeConstructorDescriptor = property.type.constructor.declarationDescriptor + val classifier = property.backingField!!.type.classifierOrNull val irPropertyStringValue = - if (typeConstructorDescriptor is ClassDescriptor && - KotlinBuiltIns.isArrayOrPrimitiveArray(typeConstructorDescriptor) - ) + if (classifier.isArrayOrPrimitiveArray) irCall(context.irBuiltIns.dataClassArrayMemberToStringSymbol, context.irBuiltIns.stringType).apply { putValueArgument(0, irPropertyValue) } @@ -257,10 +246,16 @@ abstract class DataClassMembersGenerator( } } - fun getProperty(property: PropertyDescriptor): IrProperty = + fun getIrProperty(property: PropertyDescriptor): IrProperty = irPropertiesByDescriptor[property] ?: throw AssertionError("Class: ${irClass.descriptor}: unexpected property descriptor: $property") + fun getBackingField(property: PropertyDescriptor): IrField = + getIrProperty(property).backingField!! + + val IrClassifierSymbol?.isArrayOrPrimitiveArray: Boolean + get() = this == context.irBuiltIns.arrayClass || this in context.irBuiltIns.primitiveArrays + abstract fun declareSimpleFunction(startOffset: Int, endOffset: Int, functionDescriptor: FunctionDescriptor): IrFunction abstract fun generateSyntheticFunctionParameterDeclarations(irFunction: IrFunction) @@ -336,60 +331,33 @@ abstract class DataClassMembersGenerator( // Entry for psi2ir fun generateEqualsMethod(function: FunctionDescriptor, properties: List) { buildMember(function) { - generateEqualsMethodBody(properties) + generateEqualsMethodBody(properties.map { getIrProperty(it) }) } } // Entry for fir2ir - fun generateEqualsMethod(irFunction: IrFunction, properties: List) { + fun generateEqualsMethod(irFunction: IrFunction, properties: List) { buildMember(irFunction) { generateEqualsMethodBody(properties) } } - private fun MemberScope.findHashCodeFunctionOrNull() = - getContributedFunctions(Name.identifier("hashCode"), NoLookupLocation.FROM_BACKEND) - .find { it.valueParameters.isEmpty() } + interface HashCodeFunctionInfo { + val symbol: IrSimpleFunctionSymbol + fun commitSubstituted(irMemberAccessExpression: IrMemberAccessExpression<*>) + } - private fun getHashCodeFunction(type: KotlinType): FunctionDescriptor = - type.memberScope.findHashCodeFunctionOrNull() - ?: context.builtIns.any.unsubstitutedMemberScope.findHashCodeFunctionOrNull()!! - - private fun getHashCodeFunction( - type: KotlinType, - symbolResolve: (FunctionDescriptor) -> IrSimpleFunctionSymbol - ): IrSimpleFunctionSymbol = - when (val typeConstructorDescriptor = type.constructor.declarationDescriptor) { - is ClassDescriptor -> - if (KotlinBuiltIns.isArrayOrPrimitiveArray(typeConstructorDescriptor)) - context.irBuiltIns.dataClassArrayMemberHashCodeSymbol - else - symbolResolve(getHashCodeFunction(type)) - - is TypeParameterDescriptor -> - getHashCodeFunction(typeConstructorDescriptor.representativeUpperBound, symbolResolve) - - else -> - throw AssertionError("Unexpected type: $type") - } - - private fun getHashCodeFunction( - property: PropertyDescriptor, - symbolResolve: (FunctionDescriptor) -> IrSimpleFunctionSymbol - ): IrSimpleFunctionSymbol = - getHashCodeFunction(property.type, symbolResolve) - - abstract fun commitSubstituted(irMemberAccessExpression: IrMemberAccessExpression<*>, descriptor: CallableDescriptor) + abstract fun getHashCodeFunctionInfo(type: IrType): HashCodeFunctionInfo // Entry for psi2ir fun generateHashCodeMethod(function: FunctionDescriptor, properties: List) { buildMember(function) { - generateHashCodeMethodBody(properties) + generateHashCodeMethodBody(properties.map { getIrProperty(it) }) } } // Entry for fir2ir - fun generateHashCodeMethod(irFunction: IrFunction, properties: List) { + fun generateHashCodeMethod(irFunction: IrFunction, properties: List) { buildMember(irFunction) { generateHashCodeMethodBody(properties) } @@ -398,12 +366,12 @@ abstract class DataClassMembersGenerator( // Entry for psi2ir fun generateToStringMethod(function: FunctionDescriptor, properties: List) { buildMember(function) { - generateToStringMethodBody(properties) + generateToStringMethodBody(properties.map { getIrProperty(it) }) } } // Entry for fir2ir - fun generateToStringMethod(irFunction: IrFunction, properties: List) { + fun generateToStringMethod(irFunction: IrFunction, properties: List) { buildMember(irFunction) { generateToStringMethodBody(properties) } diff --git a/compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassInGeneratedToString.kt b/compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassInGeneratedToString.kt index 28c6fa70476..e1a7fab3496 100644 --- a/compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassInGeneratedToString.kt +++ b/compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassInGeneratedToString.kt @@ -1,5 +1,4 @@ // !LANGUAGE: +InlineClasses -// IGNORE_BACKEND_FIR: JVM_IR // FILE: Z.kt inline class Z(val value: Int) diff --git a/compiler/testData/ir/irText/classes/inlineClassSyntheticMethods.fir.txt b/compiler/testData/ir/irText/classes/inlineClassSyntheticMethods.fir.txt index 35b49f52d51..dac9b0059cd 100644 --- a/compiler/testData/ir/irText/classes/inlineClassSyntheticMethods.fir.txt +++ b/compiler/testData/ir/irText/classes/inlineClassSyntheticMethods.fir.txt @@ -95,7 +95,7 @@ FILE fqName: fileName:/inlineClassSyntheticMethods.kt $this: VALUE_PARAMETER GENERATED_INLINE_CLASS_MEMBER name: type:.IC.IC> BLOCK_BODY RETURN type=kotlin.Nothing from='public open fun hashCode (): kotlin.Int declared in .IC' - CALL 'public open fun hashCode (): kotlin.Int declared in kotlin.Any' type=kotlin.Int origin=null + CALL 'public final fun hashCode (): kotlin.Int declared in .C' type=kotlin.Int origin=null $this: GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:c type:.C.IC> visibility:private [final]' type=.C.IC> origin=null receiver: GET_VAR ': .IC.IC> declared in .IC.hashCode' type=.IC.IC> origin=null FUN GENERATED_INLINE_CLASS_MEMBER name:toString visibility:public modality:OPEN <> ($this:.IC.IC>) returnType:kotlin.String diff --git a/compiler/testData/ir/irText/classes/lambdaInDataClassDefaultParameter.fir.txt b/compiler/testData/ir/irText/classes/lambdaInDataClassDefaultParameter.fir.txt index 921be13b44b..41833762667 100644 --- a/compiler/testData/ir/irText/classes/lambdaInDataClassDefaultParameter.fir.txt +++ b/compiler/testData/ir/irText/classes/lambdaInDataClassDefaultParameter.fir.txt @@ -81,7 +81,7 @@ FILE fqName: fileName:/lambdaInDataClassDefaultParameter.kt $this: VALUE_PARAMETER GENERATED_DATA_CLASS_MEMBER name: type:.A BLOCK_BODY RETURN type=kotlin.Nothing from='public open fun hashCode (): kotlin.Int declared in .A' - CALL 'public open fun hashCode (): kotlin.Int declared in kotlin.Any' type=kotlin.Int origin=null + CALL 'public open fun hashCode (): kotlin.Int [fake_override] declared in kotlin.Function2' type=kotlin.Int origin=null $this: GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:runA type:@[ExtensionFunctionType] kotlin.Function2<.A, kotlin.String, kotlin.Unit> visibility:private [final]' type=@[ExtensionFunctionType] kotlin.Function2<.A, kotlin.String, kotlin.Unit> origin=null receiver: GET_VAR ': .A declared in .A.hashCode' type=.A origin=null FUN GENERATED_DATA_CLASS_MEMBER name:toString visibility:public modality:OPEN <> ($this:.A) returnType:kotlin.String diff --git a/compiler/testData/ir/irText/firProblems/SignatureClash.fir.txt b/compiler/testData/ir/irText/firProblems/SignatureClash.fir.txt index b034dcca96b..c2fbb9ed3f7 100644 --- a/compiler/testData/ir/irText/firProblems/SignatureClash.fir.txt +++ b/compiler/testData/ir/irText/firProblems/SignatureClash.fir.txt @@ -169,7 +169,7 @@ FILE fqName: fileName:/SignatureClash.kt $this: VALUE_PARAMETER GENERATED_DATA_CLASS_MEMBER name: type:.DataClass BLOCK_BODY RETURN type=kotlin.Nothing from='public open fun hashCode (): kotlin.Int declared in .DataClass' - CALL 'public open fun hashCode (): kotlin.Int declared in kotlin.Any' type=kotlin.Int origin=null + CALL 'public open fun hashCode (): kotlin.Int [fake_override] declared in .Delegate' type=kotlin.Int origin=null $this: GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:delegate type:.Delegate visibility:private [final]' type=.Delegate origin=null receiver: GET_VAR ': .DataClass declared in .DataClass.hashCode' type=.DataClass origin=null FUN GENERATED_DATA_CLASS_MEMBER name:toString visibility:public modality:OPEN <> ($this:.DataClass) returnType:kotlin.String