FIR2IR: avoid descriptors computing hashCode

When synthesizing the hashCode function for data classes, descriptors
were used, in partcular, memberScope for primitive classes.
IrBasedDescriptors have no member scope, so we compute the hashCode
function based on IR structures.
This commit is contained in:
Georgy Bronnikov
2020-12-07 23:09:44 +03:00
parent b07dccb8d7
commit 076272f7ca
7 changed files with 144 additions and 81 deletions
@@ -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)
}
}
@@ -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<PropertyDescriptor>) {
fun generateEqualsMethodBody(properties: List<IrProperty>) {
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<PropertyDescriptor>) {
fun generateHashCodeMethodBody(properties: List<IrProperty>) {
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<PropertyDescriptor>) {
fun generateToStringMethodBody(properties: List<IrProperty>) {
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<PropertyDescriptor>) {
buildMember(function) {
generateEqualsMethodBody(properties)
generateEqualsMethodBody(properties.map { getIrProperty(it) })
}
}
// Entry for fir2ir
fun generateEqualsMethod(irFunction: IrFunction, properties: List<PropertyDescriptor>) {
fun generateEqualsMethod(irFunction: IrFunction, properties: List<IrProperty>) {
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<PropertyDescriptor>) {
buildMember(function) {
generateHashCodeMethodBody(properties)
generateHashCodeMethodBody(properties.map { getIrProperty(it) })
}
}
// Entry for fir2ir
fun generateHashCodeMethod(irFunction: IrFunction, properties: List<PropertyDescriptor>) {
fun generateHashCodeMethod(irFunction: IrFunction, properties: List<IrProperty>) {
buildMember(irFunction) {
generateHashCodeMethodBody(properties)
}
@@ -398,12 +366,12 @@ abstract class DataClassMembersGenerator(
// Entry for psi2ir
fun generateToStringMethod(function: FunctionDescriptor, properties: List<PropertyDescriptor>) {
buildMember(function) {
generateToStringMethodBody(properties)
generateToStringMethodBody(properties.map { getIrProperty(it) })
}
}
// Entry for fir2ir
fun generateToStringMethod(irFunction: IrFunction, properties: List<PropertyDescriptor>) {
fun generateToStringMethod(irFunction: IrFunction, properties: List<IrProperty>) {
buildMember(irFunction) {
generateToStringMethodBody(properties)
}