diff --git a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/ConversionUtils.kt b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/ConversionUtils.kt index 236b7ff6a55..0a32bbc8a22 100644 --- a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/ConversionUtils.kt +++ b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/ConversionUtils.kt @@ -9,10 +9,7 @@ import com.intellij.psi.PsiCompiledElement import org.jetbrains.kotlin.KtNodeTypes import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.FirSession -import org.jetbrains.kotlin.fir.declarations.FirClass -import org.jetbrains.kotlin.fir.declarations.FirConstructor -import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction -import org.jetbrains.kotlin.fir.declarations.FirVariable +import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.synthetic.FirSyntheticProperty import org.jetbrains.kotlin.fir.expressions.FirConstExpression import org.jetbrains.kotlin.fir.expressions.FirConstKind @@ -207,17 +204,57 @@ private fun FirConstKind<*>.toIrConstKind(): IrConstKind<*> = when (this) { FirConstKind.IntegerLiteral, FirConstKind.UnsignedIntegerLiteral -> throw IllegalArgumentException() } -internal fun FirClass<*>.collectCallableNamesFromSupertypes(session: FirSession, result: MutableList = mutableListOf()): List { +private val simpleDeclarationCollector: (FirDeclaration, MutableMap) -> Unit = { declaration, map -> + when (declaration) { + is FirSimpleFunction -> + map.putIfAbsent(declaration.name, declaration) + is FirVariable<*> -> + map.putIfAbsent(declaration.name, declaration) + } +} + +internal fun FirClass<*>.collectCallableNamesFromSupertypes( + session: FirSession, + result: MutableMap = mutableMapOf(), + record: (FirDeclaration, MutableMap) -> Unit = simpleDeclarationCollector +): Set { for (superTypeRef in superTypeRefs) { - superTypeRef.collectCallableNamesFromThisAndSupertypes(session, result) + superTypeRef.collectDeclarationsFromThisAndSupertypes(session, result, record) + } + return result.keys +} + +internal fun FirClass<*>.collectContributedFunctionsFromSupertypes( + session: FirSession, + result: MutableMap = mutableMapOf(), + record: (FirDeclaration, MutableMap) -> Unit = { declaration, map -> + if (declaration is FirSimpleFunction && declaration.body != null) { + map.putIfAbsent(declaration.name, declaration) + } + } +): Map { + for (superTypeRef in superTypeRefs) { + superTypeRef.collectDeclarationsFromThisAndSupertypes(session, result, record) } return result } -private fun FirTypeRef.collectCallableNamesFromThisAndSupertypes( +fun FirClass<*>.collectDeclarationsFromSupertypes( session: FirSession, - result: MutableList = mutableListOf() -): List { + result: MutableMap = mutableMapOf(), + record: (FirDeclaration, MutableMap) -> Unit = simpleDeclarationCollector +): Map { + for (superTypeRef in superTypeRefs) { + superTypeRef.collectDeclarationsFromThisAndSupertypes(session, result, record) + } + return result +} + +private fun FirTypeRef.collectDeclarationsFromThisAndSupertypes( + session: FirSession, + result: MutableMap = mutableMapOf(), + record: (FirDeclaration, MutableMap) -> Unit = simpleDeclarationCollector +): Map { if (this is FirResolvedTypeRef) { val superType = type if (superType is ConeClassLikeType) { @@ -225,16 +262,13 @@ private fun FirTypeRef.collectCallableNamesFromThisAndSupertypes( is FirClassSymbol -> { val superClass = superSymbol.fir as FirClass<*> for (declaration in superClass.declarations) { - when (declaration) { - is FirSimpleFunction -> result += declaration.name - is FirVariable<*> -> result += declaration.name - } + record(declaration, result) } - superClass.collectCallableNamesFromSupertypes(session, result) + superClass.collectDeclarationsFromSupertypes(session, result, record) } is FirTypeAliasSymbol -> { val superAlias = superSymbol.fir - superAlias.expandedTypeRef.collectCallableNamesFromThisAndSupertypes(session, result) + superAlias.expandedTypeRef.collectDeclarationsFromThisAndSupertypes(session, result, record) } } } diff --git a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrTypeConverter.kt b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrTypeConverter.kt index e29cb0955c9..98d55c395e5 100644 --- a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrTypeConverter.kt +++ b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrTypeConverter.kt @@ -131,4 +131,12 @@ class Fir2IrTypeConverter( private fun getBuiltInClassSymbol(classId: ClassId?): IrClassSymbol? { return classIdToSymbolMap[classId] ?: getArrayClassSymbol(classId) } -} \ No newline at end of file +} + +fun FirTypeRef.toIrType( + typeConverter: Fir2IrTypeConverter, + typeContext: ConversionTypeContext = ConversionTypeContext.DEFAULT +): IrType = + with(typeConverter) { + toIrType(typeContext) + } diff --git a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/ClassMemberGenerator.kt b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/ClassMemberGenerator.kt index 4a8c5b3daab..282d14eafe5 100644 --- a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/ClassMemberGenerator.kt +++ b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/ClassMemberGenerator.kt @@ -65,7 +65,7 @@ internal class ClassMemberGenerator( // Add synthetic members *before* fake override generations. // Otherwise, redundant members, e.g., synthetic toString _and_ fake override toString, will be added. if (irClass.isData && klass.getPrimaryConstructorIfAny() != null) { - processedCallableNames += DataClassMembersGenerator(components).generateDataClassMembers(irClass) + processedCallableNames += DataClassMembersGenerator(components).generateDataClassMembers(klass, irClass) } with(fakeOverrideGenerator) { irClass.addFakeOverrides(klass, processedCallableNames) } klass.declarations.forEach { 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 1efc451de79..031f60f7366 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 @@ -7,7 +7,13 @@ package org.jetbrains.kotlin.fir.backend.generators import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.fir.backend.Fir2IrComponents +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.fir.backend.* import org.jetbrains.kotlin.fir.backend.declareThisReceiverParameter +import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.builders.IrGeneratorContextBase import org.jetbrains.kotlin.ir.declarations.* @@ -25,19 +31,19 @@ class DataClassMembersGenerator(val components: Fir2IrComponents) { // TODO: generateInlineClassMembers - fun generateDataClassMembers(irClass: IrClass): List = - MyDataClassMethodsGenerator(irClass, IrDeclarationOrigin.GENERATED_DATA_CLASS_MEMBER).generate() + fun generateDataClassMembers(klass: FirClass<*>, irClass: IrClass): List = + MyDataClassMethodsGenerator(klass, irClass, IrDeclarationOrigin.GENERATED_DATA_CLASS_MEMBER).generate() fun generateDataClassComponentBody(irFunction: IrFunction) = - MyDataClassMethodsGenerator(irFunction.parentAsClass, IrDeclarationOrigin.GENERATED_DATA_CLASS_MEMBER) + MyDataClassMethodsGenerator(null, irFunction.parentAsClass, IrDeclarationOrigin.GENERATED_DATA_CLASS_MEMBER) .generateComponentBody(irFunction) fun generateDataClassCopyBody(irFunction: IrFunction) = - MyDataClassMethodsGenerator(irFunction.parentAsClass, IrDeclarationOrigin.GENERATED_DATA_CLASS_MEMBER) + MyDataClassMethodsGenerator(null, irFunction.parentAsClass, IrDeclarationOrigin.GENERATED_DATA_CLASS_MEMBER) .generateCopyBody(irFunction) - private inner class MyDataClassMethodsGenerator( + val klass: FirClass<*>?, val irClass: IrClass, val origin: IrDeclarationOrigin ) { @@ -85,38 +91,91 @@ class DataClassMembersGenerator(val components: Fir2IrComponents) { valueParameterDescriptor.bind(this) } + + private val FirSimpleFunction.matchesEqualsSignature: Boolean + get() = valueParameters.size == 1 && + valueParameters[0].returnTypeRef.toIrType(components.typeConverter) == components.irBuiltIns.anyNType && + returnTypeRef.toIrType(components.typeConverter) == components.irBuiltIns.booleanType + + private val FirSimpleFunction.matchesHashCodeSignature: Boolean + get() = valueParameters.isEmpty() && + returnTypeRef.toIrType(components.typeConverter) == components.irBuiltIns.intType + + private val FirSimpleFunction.matchesToStringSignature: Boolean + get() = valueParameters.isEmpty() && + returnTypeRef.toIrType(components.typeConverter) == components.irBuiltIns.stringType + + private val FirSimpleFunction.matchesDataClassSyntheticMemberSignatures: Boolean + get() = (this.name == equalsName && matchesEqualsSignature) || + (this.name == hashCodeName && matchesHashCodeSignature) || + (this.name == toStringName && matchesToStringSignature) + fun generate(): List { - if (properties.isEmpty()) { + if (properties.isEmpty() || klass == null) { return emptyList() } - // TODO: generate equals, hashCode, and toString only if needed - val equalsFunction = createSyntheticIrFunction( - Name.identifier("equals"), - components.irBuiltIns.booleanType - ).apply { - valueParameters = listOf( - createSyntheticIrParameter(this, Name.identifier("other"), components.irBuiltIns.anyNType) - ) + val result = mutableListOf() + + val contributedFunctionsInThisType = klass.declarations.mapNotNull { + if (it is FirSimpleFunction && it.matchesDataClassSyntheticMemberSignatures) { + it.name + } else + null } - irDataClassMembersGenerator.generateEqualsMethod(equalsFunction, properties) - irClass.declarations.add(equalsFunction) + val nonOverridableContributedFunctionsInSupertypes = + klass.collectContributedFunctionsFromSupertypes(components.session) { declaration, map -> + if (declaration is FirSimpleFunction && + declaration.body != null && + !Visibilities.isPrivate(declaration.visibility) && + declaration.modality == Modality.FINAL && + declaration.matchesDataClassSyntheticMemberSignatures + ) { + map.putIfAbsent(declaration.name, declaration) + } + } - val hashCodeFunction = createSyntheticIrFunction( - Name.identifier("hashCode"), - components.irBuiltIns.intType - ) - irDataClassMembersGenerator.generateHashCodeMethod(hashCodeFunction, properties) - irClass.declarations.add(hashCodeFunction) + if (!contributedFunctionsInThisType.contains(equalsName) && + !nonOverridableContributedFunctionsInSupertypes.containsKey(equalsName) + ) { + result.add(equalsName) + val equalsFunction = createSyntheticIrFunction( + equalsName, + components.irBuiltIns.booleanType, + ).apply { + valueParameters = listOf( + createSyntheticIrParameter(this, Name.identifier("other"), components.irBuiltIns.anyNType) + ) + } + irDataClassMembersGenerator.generateEqualsMethod(equalsFunction, properties) + irClass.declarations.add(equalsFunction) + } - val toStringFunction = createSyntheticIrFunction( - Name.identifier("toString"), - components.irBuiltIns.stringType - ) - irDataClassMembersGenerator.generateToStringMethod(toStringFunction, properties) - irClass.declarations.add(toStringFunction) + if (!contributedFunctionsInThisType.contains(hashCodeName) && + !nonOverridableContributedFunctionsInSupertypes.containsKey(hashCodeName) + ) { + result.add(hashCodeName) + val hashCodeFunction = createSyntheticIrFunction( + hashCodeName, + components.irBuiltIns.intType, + ) + irDataClassMembersGenerator.generateHashCodeMethod(hashCodeFunction, properties) + irClass.declarations.add(hashCodeFunction) + } - return listOf(equalsFunction.name, hashCodeFunction.name, toStringFunction.name) + if (!contributedFunctionsInThisType.contains(toStringName) && + !nonOverridableContributedFunctionsInSupertypes.containsKey(toStringName) + ) { + result.add(toStringName) + val toStringFunction = createSyntheticIrFunction( + toStringName, + components.irBuiltIns.stringType, + ) + irDataClassMembersGenerator.generateToStringMethod(toStringFunction, properties) + irClass.declarations.add(toStringFunction) + } + + return result } fun generateComponentBody(irFunction: IrFunction) { @@ -190,6 +249,9 @@ class DataClassMembersGenerator(val components: Fir2IrComponents) { companion object { private val copyName = Name.identifier("copy") + private val equalsName = Name.identifier("equals") + private val hashCodeName = Name.identifier("hashCode") + private val toStringName = Name.identifier("toString") fun isCopy(irFunction: IrFunction): Boolean = irFunction.name == copyName diff --git a/compiler/testData/codegen/box/dataClasses/equals/alreadyDeclared.kt b/compiler/testData/codegen/box/dataClasses/equals/alreadyDeclared.kt index 400845b819f..65e9e5b9bcf 100644 --- a/compiler/testData/codegen/box/dataClasses/equals/alreadyDeclared.kt +++ b/compiler/testData/codegen/box/dataClasses/equals/alreadyDeclared.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND_FIR: JVM_IR data class A(val x: Int) { override fun equals(other: Any?): Boolean = false } diff --git a/compiler/testData/codegen/box/dataClasses/equals/alreadyDeclaredWrongSignature.kt b/compiler/testData/codegen/box/dataClasses/equals/alreadyDeclaredWrongSignature.kt index 22e4c8d8589..f9110e47bc8 100644 --- a/compiler/testData/codegen/box/dataClasses/equals/alreadyDeclaredWrongSignature.kt +++ b/compiler/testData/codegen/box/dataClasses/equals/alreadyDeclaredWrongSignature.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND_FIR: JVM_IR // TARGET_BACKEND: JVM // WITH_RUNTIME diff --git a/compiler/testData/codegen/box/dataClasses/hashCode/alreadyDeclared.kt b/compiler/testData/codegen/box/dataClasses/hashCode/alreadyDeclared.kt index 37aa97dd82d..ee7b621c974 100644 --- a/compiler/testData/codegen/box/dataClasses/hashCode/alreadyDeclared.kt +++ b/compiler/testData/codegen/box/dataClasses/hashCode/alreadyDeclared.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND_FIR: JVM_IR data class A(val x: Int) { override fun hashCode(): Int = -3 } diff --git a/compiler/testData/codegen/box/dataClasses/nonTrivialFinalMemberInSuperClass.kt b/compiler/testData/codegen/box/dataClasses/nonTrivialFinalMemberInSuperClass.kt index f16b89a55c5..4e655f2d38e 100644 --- a/compiler/testData/codegen/box/dataClasses/nonTrivialFinalMemberInSuperClass.kt +++ b/compiler/testData/codegen/box/dataClasses/nonTrivialFinalMemberInSuperClass.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND_FIR: JVM_IR abstract class Base { final override fun toString() = "OK" final override fun hashCode() = 42 diff --git a/compiler/testData/codegen/box/dataClasses/toString/alreadyDeclared.kt b/compiler/testData/codegen/box/dataClasses/toString/alreadyDeclared.kt index cf068564eb3..f95ea92633c 100644 --- a/compiler/testData/codegen/box/dataClasses/toString/alreadyDeclared.kt +++ b/compiler/testData/codegen/box/dataClasses/toString/alreadyDeclared.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND_FIR: JVM_IR data class A(val x: Int) { override fun toString(): String = "!" }