Improve support of custom equals in inline classes

- Ensure that typed equals parameter's type is a star projection of
  corresponding inline class

- Make possible to declare typed equals that returns 'Nothing'

- Forbid type parameters in typed equals operator declaration

^KT-54909 fixed
^KT-54910 fixed
This commit is contained in:
vladislav.grechko
2022-11-11 16:44:03 +01:00
parent 02484baf07
commit 36b8ba8df3
38 changed files with 369 additions and 136 deletions
@@ -6,11 +6,10 @@
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
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.IrGeneratorContextBase
import org.jetbrains.kotlin.ir.builders.declarations.addFunction
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.declarations.*
@@ -38,23 +37,11 @@ class MethodsFromAnyGeneratorForLowerings(val context: BackendContext, val irCla
fun createEqualsMethodDeclaration(): IrSimpleFunction =
irClass.addSyntheticFunction("equals", context.irBuiltIns.booleanType).apply {
overriddenSymbols = irClass.collectOverridenSymbols { it.isEquals(context) }
overriddenSymbols = irClass.collectOverridenSymbols { it.isEquals() }
addValueParameter("other", context.irBuiltIns.anyNType)
}
companion object {
fun IrFunction.isToString(): Boolean =
name.asString() == "toString" && extensionReceiverParameter == null && contextReceiverParametersCount == 0 && valueParameters.isEmpty()
fun IrFunction.isHashCode() =
name.asString() == "hashCode" && extensionReceiverParameter == null && contextReceiverParametersCount == 0 && valueParameters.isEmpty()
fun IrFunction.isEquals(context: BackendContext) =
name.asString() == "equals" &&
extensionReceiverParameter == null && contextReceiverParametersCount == 0 &&
valueParameters.singleOrNull()?.type == context.irBuiltIns.anyNType
fun IrClass.collectOverridenSymbols(predicate: (IrFunction) -> Boolean): List<IrSimpleFunctionSymbol> =
superTypes.mapNotNull { it.getClass()?.functions?.singleOrNull(predicate)?.symbol }
}
@@ -9,7 +9,6 @@ import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
import org.jetbrains.kotlin.backend.common.ScopeWithIr
import org.jetbrains.kotlin.backend.common.lower.MethodsFromAnyGeneratorForLowerings.Companion.isHashCode
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.DescriptorVisibility
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.lower.MethodsFromAnyGeneratorForLowerings.Companion.isEquals
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.irBlockBody
import org.jetbrains.kotlin.backend.common.lower.loops.forLoopsPhase
@@ -29,7 +28,6 @@ import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.InlineClassDescriptorResolver
import org.jetbrains.kotlin.resolve.JVM_INLINE_ANNOTATION_FQ_NAME
val jvmInlineClassPhase = makeIrFilePhase(
@@ -488,7 +486,7 @@ private class JvmInlineClassLowering(context: JvmBackendContext) : JvmValueClass
val right = function.valueParameters[1]
val type = left.type.unboxInlineClass()
val untypedEquals = valueClass.functions.single { it.isEquals(context) }
val untypedEquals = valueClass.functions.single { it.isEquals() }
function.body = context.createIrBuilder(valueClass.symbol).run {
val context = this@JvmInlineClassLowering.context
@@ -5,8 +5,6 @@
package org.jetbrains.kotlin.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.lower.MethodsFromAnyGeneratorForLowerings.Companion.isEquals
import org.jetbrains.kotlin.backend.common.lower.MethodsFromAnyGeneratorForLowerings.Companion.isToString
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.irCatch
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
@@ -705,7 +703,6 @@ private class JvmMultiFieldValueClassLowering(context: JvmBackendContext) : JvmV
}
}
if (expression.isSpecializedMFVCEqEq) {
val backendContext = context
return context.createIrBuilder(getCurrentScopeSymbol()).irBlock {
val leftArgument = expression.getValueArgument(0)!!
val rightArgument = expression.getValueArgument(1)!!
@@ -736,7 +733,7 @@ private class JvmMultiFieldValueClassLowering(context: JvmBackendContext) : JvmV
}
} else {
// left one is unboxed, right is not
val equals = leftClass.functions.single { it.isEquals(backendContext) }
val equals = leftClass.functions.single { it.isEquals() }
+irCall(equals).apply {
copyTypeArgumentsFrom(expression)
dispatchReceiver = leftArgument
@@ -184,7 +184,7 @@ internal abstract class JvmValueClassAbstractLowering(val context: JvmBackendCon
function,
replacement,
when {
function.isTypedEquals -> InlineClassAbi.mangledNameFor(function, mangleReturnTypes = false, useOldMangleRules = false)
function.isTypedEquals() -> InlineClassAbi.mangledNameFor(function, mangleReturnTypes = false, useOldMangleRules = false)
// If the original function has signature which need mangling we still need to replace it with a mangled version.
(!function.isFakeOverride || function.findInterfaceImplementation(context.state.jvmDefaultMode) != null) && when (specificMangle) {
SpecificMangle.Inline -> function.signatureRequiresMangling(includeInline = true, includeMFVC = false)
@@ -5,10 +5,7 @@
package org.jetbrains.kotlin.backend.jvm
import org.jetbrains.kotlin.backend.jvm.ir.classFileContainsMethod
import org.jetbrains.kotlin.backend.jvm.ir.extensionReceiverName
import org.jetbrains.kotlin.backend.jvm.ir.isStaticValueClassReplacement
import org.jetbrains.kotlin.backend.jvm.ir.parentClassId
import org.jetbrains.kotlin.backend.jvm.ir.*
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.Modality
@@ -64,7 +61,7 @@ class MemoizedInlineClassReplacements(
// Mangle all functions in the body of an inline class
it.parent.safeAs<IrClass>()?.isSingleFieldValueClass == true ->
when {
it.isTypedEquals -> createStaticReplacement(it).also {
it.isTypedEquals() -> createStaticReplacement(it).also {
it.name = InlineClassDescriptorResolver.SPECIALIZED_EQUALS_NAME
specializedEqualsCache.computeIfAbsent(it.parentAsClass) { it }
}
@@ -16,7 +16,6 @@ import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.overrides.FakeOverrideBuilderStrategy
import org.jetbrains.kotlin.ir.overrides.IrOverridingUtil
import org.jetbrains.kotlin.ir.overrides.IrUnimplementedOverridesStrategy
import org.jetbrains.kotlin.ir.overrides.IrUnimplementedOverridesStrategy.ProcessAsFakeOverrides
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.symbols.impl.IrPropertySymbolImpl
@@ -1311,10 +1310,24 @@ fun IrBuiltIns.getKFunctionType(returnType: IrType, parameterTypes: List<IrType>
fun IdSignature?.isComposite(): Boolean =
this is IdSignature.CompositeSignature
val IrFunction.isTypedEquals: Boolean
get() {
val parentClass = parent as? IrClass ?: return false
return name == OperatorNameConventions.EQUALS && returnType.isBoolean() && valueParameters.size == 1
&& (valueParameters[0].type.classFqName?.run { parentClass.hasEqualFqName(this) } ?: false)
&& contextReceiverParametersCount == 0 && extensionReceiverParameter == null && parentClass.isValue
}
fun IrFunction.isToString(): Boolean =
name.asString() == "toString" && extensionReceiverParameter == null && contextReceiverParametersCount == 0 && valueParameters.isEmpty()
fun IrFunction.isHashCode() =
name.asString() == "hashCode" && extensionReceiverParameter == null && contextReceiverParametersCount == 0 && valueParameters.isEmpty()
fun IrFunction.isEquals() =
name.asString() == "equals" &&
extensionReceiverParameter == null && contextReceiverParametersCount == 0 &&
valueParameters.singleOrNull()?.type?.isNullableAny() == true
fun IrFunction.isTypedEquals(): Boolean {
val parentClass = parent as? IrClass ?: return false
val enclosingClassStartProjection = parentClass.symbol.starProjectedType
return name == OperatorNameConventions.EQUALS
&& (returnType.isBoolean() || returnType.isNothing())
&& valueParameters.size == 1
&& (valueParameters[0].type == enclosingClassStartProjection)
&& contextReceiverParametersCount == 0 && extensionReceiverParameter == null
&& parentClass.isValue
}