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
@@ -3884,7 +3884,7 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
}
add(FirErrors.INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS) { firDiagnostic ->
InefficientEqualsOverridingInInlineClassImpl(
firDiagnostic.a,
firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.a),
firDiagnostic as KtPsiDiagnostic,
token,
)
@@ -2710,7 +2710,7 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
abstract class InefficientEqualsOverridingInInlineClass : KtFirDiagnostic<KtNamedFunction>() {
override val diagnosticClass get() = InefficientEqualsOverridingInInlineClass::class
abstract val className: String
abstract val type: KtType
}
abstract class CannotAllUnderImportFromSingleton : KtFirDiagnostic<KtImportDirective>() {
@@ -3267,7 +3267,7 @@ internal class RedundantInlineSuspendFunctionTypeImpl(
) : KtFirDiagnostic.RedundantInlineSuspendFunctionType(), KtAbstractFirDiagnostic<KtElement>
internal class InefficientEqualsOverridingInInlineClassImpl(
override val className: String,
override val type: KtType,
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
) : KtFirDiagnostic.InefficientEqualsOverridingInInlineClass(), KtAbstractFirDiagnostic<KtNamedFunction>
@@ -18011,9 +18011,9 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag
}
@Test
@TestMetadata("typedEqualsOperatorModifierInInlineClass.kt")
public void testTypedEqualsOperatorModifierInInlineClass() throws Exception {
runTest("compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.kt");
@TestMetadata("typedEqualsOperatorDeclarationCheck.kt")
public void testTypedEqualsOperatorDeclarationCheck() throws Exception {
runTest("compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorDeclarationCheck.kt");
}
@Test
@@ -18011,9 +18011,9 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti
}
@Test
@TestMetadata("typedEqualsOperatorModifierInInlineClass.kt")
public void testTypedEqualsOperatorModifierInInlineClass() throws Exception {
runTest("compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.kt");
@TestMetadata("typedEqualsOperatorDeclarationCheck.kt")
public void testTypedEqualsOperatorDeclarationCheck() throws Exception {
runTest("compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorDeclarationCheck.kt");
}
@Test
@@ -18011,9 +18011,9 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac
}
@Test
@TestMetadata("typedEqualsOperatorModifierInInlineClass.kt")
public void testTypedEqualsOperatorModifierInInlineClass() throws Exception {
runTest("compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.kt");
@TestMetadata("typedEqualsOperatorDeclarationCheck.kt")
public void testTypedEqualsOperatorDeclarationCheck() throws Exception {
runTest("compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorDeclarationCheck.kt");
}
@Test
@@ -1400,7 +1400,7 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
val REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE by warning<KtElement>(PositioningStrategy.SUSPEND_MODIFIER)
val INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS by warning<KtNamedFunction>(PositioningStrategy.DECLARATION_NAME) {
parameter<String>("className")
parameter<ConeKotlinType>("type")
}
}
@@ -724,7 +724,7 @@ object FirErrors {
val ILLEGAL_INLINE_PARAMETER_MODIFIER by error0<KtElement>(SourceElementPositioningStrategies.INLINE_PARAMETER_MODIFIER)
val INLINE_SUSPEND_FUNCTION_TYPE_UNSUPPORTED by error0<KtParameter>()
val REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE by warning0<KtElement>(SourceElementPositioningStrategies.SUSPEND_MODIFIER)
val INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS by warning1<KtNamedFunction, String>(SourceElementPositioningStrategies.DECLARATION_NAME)
val INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS by warning1<KtNamedFunction, ConeKotlinType>(SourceElementPositioningStrategies.DECLARATION_NAME)
// Imports
val CANNOT_ALL_UNDER_IMPORT_FROM_SINGLETON by error1<KtImportDirective, Name>(SourceElementPositioningStrategies.IMPORT_LAST_NAME)
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.*
import org.jetbrains.kotlin.fir.expressions.toResolvedCallableSymbol
import org.jetbrains.kotlin.fir.resolve.defaultType
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.resolve.isEquals
import org.jetbrains.kotlin.fir.resolve.lookupSuperTypes
@@ -205,19 +206,28 @@ object FirInlineClassDeclarationChecker : FirRegularClassChecker() {
if (context.languageVersionSettings.supportsFeature(LanguageFeature.CustomEqualsInInlineClasses)) {
var equalsFromAnyOverriding: FirSimpleFunction? = null
var typedEqualsIsDefined = false
var typedEquals: FirSimpleFunction? = null
declaration.declarations.forEach {
if (it !is FirSimpleFunction) {
return@forEach
}
if (it.isEquals()) equalsFromAnyOverriding = it
if (it.isTypedEqualsInInlineClass(context.session)) typedEqualsIsDefined = true
if (it.isTypedEqualsInInlineClass(context.session)) typedEquals = it
}
if (equalsFromAnyOverriding != null && !typedEqualsIsDefined) {
if (typedEquals?.typeParameters?.isNotEmpty() == true) {
reporter.reportOn(
typedEquals!!.source,
FirErrors.TYPE_PARAMETERS_NOT_ALLOWED,
context
)
}
if (equalsFromAnyOverriding != null && typedEquals == null) {
reporter.reportOn(
equalsFromAnyOverriding!!.source,
FirErrors.INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS,
declaration.name.asString(),
declaration.defaultType().replaceArgumentsWithStarProjections(),
context
)
}
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.fir.containingClassLookupTag
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
import org.jetbrains.kotlin.fir.declarations.utils.isInline
import org.jetbrains.kotlin.fir.declarations.utils.isOperator
import org.jetbrains.kotlin.fir.resolve.defaultType
import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.ConeClassLikeLookupTagImpl
import org.jetbrains.kotlin.fir.types.*
@@ -196,7 +197,9 @@ private object OperatorFunctionChecks {
return buildString {
append("must override ''equals()'' in Any")
if (customEqualsSupported && containingClassSymbol.isInline) {
append(" or define ''equals(other: ${containingClassSymbol.name}): Boolean''")
val expectedParameterTypeRendered =
containingClassSymbol.defaultType().replaceArgumentsWithStarProjections().renderReadable();
append(" or define ''equals(other: ${expectedParameterTypeRendered}): Boolean''")
}
}
}
@@ -10,16 +10,18 @@ import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.modality
import org.jetbrains.kotlin.fir.containingClassLookupTag
import org.jetbrains.kotlin.fir.containingClassForStaticMemberAttr
import org.jetbrains.kotlin.fir.containingClassLookupTag
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.*
import org.jetbrains.kotlin.fir.resolve.defaultType
import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.classId
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.fir.types.isBoolean
import org.jetbrains.kotlin.fir.types.isNothing
import org.jetbrains.kotlin.fir.types.replaceArgumentsWithStarProjections
import org.jetbrains.kotlin.util.OperatorNameConventions
internal fun isInsideExpectClass(containingClass: FirClass, context: CheckerContext): Boolean {
@@ -118,10 +120,13 @@ fun FirClassSymbol<*>.primaryConstructorSymbol(): FirConstructorSymbol? {
fun FirSimpleFunction.isTypedEqualsInInlineClass(session: FirSession): Boolean =
containingClassLookupTag()?.toFirRegularClassSymbol(session)?.run {
val inlineClassStarProjection = this@run.defaultType().replaceArgumentsWithStarProjections()
with(this@isTypedEqualsInInlineClass) {
contextReceivers.isEmpty() && receiverParameter == null && name == OperatorNameConventions.EQUALS
&& this@run.isInline && valueParameters.size == 1 && returnTypeRef.isBoolean
&& valueParameters[0].returnTypeRef.coneType.classId == this@run.classId
contextReceivers.isEmpty() && receiverParameter == null
&& name == OperatorNameConventions.EQUALS
&& this@run.isInline && valueParameters.size == 1
&& (returnTypeRef.isBoolean || returnTypeRef.isNothing)
&& valueParameters[0].returnTypeRef.coneType == inlineClassStarProjection
}
} ?: false
@@ -2011,8 +2011,8 @@ object FirErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
map.put(
INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS,
"Overriding ''equals'' from ''Any'' in inline class alongside with lack of ''equals(other: {0}): Boolean'' leads to boxing on every equality comparison",
STRING
"Overriding ''equals'' from ''Any'' in inline class without operator ''equals(other: {0}): Boolean'' leads to boxing on every equality comparison",
RENDER_TYPE
)
//imports
@@ -426,7 +426,7 @@ public interface Errors {
DiagnosticFactory0<PsiElement> VALUE_CLASS_CANNOT_BE_CLONEABLE = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> INLINE_CLASS_DEPRECATED = DiagnosticFactory0.create(WARNING);
DiagnosticFactory0<KtContextReceiverList> INLINE_CLASS_CANNOT_HAVE_CONTEXT_RECEIVERS = DiagnosticFactory0.create(ERROR);
DiagnosticFactory1<KtNamedFunction, String> INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS =
DiagnosticFactory1<KtNamedFunction, KotlinType> INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS =
DiagnosticFactory1.create(WARNING, DECLARATION_NAME);
// Result class
@@ -802,8 +802,8 @@ public class DefaultErrorMessages {
MAP.put(INLINE_CLASS_DEPRECATED, "'inline' modifier is deprecated. Use 'value' instead");
MAP.put(INLINE_CLASS_CANNOT_HAVE_CONTEXT_RECEIVERS, "Inline classes cannot have context receivers");
MAP.put(INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS,
"Overriding ''equals'' from ''Any'' in inline class alongside with lack of ''equals(other: {0}): Boolean'' leads to boxing on every equality comparison",
STRING);
"Overriding ''equals'' from ''Any'' in inline class without operator ''equals(other: {0}): Boolean'' leads to boxing on every equality comparison",
RENDER_TYPE);
MAP.put(RESULT_CLASS_IN_RETURN_TYPE, "'kotlin.Result' cannot be used as a return type");
MAP.put(RESULT_CLASS_WITH_NULLABLE_OPERATOR, "Expression of type ''kotlin.Result'' cannot be used as a left operand of ''{0}''", STRING);
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.isNothing
import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
import org.jetbrains.kotlin.types.typeUtil.isUnit
import org.jetbrains.kotlin.types.typeUtil.replaceArgumentsWithStarProjections
private val javaLangCloneable = FqNameUnsafe("java.lang.Cloneable")
@@ -169,9 +170,20 @@ object InlineClassDeclarationChecker : DeclarationChecker {
fun KtClass.namedFunctions() = declarations.filterIsInstance<KtNamedFunction>()
if (context.languageVersionSettings.supportsFeature(LanguageFeature.CustomEqualsInInlineClasses)) {
val typedEquals = declaration.namedFunctions().firstOrNull { isTypedEquals(it) }
if (typedEquals?.typeParameters?.isNotEmpty() == true) {
trace.report(Errors.TYPE_PARAMETERS_NOT_ALLOWED.on(typedEquals))
}
declaration.namedFunctions().singleOrNull { isUntypedEquals(it) }?.apply {
if (declaration.namedFunctions().none { isTypedEquals(it) }) {
trace.report(Errors.INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS.on(this@apply, descriptor.name.asString()))
if (typedEquals == null) {
trace.report(
Errors.INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS.on(
this@apply,
descriptor.defaultType.replaceArgumentsWithStarProjections()
)
)
}
}
}
@@ -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
}
@@ -81,8 +81,47 @@ public final class IC4 {
public synthetic final method unbox-impl(): int
}
@kotlin.jvm.JvmInline
@kotlin.Metadata
public final class IC5 {
// source: 'inlineClassEqualsOverride.kt'
private final field value: int
private synthetic method <init>(p0: int): void
public synthetic final static method box-impl(p0: int): IC5
public static method constructor-impl(p0: int): int
public method equals(p0: java.lang.Object): boolean
public static method equals-impl(p0: int, p1: java.lang.Object): boolean
public final static @org.jetbrains.annotations.NotNull method equals-impl0(p0: int, p1: int): java.lang.Void
public final method getValue(): int
public method hashCode(): int
public static method hashCode-impl(p0: int): int
public method toString(): java.lang.String
public static method toString-impl(p0: int): java.lang.String
public synthetic final method unbox-impl(): int
}
@kotlin.jvm.JvmInline
@kotlin.Metadata
public final class IC6 {
// source: 'inlineClassEqualsOverride.kt'
private final field value: int
private synthetic method <init>(p0: int): void
public synthetic final static method box-impl(p0: int): IC6
public static method constructor-impl(p0: int): int
public final @org.jetbrains.annotations.NotNull method equals(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Void
public final static @org.jetbrains.annotations.NotNull method equals-impl(p0: int, @org.jetbrains.annotations.Nullable p1: java.lang.Object): java.lang.Void
public final static method equals-impl0(p0: int, p1: int): boolean
public final method getValue(): int
public method hashCode(): int
public static method hashCode-impl(p0: int): int
public method toString(): java.lang.String
public static method toString-impl(p0: int): java.lang.String
public synthetic final method unbox-impl(): int
}
@kotlin.Metadata
public final class InlineClassEqualsOverrideKt {
// source: 'inlineClassEqualsOverride.kt'
public synthetic final static method assertThrows(p0: kotlin.jvm.functions.Function0): boolean
public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String
}
@@ -19,7 +19,7 @@ interface I {
OPTIONAL_JVM_INLINE_ANNOTATION
value class IC2(val value: Int) : I {
override fun equals(param: IC2): Boolean {
override operator fun equals(param: IC2): Boolean {
return abs(value - param.value) < 2
}
}
@@ -34,6 +34,26 @@ value class IC4(val value: Int) {
override fun equals(other: Any?) = TODO()
}
OPTIONAL_JVM_INLINE_ANNOTATION
value class IC5(val value: Int) {
operator fun equals(other: IC5): Nothing = TODO()
}
OPTIONAL_JVM_INLINE_ANNOTATION
value class IC6(val value: Int) {
override fun equals(other: Any?): Nothing = TODO()
}
inline fun <reified T> assertThrows(block: () -> Unit): Boolean {
try {
block.invoke()
} catch (t: Throwable) {
return t is T
}
return false
}
fun box() = when {
IC1(1.0) != IC1(1.05) -> "Fail 1.1"
(IC1(1.0) as Any) != IC1(1.05) -> "Fail 1.2"
@@ -68,5 +88,9 @@ fun box() = when {
IC1(1.0) == Any() -> "Fail 7.1"
(IC1(1.0) as Any) == Any() -> "Fail 7.2"
!assertThrows<NotImplementedError> { IC5(0) == IC5(1) } -> "Fail 8.1"
!assertThrows<NotImplementedError> { IC6(0) == IC6(1) } -> "Fail 8.2"
else -> "OK"
}
@@ -81,8 +81,47 @@ public final class IC4 {
public synthetic final method unbox-impl(): int
}
@kotlin.jvm.JvmInline
@kotlin.Metadata
public final class IC5 {
// source: 'inlineClassEqualsOverride.kt'
private final field value: int
private synthetic method <init>(p0: int): void
public synthetic final static method box-impl(p0: int): IC5
public static method constructor-impl(p0: int): int
public method equals(p0: java.lang.Object): boolean
public static method equals-impl(p0: int, p1: java.lang.Object): boolean
public final static @org.jetbrains.annotations.NotNull method equals-impl0(p0: int, p1: int): java.lang.Void
public final method getValue(): int
public method hashCode(): int
public static method hashCode-impl(p0: int): int
public method toString(): java.lang.String
public static method toString-impl(p0: int): java.lang.String
public synthetic final method unbox-impl(): int
}
@kotlin.jvm.JvmInline
@kotlin.Metadata
public final class IC6 {
// source: 'inlineClassEqualsOverride.kt'
private final field value: int
private synthetic method <init>(p0: int): void
public synthetic final static method box-impl(p0: int): IC6
public static method constructor-impl(p0: int): int
public @org.jetbrains.annotations.NotNull method equals(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Void
public static @org.jetbrains.annotations.NotNull method equals-impl(p0: int, @org.jetbrains.annotations.Nullable p1: java.lang.Object): java.lang.Void
public final static method equals-impl0(p0: int, p1: int): boolean
public final method getValue(): int
public method hashCode(): int
public static method hashCode-impl(p0: int): int
public method toString(): java.lang.String
public static method toString-impl(p0: int): java.lang.String
public synthetic final method unbox-impl(): int
}
@kotlin.Metadata
public final class InlineClassEqualsOverrideKt {
// source: 'inlineClassEqualsOverride.kt'
public synthetic final static method assertThrows(p0: kotlin.jvm.functions.Function0): boolean
public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String
}
@@ -7,7 +7,7 @@ import kotlin.math.abs
OPTIONAL_JVM_INLINE_ANNOTATION
value class IC(val x: Double) {
fun equals(other: IC): Boolean {
operator fun equals(other: IC): Boolean {
return abs(x - other.x) < 0.1
}
@@ -6,7 +6,7 @@
OPTIONAL_JVM_INLINE_ANNOTATION
value class IC1<T : Number>(val x: T) {
fun equals(other: Int) = false
fun equals(other: IC1<T>) = true
operator fun equals(other: IC1<*>) = true
}
class Generic<T, R>(val x: T, val y: R)
@@ -14,19 +14,19 @@ class Generic<T, R>(val x: T, val y: R)
OPTIONAL_JVM_INLINE_ANNOTATION
value class IC2<T, R>(val value: Generic<T, R>) {
fun equals(other: IC1<Double>) = false
fun equals(other: IC2<T, R>) = true
operator fun equals(other: IC2<*, *>) = true
}
OPTIONAL_JVM_INLINE_ANNOTATION
value class IC3<T>(val value: T) {
fun equals(other: Int) = false
fun equals(other: IC3<*>) = true
operator fun equals(other: IC3<*>) = true
}
OPTIONAL_JVM_INLINE_ANNOTATION
value class IC4<T>(val value: T) {
fun equals(other: String) = false
fun equals(other: IC4<String>) = true
operator fun equals(other: IC4<*>) = true
}
@@ -6,7 +6,7 @@
OPTIONAL_JVM_INLINE_ANNOTATION
value class A(val x: Int) {
fun equals(other: A) = true
operator fun equals(other: A) = true
}
class C
@@ -20,13 +20,13 @@ value class B2(val x: A?)
OPTIONAL_JVM_INLINE_ANNOTATION
value class D1(val x: C) {
fun equals(other: D1) = true
operator fun equals(other: D1) = true
}
OPTIONAL_JVM_INLINE_ANNOTATION
value class D2(val x: C?) {
fun equals(other: D2) = true
operator fun equals(other: D2) = true
}
OPTIONAL_JVM_INLINE_ANNOTATION
+1 -1
View File
@@ -5,7 +5,7 @@
OPTIONAL_JVM_INLINE_ANNOTATION
value class A(val x: Int) {
fun equals(other: A) = x % 5 == other.x % 5
operator fun equals(other: A) = x % 5 == other.x % 5
}
OPTIONAL_JVM_INLINE_ANNOTATION
@@ -22,7 +22,16 @@ value class IC2(val x: Int) {
value class IC3(val x: Int) {
override fun equals(other: Any?) = true
fun equals(other: IC3) = true
operator fun equals(other: IC3) = true
override fun hashCode() = 0
}
@JvmInline
value class IC4<T>(val x: Int) {
override fun <!INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS!>equals<!>(other: Any?) = true
fun equals(other: IC4<T>) = true
override fun hashCode() = 0
}
@@ -19,8 +19,18 @@ package
@kotlin.jvm.JvmInline public final value class IC3 {
public constructor IC3(/*0*/ x: kotlin.Int)
public final val x: kotlin.Int
public final fun equals(/*0*/ other: IC3): kotlin.Boolean
public final operator fun equals(/*0*/ other: IC3): kotlin.Boolean
public open override /*1*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
}
@kotlin.jvm.JvmInline public final value class IC4</*0*/ T> {
public constructor IC4</*0*/ T>(/*0*/ x: kotlin.Int)
public final val x: kotlin.Int
public final fun equals(/*0*/ other: IC4<T>): kotlin.Boolean
public open override /*1*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
}
@@ -0,0 +1,52 @@
// FIR_IDENTICAL
// WITH_STDLIB
// !DIAGNOSTICS: -DEBUG_INFO_SMARTCAST
// LANGUAGE: +CustomEqualsInInlineClasses
@JvmInline
value class IC1(val x: Int) {
override fun equals(other: Any?) = true
operator fun equals(other: IC1) = true
override fun hashCode() = 0
}
@JvmInline
value class IC2(val x: Int) {
<!INAPPLICABLE_OPERATOR_MODIFIER!>operator<!> fun equals(other: IC1) = true
<!INAPPLICABLE_OPERATOR_MODIFIER!>operator<!> fun equals(other: IC2) {
}
}
@JvmInline
value class IC3<T>(val x: T) {
<!INAPPLICABLE_OPERATOR_MODIFIER!>operator<!> fun equals(other: IC3<T>) = true
}
@JvmInline
value class IC4<T>(val x: T) {
<!INAPPLICABLE_OPERATOR_MODIFIER!>operator<!> fun equals(other: IC4<String>) = true
}
@JvmInline
value class IC5<T: Number>(val x: T) {
<!INAPPLICABLE_OPERATOR_MODIFIER!>operator<!> fun equals(other: T) = true
}
@JvmInline
value class IC6<T, R>(val x: T) {
operator fun<!TYPE_PARAMETERS_NOT_ALLOWED!><S1, S2><!> equals(other: IC6<*, *>) = true
}
@JvmInline
value class IC7<T, R>(val x: T) {
operator fun equals(other: IC7<*, *>) = true
}
@JvmInline
value class IC8<T, R>(val x: T) {
operator fun equals(other: IC8<*, *>): Nothing = TODO()
}
@@ -0,0 +1,74 @@
package
@kotlin.jvm.JvmInline public final value class IC1 {
public constructor IC1(/*0*/ x: kotlin.Int)
public final val x: kotlin.Int
public final operator fun equals(/*0*/ other: IC1): kotlin.Boolean
public open override /*1*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
}
@kotlin.jvm.JvmInline public final value class IC2 {
public constructor IC2(/*0*/ x: kotlin.Int)
public final val x: kotlin.Int
public final operator fun equals(/*0*/ other: IC1): kotlin.Boolean
public final operator fun equals(/*0*/ other: IC2): kotlin.Unit
public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
}
@kotlin.jvm.JvmInline public final value class IC3</*0*/ T> {
public constructor IC3</*0*/ T>(/*0*/ x: T)
public final val x: T
public final operator fun equals(/*0*/ other: IC3<T>): kotlin.Boolean
public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
}
@kotlin.jvm.JvmInline public final value class IC4</*0*/ T> {
public constructor IC4</*0*/ T>(/*0*/ x: T)
public final val x: T
public final operator fun equals(/*0*/ other: IC4<kotlin.String>): kotlin.Boolean
public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
}
@kotlin.jvm.JvmInline public final value class IC5</*0*/ T : kotlin.Number> {
public constructor IC5</*0*/ T : kotlin.Number>(/*0*/ x: T)
public final val x: T
public final operator fun equals(/*0*/ other: T): kotlin.Boolean
public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
}
@kotlin.jvm.JvmInline public final value class IC6</*0*/ T, /*1*/ R> {
public constructor IC6</*0*/ T, /*1*/ R>(/*0*/ x: T)
public final val x: T
public final operator fun </*0*/ S1, /*1*/ S2> equals(/*0*/ other: IC6<*, *>): kotlin.Boolean
public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
}
@kotlin.jvm.JvmInline public final value class IC7</*0*/ T, /*1*/ R> {
public constructor IC7</*0*/ T, /*1*/ R>(/*0*/ x: T)
public final val x: T
public final operator fun equals(/*0*/ other: IC7<*, *>): kotlin.Boolean
public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
}
@kotlin.jvm.JvmInline public final value class IC8</*0*/ T, /*1*/ R> {
public constructor IC8</*0*/ T, /*1*/ R>(/*0*/ x: T)
public final val x: T
public final operator fun equals(/*0*/ other: IC8<*, *>): kotlin.Nothing
public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
}
@@ -1,22 +0,0 @@
// FIR_IDENTICAL
// WITH_STDLIB
// !DIAGNOSTICS: -DEBUG_INFO_SMARTCAST
// LANGUAGE: +CustomEqualsInInlineClasses
@JvmInline
value class IC1(val x: Int) {
override fun equals(other: Any?) = true
operator fun equals(other: IC1) = true
override fun hashCode() = 0
}
@JvmInline
value class IC2(val x: Int) {
<!INAPPLICABLE_OPERATOR_MODIFIER!>operator<!> fun equals(other: IC1) = true
<!INAPPLICABLE_OPERATOR_MODIFIER!>operator<!> fun equals(other: IC2) {
}
}
@@ -1,20 +0,0 @@
package
@kotlin.jvm.JvmInline public final value class IC1 {
public constructor IC1(/*0*/ x: kotlin.Int)
public final val x: kotlin.Int
public final operator fun equals(/*0*/ other: IC1): kotlin.Boolean
public open override /*1*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
}
@kotlin.jvm.JvmInline public final value class IC2 {
public constructor IC2(/*0*/ x: kotlin.Int)
public final val x: kotlin.Int
public final operator fun equals(/*0*/ other: IC1): kotlin.Boolean
public final operator fun equals(/*0*/ other: IC2): kotlin.Unit
public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
}
@@ -18017,9 +18017,9 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
}
@Test
@TestMetadata("typedEqualsOperatorModifierInInlineClass.kt")
public void testTypedEqualsOperatorModifierInInlineClass() throws Exception {
runTest("compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.kt");
@TestMetadata("typedEqualsOperatorDeclarationCheck.kt")
public void testTypedEqualsOperatorDeclarationCheck() throws Exception {
runTest("compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorDeclarationCheck.kt");
}
@Test
@@ -10,14 +10,11 @@ import org.jetbrains.kotlin.incremental.components.LookupLocation
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.isInlineClass
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.KotlinTypeFactory
import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
import org.jetbrains.kotlin.types.typeUtil.isBoolean
import org.jetbrains.kotlin.types.typeUtil.isNullableAny
import org.jetbrains.kotlin.types.typeUtil.*
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.sure
import kotlin.contracts.ExperimentalContracts
@@ -94,10 +91,16 @@ fun DeclarationDescriptor.containingPackage(): FqName? {
object DeserializedDeclarationsFromSupertypeConflictDataKey : CallableDescriptor.UserDataKey<CallableMemberDescriptor>
fun FunctionDescriptor.isTypedEqualsInInlineClass() = name == OperatorNameConventions.EQUALS
&& (returnType?.isBoolean() ?: false) && containingDeclaration.isInlineClass()
&& valueParameters.size == 1 && valueParameters[0].type.constructor.declarationDescriptor.classId == (containingDeclaration as? ClassDescriptor)?.classId
&& contextReceiverParameters.isEmpty() && extensionReceiverParameter == null
fun FunctionDescriptor.isTypedEqualsInInlineClass(): Boolean {
val inlineClassStarProjection =
(containingDeclaration as? ClassDescriptor)?.takeIf { it.isInlineClass() }?.defaultType?.replaceArgumentsWithStarProjections()
?: return false
val returnType = returnType ?: return false
return name == OperatorNameConventions.EQUALS
&& (returnType.isBoolean() || returnType.isNothing())
&& valueParameters.size == 1 && valueParameters[0].type == inlineClassStarProjection
&& contextReceiverParameters.isEmpty() && extensionReceiverParameter == null
}
fun FunctionDescriptor.overridesEqualsFromAny(): Boolean = name == OperatorNameConventions.EQUALS
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.ReflectionTypes
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.resolve.descriptorUtil.declaresOrInheritsDefaultValue
@@ -29,6 +30,7 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitClassReceiver
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
import org.jetbrains.kotlin.types.typeUtil.replaceArgumentsWithStarProjections
import org.jetbrains.kotlin.util.MemberKindCheck.Member
import org.jetbrains.kotlin.util.MemberKindCheck.MemberOrExtension
import org.jetbrains.kotlin.util.OperatorNameConventions.ASSIGNMENT_OPERATIONS
@@ -198,12 +200,14 @@ object OperatorChecks : AbstractModifierChecks() {
Checks(RANGE_UNTIL, MemberOrExtension, SingleValueParameter, NoDefaultAndVarargsCheck),
Checks(EQUALS, Member) {
fun DeclarationDescriptor.isAny() = this is ClassDescriptor && KotlinBuiltIns.isAny(this)
ensure(containingDeclaration.isAny() || overriddenDescriptors.any { it.containingDeclaration.isAny() }
|| (containingDeclaration.isInlineClass() && isTypedEqualsInInlineClass())) {
ensure(containingDeclaration.isAny() || overriddenDescriptors.any { it.containingDeclaration.isAny() } || isTypedEqualsInInlineClass()) {
buildString {
append("must override ''equals()'' in Any")
if (containingDeclaration.isInlineClass()) {
append(" or define ''equals(other: ${containingDeclaration.name}): Boolean''")
val expectedParameterTypeRendered = DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(
(containingDeclaration as ClassDescriptor).defaultType.replaceArgumentsWithStarProjections()
)
append(" or define ''equals(other: $expectedParameterTypeRendered): Boolean''")
}
}
}