FIR2IR: generate synthetic members for data class only if needed

This commit is contained in:
Jinseong Jeon
2020-04-14 10:40:16 -07:00
committed by Mikhail Glukhikh
parent acced52384
commit 07add635eb
9 changed files with 150 additions and 51 deletions
@@ -9,10 +9,7 @@ import com.intellij.psi.PsiCompiledElement
import org.jetbrains.kotlin.KtNodeTypes import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirClass import org.jetbrains.kotlin.fir.declarations.*
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.synthetic.FirSyntheticProperty import org.jetbrains.kotlin.fir.declarations.synthetic.FirSyntheticProperty
import org.jetbrains.kotlin.fir.expressions.FirConstExpression import org.jetbrains.kotlin.fir.expressions.FirConstExpression
import org.jetbrains.kotlin.fir.expressions.FirConstKind import org.jetbrains.kotlin.fir.expressions.FirConstKind
@@ -207,17 +204,57 @@ private fun FirConstKind<*>.toIrConstKind(): IrConstKind<*> = when (this) {
FirConstKind.IntegerLiteral, FirConstKind.UnsignedIntegerLiteral -> throw IllegalArgumentException() FirConstKind.IntegerLiteral, FirConstKind.UnsignedIntegerLiteral -> throw IllegalArgumentException()
} }
internal fun FirClass<*>.collectCallableNamesFromSupertypes(session: FirSession, result: MutableList<Name> = mutableListOf()): List<Name> { private val simpleDeclarationCollector: (FirDeclaration, MutableMap<Name, FirDeclaration>) -> 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<Name, FirDeclaration> = mutableMapOf(),
record: (FirDeclaration, MutableMap<Name, FirDeclaration>) -> Unit = simpleDeclarationCollector
): Set<Name> {
for (superTypeRef in superTypeRefs) { for (superTypeRef in superTypeRefs) {
superTypeRef.collectCallableNamesFromThisAndSupertypes(session, result) superTypeRef.collectDeclarationsFromThisAndSupertypes(session, result, record)
}
return result.keys
}
internal fun FirClass<*>.collectContributedFunctionsFromSupertypes(
session: FirSession,
result: MutableMap<Name, FirDeclaration> = mutableMapOf(),
record: (FirDeclaration, MutableMap<Name, FirDeclaration>) -> Unit = { declaration, map ->
if (declaration is FirSimpleFunction && declaration.body != null) {
map.putIfAbsent(declaration.name, declaration)
}
}
): Map<Name, FirDeclaration> {
for (superTypeRef in superTypeRefs) {
superTypeRef.collectDeclarationsFromThisAndSupertypes(session, result, record)
} }
return result return result
} }
private fun FirTypeRef.collectCallableNamesFromThisAndSupertypes( fun FirClass<*>.collectDeclarationsFromSupertypes(
session: FirSession, session: FirSession,
result: MutableList<Name> = mutableListOf() result: MutableMap<Name, FirDeclaration> = mutableMapOf(),
): List<Name> { record: (FirDeclaration, MutableMap<Name, FirDeclaration>) -> Unit = simpleDeclarationCollector
): Map<Name, FirDeclaration> {
for (superTypeRef in superTypeRefs) {
superTypeRef.collectDeclarationsFromThisAndSupertypes(session, result, record)
}
return result
}
private fun FirTypeRef.collectDeclarationsFromThisAndSupertypes(
session: FirSession,
result: MutableMap<Name, FirDeclaration> = mutableMapOf(),
record: (FirDeclaration, MutableMap<Name, FirDeclaration>) -> Unit = simpleDeclarationCollector
): Map<Name, FirDeclaration> {
if (this is FirResolvedTypeRef) { if (this is FirResolvedTypeRef) {
val superType = type val superType = type
if (superType is ConeClassLikeType) { if (superType is ConeClassLikeType) {
@@ -225,16 +262,13 @@ private fun FirTypeRef.collectCallableNamesFromThisAndSupertypes(
is FirClassSymbol -> { is FirClassSymbol -> {
val superClass = superSymbol.fir as FirClass<*> val superClass = superSymbol.fir as FirClass<*>
for (declaration in superClass.declarations) { for (declaration in superClass.declarations) {
when (declaration) { record(declaration, result)
is FirSimpleFunction -> result += declaration.name
is FirVariable<*> -> result += declaration.name
}
} }
superClass.collectCallableNamesFromSupertypes(session, result) superClass.collectDeclarationsFromSupertypes(session, result, record)
} }
is FirTypeAliasSymbol -> { is FirTypeAliasSymbol -> {
val superAlias = superSymbol.fir val superAlias = superSymbol.fir
superAlias.expandedTypeRef.collectCallableNamesFromThisAndSupertypes(session, result) superAlias.expandedTypeRef.collectDeclarationsFromThisAndSupertypes(session, result, record)
} }
} }
} }
@@ -132,3 +132,11 @@ class Fir2IrTypeConverter(
return classIdToSymbolMap[classId] ?: getArrayClassSymbol(classId) return classIdToSymbolMap[classId] ?: getArrayClassSymbol(classId)
} }
} }
fun FirTypeRef.toIrType(
typeConverter: Fir2IrTypeConverter,
typeContext: ConversionTypeContext = ConversionTypeContext.DEFAULT
): IrType =
with(typeConverter) {
toIrType(typeContext)
}
@@ -65,7 +65,7 @@ internal class ClassMemberGenerator(
// Add synthetic members *before* fake override generations. // Add synthetic members *before* fake override generations.
// Otherwise, redundant members, e.g., synthetic toString _and_ fake override toString, will be added. // Otherwise, redundant members, e.g., synthetic toString _and_ fake override toString, will be added.
if (irClass.isData && klass.getPrimaryConstructorIfAny() != null) { if (irClass.isData && klass.getPrimaryConstructorIfAny() != null) {
processedCallableNames += DataClassMembersGenerator(components).generateDataClassMembers(irClass) processedCallableNames += DataClassMembersGenerator(components).generateDataClassMembers(klass, irClass)
} }
with(fakeOverrideGenerator) { irClass.addFakeOverrides(klass, processedCallableNames) } with(fakeOverrideGenerator) { irClass.addFakeOverrides(klass, processedCallableNames) }
klass.declarations.forEach { klass.declarations.forEach {
@@ -7,7 +7,13 @@ package org.jetbrains.kotlin.fir.backend.generators
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.fir.backend.Fir2IrComponents 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.backend.declareThisReceiverParameter
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.IrGeneratorContextBase import org.jetbrains.kotlin.ir.builders.IrGeneratorContextBase
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.*
@@ -25,19 +31,19 @@ class DataClassMembersGenerator(val components: Fir2IrComponents) {
// TODO: generateInlineClassMembers // TODO: generateInlineClassMembers
fun generateDataClassMembers(irClass: IrClass): List<Name> = fun generateDataClassMembers(klass: FirClass<*>, irClass: IrClass): List<Name> =
MyDataClassMethodsGenerator(irClass, IrDeclarationOrigin.GENERATED_DATA_CLASS_MEMBER).generate() MyDataClassMethodsGenerator(klass, irClass, IrDeclarationOrigin.GENERATED_DATA_CLASS_MEMBER).generate()
fun generateDataClassComponentBody(irFunction: IrFunction) = fun generateDataClassComponentBody(irFunction: IrFunction) =
MyDataClassMethodsGenerator(irFunction.parentAsClass, IrDeclarationOrigin.GENERATED_DATA_CLASS_MEMBER) MyDataClassMethodsGenerator(null, irFunction.parentAsClass, IrDeclarationOrigin.GENERATED_DATA_CLASS_MEMBER)
.generateComponentBody(irFunction) .generateComponentBody(irFunction)
fun generateDataClassCopyBody(irFunction: IrFunction) = fun generateDataClassCopyBody(irFunction: IrFunction) =
MyDataClassMethodsGenerator(irFunction.parentAsClass, IrDeclarationOrigin.GENERATED_DATA_CLASS_MEMBER) MyDataClassMethodsGenerator(null, irFunction.parentAsClass, IrDeclarationOrigin.GENERATED_DATA_CLASS_MEMBER)
.generateCopyBody(irFunction) .generateCopyBody(irFunction)
private inner class MyDataClassMethodsGenerator( private inner class MyDataClassMethodsGenerator(
val klass: FirClass<*>?,
val irClass: IrClass, val irClass: IrClass,
val origin: IrDeclarationOrigin val origin: IrDeclarationOrigin
) { ) {
@@ -85,38 +91,91 @@ class DataClassMembersGenerator(val components: Fir2IrComponents) {
valueParameterDescriptor.bind(this) 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<Name> { fun generate(): List<Name> {
if (properties.isEmpty()) { if (properties.isEmpty() || klass == null) {
return emptyList() return emptyList()
} }
// TODO: generate equals, hashCode, and toString only if needed val result = mutableListOf<Name>()
val equalsFunction = createSyntheticIrFunction(
Name.identifier("equals"), val contributedFunctionsInThisType = klass.declarations.mapNotNull {
components.irBuiltIns.booleanType if (it is FirSimpleFunction && it.matchesDataClassSyntheticMemberSignatures) {
).apply { it.name
valueParameters = listOf( } else
createSyntheticIrParameter(this, Name.identifier("other"), components.irBuiltIns.anyNType) null
)
} }
irDataClassMembersGenerator.generateEqualsMethod(equalsFunction, properties) val nonOverridableContributedFunctionsInSupertypes =
irClass.declarations.add(equalsFunction) 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( if (!contributedFunctionsInThisType.contains(equalsName) &&
Name.identifier("hashCode"), !nonOverridableContributedFunctionsInSupertypes.containsKey(equalsName)
components.irBuiltIns.intType ) {
) result.add(equalsName)
irDataClassMembersGenerator.generateHashCodeMethod(hashCodeFunction, properties) val equalsFunction = createSyntheticIrFunction(
irClass.declarations.add(hashCodeFunction) 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( if (!contributedFunctionsInThisType.contains(hashCodeName) &&
Name.identifier("toString"), !nonOverridableContributedFunctionsInSupertypes.containsKey(hashCodeName)
components.irBuiltIns.stringType ) {
) result.add(hashCodeName)
irDataClassMembersGenerator.generateToStringMethod(toStringFunction, properties) val hashCodeFunction = createSyntheticIrFunction(
irClass.declarations.add(toStringFunction) 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) { fun generateComponentBody(irFunction: IrFunction) {
@@ -190,6 +249,9 @@ class DataClassMembersGenerator(val components: Fir2IrComponents) {
companion object { companion object {
private val copyName = Name.identifier("copy") 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 = fun isCopy(irFunction: IrFunction): Boolean =
irFunction.name == copyName irFunction.name == copyName
@@ -1,4 +1,3 @@
// IGNORE_BACKEND_FIR: JVM_IR
data class A(val x: Int) { data class A(val x: Int) {
override fun equals(other: Any?): Boolean = false override fun equals(other: Any?): Boolean = false
} }
@@ -1,4 +1,3 @@
// IGNORE_BACKEND_FIR: JVM_IR
// TARGET_BACKEND: JVM // TARGET_BACKEND: JVM
// WITH_RUNTIME // WITH_RUNTIME
@@ -1,4 +1,3 @@
// IGNORE_BACKEND_FIR: JVM_IR
data class A(val x: Int) { data class A(val x: Int) {
override fun hashCode(): Int = -3 override fun hashCode(): Int = -3
} }
@@ -1,4 +1,3 @@
// IGNORE_BACKEND_FIR: JVM_IR
abstract class Base { abstract class Base {
final override fun toString() = "OK" final override fun toString() = "OK"
final override fun hashCode() = 42 final override fun hashCode() = 42
@@ -1,4 +1,3 @@
// IGNORE_BACKEND_FIR: JVM_IR
data class A(val x: Int) { data class A(val x: Int) {
override fun toString(): String = "!" override fun toString(): String = "!"
} }