From 36b8ba8df37aceffd3f6404db1e6bcd2d70811ff Mon Sep 17 00:00:00 2001 From: "vladislav.grechko" Date: Fri, 11 Nov 2022 16:44:03 +0100 Subject: [PATCH] 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 --- .../diagnostics/KtFirDataClassConverters.kt | 2 +- .../api/fir/diagnostics/KtFirDiagnostics.kt | 2 +- .../fir/diagnostics/KtFirDiagnosticsImpl.kt | 2 +- ...CompilerTestFE10TestdataTestGenerated.java | 6 +- ...irOldFrontendDiagnosticsTestGenerated.java | 6 +- ...DiagnosticsWithLightTreeTestGenerated.java | 6 +- .../diagnostics/FirDiagnosticsList.kt | 2 +- .../fir/analysis/diagnostics/FirErrors.kt | 2 +- .../FirInlineClassDeclarationChecker.kt | 18 ++++- .../declaration/FirOperatorModifierChecker.kt | 5 +- .../checkers/declaration/declarationUtils.kt | 15 ++-- .../diagnostics/FirErrorsDefaultMessages.kt | 4 +- .../jetbrains/kotlin/diagnostics/Errors.java | 2 +- .../rendering/DefaultErrorMessages.java | 4 +- .../checkers/InlineClassDeclarationChecker.kt | 16 +++- .../MethodsFromAnyGeneratorForLowerings.kt | 17 +---- .../lower/SingleAbstractMethodLowering.kt | 1 - .../jvm/lower/JvmInlineClassLowering.kt | 4 +- .../lower/JvmMultiFieldValueClassLowering.kt | 5 +- .../lower/JvmValueClassAbstractLowering.kt | 2 +- .../jvm/MemoizedInlineClassReplacements.kt | 7 +- .../org/jetbrains/kotlin/ir/util/IrUtils.kt | 29 ++++++-- .../inlineClassEqualsOverride.fir.txt | 39 ++++++++++ .../inlineClassEqualsOverride.kt | 26 ++++++- .../inlineClassEqualsOverride.txt | 39 ++++++++++ ...nlineClassEqualsOverridenForCollections.kt | 2 +- .../inlineClassTypedEqualsGenerics.kt | 8 +- .../inlineOverInlineWithCustomEquals.kt | 6 +- .../codegen/box/inlineClasses/kt54536.kt | 2 +- ...nefficientEqualsOverridingInInlineClass.kt | 11 ++- ...efficientEqualsOverridingInInlineClass.txt | 12 ++- .../typedEqualsOperatorDeclarationCheck.kt | 52 +++++++++++++ .../typedEqualsOperatorDeclarationCheck.txt | 74 +++++++++++++++++++ ...ypedEqualsOperatorModifierInInlineClass.kt | 22 ------ ...pedEqualsOperatorModifierInInlineClass.txt | 20 ----- .../test/runners/DiagnosticTestGenerated.java | 6 +- .../kotlin/descriptors/descriptorUtil.kt | 19 +++-- .../jetbrains/kotlin/util/modifierChecks.kt | 10 ++- 38 files changed, 369 insertions(+), 136 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorDeclarationCheck.kt create mode 100644 compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorDeclarationCheck.txt delete mode 100644 compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.kt delete mode 100644 compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.txt diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt index 9410f18fe08..12b69a03be5 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -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, ) diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt index 5ac50b2a35b..a66c72857e5 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt @@ -2710,7 +2710,7 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { abstract class InefficientEqualsOverridingInInlineClass : KtFirDiagnostic() { override val diagnosticClass get() = InefficientEqualsOverridingInInlineClass::class - abstract val className: String + abstract val type: KtType } abstract class CannotAllUnderImportFromSingleton : KtFirDiagnostic() { diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index e4005cadb16..056871c51ab 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -3267,7 +3267,7 @@ internal class RedundantInlineSuspendFunctionTypeImpl( ) : KtFirDiagnostic.RedundantInlineSuspendFunctionType(), KtAbstractFirDiagnostic internal class InefficientEqualsOverridingInInlineClassImpl( - override val className: String, + override val type: KtType, override val firDiagnostic: KtPsiDiagnostic, override val token: KtLifetimeToken, ) : KtFirDiagnostic.InefficientEqualsOverridingInInlineClass(), KtAbstractFirDiagnostic diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java index 7064e43ffcf..1e007e23f72 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java @@ -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 diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java index 4d23ae08d57..3d7a7b15e31 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java @@ -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 diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java index 4711f1fe4ae..e0f73ece593 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java @@ -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 diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt index 31936b23d38..92f3751fb55 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt @@ -1400,7 +1400,7 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") { val REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE by warning(PositioningStrategy.SUSPEND_MODIFIER) val INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS by warning(PositioningStrategy.DECLARATION_NAME) { - parameter("className") + parameter("type") } } diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index 71ebe1f6bc1..6f17edac54f 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -724,7 +724,7 @@ object FirErrors { val ILLEGAL_INLINE_PARAMETER_MODIFIER by error0(SourceElementPositioningStrategies.INLINE_PARAMETER_MODIFIER) val INLINE_SUSPEND_FUNCTION_TYPE_UNSUPPORTED by error0() val REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE by warning0(SourceElementPositioningStrategies.SUSPEND_MODIFIER) - val INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS by warning1(SourceElementPositioningStrategies.DECLARATION_NAME) + val INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS by warning1(SourceElementPositioningStrategies.DECLARATION_NAME) // Imports val CANNOT_ALL_UNDER_IMPORT_FROM_SINGLETON by error1(SourceElementPositioningStrategies.IMPORT_LAST_NAME) diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirInlineClassDeclarationChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirInlineClassDeclarationChecker.kt index 6dde64fa69a..d755bb06fa4 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirInlineClassDeclarationChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirInlineClassDeclarationChecker.kt @@ -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 ) } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirOperatorModifierChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirOperatorModifierChecker.kt index edc7eeb0fe4..f75919393c0 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirOperatorModifierChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirOperatorModifierChecker.kt @@ -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''") } } } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/declarationUtils.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/declarationUtils.kt index df6ebef3388..8cf7979efb2 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/declarationUtils.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/declarationUtils.kt @@ -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 diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt index 6e80e9fe35f..694a5c00124 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt @@ -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 diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 7567d6faa31..27858ddb41e 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -426,7 +426,7 @@ public interface Errors { DiagnosticFactory0 VALUE_CLASS_CANNOT_BE_CLONEABLE = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 INLINE_CLASS_DEPRECATED = DiagnosticFactory0.create(WARNING); DiagnosticFactory0 INLINE_CLASS_CANNOT_HAVE_CONTEXT_RECEIVERS = DiagnosticFactory0.create(ERROR); - DiagnosticFactory1 INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS = + DiagnosticFactory1 INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS = DiagnosticFactory1.create(WARNING, DECLARATION_NAME); // Result class diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index e3ad52927a9..863e7a4620f 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -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); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/InlineClassDeclarationChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/InlineClassDeclarationChecker.kt index 4516465f3b7..6718d6d6c33 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/InlineClassDeclarationChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/InlineClassDeclarationChecker.kt @@ -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() 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() + ) + ) } } } diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/MethodsFromAnyGeneratorForLowerings.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/MethodsFromAnyGeneratorForLowerings.kt index 9d26499a35e..4cbb6034142 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/MethodsFromAnyGeneratorForLowerings.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/MethodsFromAnyGeneratorForLowerings.kt @@ -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 = superTypes.mapNotNull { it.getClass()?.functions?.singleOrNull(predicate)?.symbol } } diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/SingleAbstractMethodLowering.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/SingleAbstractMethodLowering.kt index b12ae3e0473..d17b207ff22 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/SingleAbstractMethodLowering.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/SingleAbstractMethodLowering.kt @@ -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 diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt index 9dd4c46cb1a..d3a36aaf649 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt @@ -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 diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmMultiFieldValueClassLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmMultiFieldValueClassLowering.kt index 28d7122f7a4..f94de960a28 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmMultiFieldValueClassLowering.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmMultiFieldValueClassLowering.kt @@ -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 diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmValueClassAbstractLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmValueClassAbstractLowering.kt index ceb25a611b8..4d86ba4014c 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmValueClassAbstractLowering.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmValueClassAbstractLowering.kt @@ -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) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MemoizedInlineClassReplacements.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MemoizedInlineClassReplacements.kt index bd9494b5656..18946ae017f 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MemoizedInlineClassReplacements.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MemoizedInlineClassReplacements.kt @@ -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()?.isSingleFieldValueClass == true -> when { - it.isTypedEquals -> createStaticReplacement(it).also { + it.isTypedEquals() -> createStaticReplacement(it).also { it.name = InlineClassDescriptorResolver.SPECIALIZED_EQUALS_NAME specializedEqualsCache.computeIfAbsent(it.parentAsClass) { it } } diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IrUtils.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IrUtils.kt index 6b822edad26..1bb0ea7cf10 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IrUtils.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IrUtils.kt @@ -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 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 - } \ No newline at end of file +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 +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.fir.txt b/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.fir.txt index 0c6d8d4a1c0..7af1ca77f98 100644 --- a/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.fir.txt +++ b/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.fir.txt @@ -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 (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 (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 } diff --git a/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.kt b/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.kt index 0104dbc8c93..6dd099b3b6d 100644 --- a/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.kt +++ b/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.kt @@ -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 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 { IC5(0) == IC5(1) } -> "Fail 8.1" + !assertThrows { IC6(0) == IC6(1) } -> "Fail 8.2" + + else -> "OK" } \ No newline at end of file diff --git a/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.txt b/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.txt index d500c81a878..579dfd994eb 100644 --- a/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.txt +++ b/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.txt @@ -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 (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 (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 } diff --git a/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverridenForCollections.kt b/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverridenForCollections.kt index 4a630ad9734..abe5b4d2a20 100644 --- a/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverridenForCollections.kt +++ b/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverridenForCollections.kt @@ -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 } diff --git a/compiler/testData/codegen/box/inlineClasses/inlineClassTypedEqualsGenerics.kt b/compiler/testData/codegen/box/inlineClasses/inlineClassTypedEqualsGenerics.kt index 54a8c6254e6..7ba5253ac1d 100644 --- a/compiler/testData/codegen/box/inlineClasses/inlineClassTypedEqualsGenerics.kt +++ b/compiler/testData/codegen/box/inlineClasses/inlineClassTypedEqualsGenerics.kt @@ -6,7 +6,7 @@ OPTIONAL_JVM_INLINE_ANNOTATION value class IC1(val x: T) { fun equals(other: Int) = false - fun equals(other: IC1) = true + operator fun equals(other: IC1<*>) = true } class Generic(val x: T, val y: R) @@ -14,19 +14,19 @@ class Generic(val x: T, val y: R) OPTIONAL_JVM_INLINE_ANNOTATION value class IC2(val value: Generic) { fun equals(other: IC1) = false - fun equals(other: IC2) = true + operator fun equals(other: IC2<*, *>) = true } OPTIONAL_JVM_INLINE_ANNOTATION value class IC3(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(val value: T) { fun equals(other: String) = false - fun equals(other: IC4) = true + operator fun equals(other: IC4<*>) = true } diff --git a/compiler/testData/codegen/box/inlineClasses/inlineOverInlineWithCustomEquals.kt b/compiler/testData/codegen/box/inlineClasses/inlineOverInlineWithCustomEquals.kt index f039e51c3cd..5c59db7de3f 100644 --- a/compiler/testData/codegen/box/inlineClasses/inlineOverInlineWithCustomEquals.kt +++ b/compiler/testData/codegen/box/inlineClasses/inlineOverInlineWithCustomEquals.kt @@ -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 diff --git a/compiler/testData/codegen/box/inlineClasses/kt54536.kt b/compiler/testData/codegen/box/inlineClasses/kt54536.kt index ba5d880ba4b..1eaec18f024 100644 --- a/compiler/testData/codegen/box/inlineClasses/kt54536.kt +++ b/compiler/testData/codegen/box/inlineClasses/kt54536.kt @@ -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 diff --git a/compiler/testData/diagnostics/tests/inlineClasses/inefficientEqualsOverridingInInlineClass.kt b/compiler/testData/diagnostics/tests/inlineClasses/inefficientEqualsOverridingInInlineClass.kt index 843d5ef127d..39942aa2fdd 100644 --- a/compiler/testData/diagnostics/tests/inlineClasses/inefficientEqualsOverridingInInlineClass.kt +++ b/compiler/testData/diagnostics/tests/inlineClasses/inefficientEqualsOverridingInInlineClass.kt @@ -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(val x: Int) { + override fun equals(other: Any?) = true + + fun equals(other: IC4) = true override fun hashCode() = 0 } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inlineClasses/inefficientEqualsOverridingInInlineClass.txt b/compiler/testData/diagnostics/tests/inlineClasses/inefficientEqualsOverridingInInlineClass.txt index 06e3b9dcd95..fa41169e098 100644 --- a/compiler/testData/diagnostics/tests/inlineClasses/inefficientEqualsOverridingInInlineClass.txt +++ b/compiler/testData/diagnostics/tests/inlineClasses/inefficientEqualsOverridingInInlineClass.txt @@ -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 { + public constructor IC4(/*0*/ x: kotlin.Int) + public final val x: kotlin.Int + public final fun equals(/*0*/ other: IC4): 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 +} + diff --git a/compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorDeclarationCheck.kt b/compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorDeclarationCheck.kt new file mode 100644 index 00000000000..c9ccf07bab6 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorDeclarationCheck.kt @@ -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) { + operator fun equals(other: IC1) = true + + operator fun equals(other: IC2) { + } +} + +@JvmInline +value class IC3(val x: T) { + operator fun equals(other: IC3) = true +} + +@JvmInline +value class IC4(val x: T) { + operator fun equals(other: IC4) = true +} + +@JvmInline +value class IC5(val x: T) { + operator fun equals(other: T) = true +} + +@JvmInline +value class IC6(val x: T) { + operator fun equals(other: IC6<*, *>) = true +} + +@JvmInline +value class IC7(val x: T) { + operator fun equals(other: IC7<*, *>) = true +} + +@JvmInline +value class IC8(val x: T) { + operator fun equals(other: IC8<*, *>): Nothing = TODO() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorDeclarationCheck.txt b/compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorDeclarationCheck.txt new file mode 100644 index 00000000000..d4f899262b8 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorDeclarationCheck.txt @@ -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 { + public constructor IC3(/*0*/ x: T) + public final val x: T + public final operator fun equals(/*0*/ other: IC3): 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 { + public constructor IC4(/*0*/ x: T) + public final val x: T + public final operator fun equals(/*0*/ other: IC4): 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 { + public constructor IC5(/*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 { + public constructor IC6(/*0*/ x: T) + public final val x: T + public final operator fun 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 { + public constructor IC7(/*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 { + public constructor IC8(/*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 +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.kt b/compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.kt deleted file mode 100644 index d50022551b6..00000000000 --- a/compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.kt +++ /dev/null @@ -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) { - operator fun equals(other: IC1) = true - - operator fun equals(other: IC2) { - } -} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.txt b/compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.txt deleted file mode 100644 index e304e652c55..00000000000 --- a/compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.txt +++ /dev/null @@ -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 -} diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java index ada24f06ed2..55e8da7e0bb 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java @@ -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 diff --git a/core/descriptors/src/org/jetbrains/kotlin/descriptors/descriptorUtil.kt b/core/descriptors/src/org/jetbrains/kotlin/descriptors/descriptorUtil.kt index 5c579ed7cea..b19eaf6ecba 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/descriptors/descriptorUtil.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/descriptors/descriptorUtil.kt @@ -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 -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 diff --git a/core/descriptors/src/org/jetbrains/kotlin/util/modifierChecks.kt b/core/descriptors/src/org/jetbrains/kotlin/util/modifierChecks.kt index 9f048a5e861..e141435294b 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/util/modifierChecks.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/util/modifierChecks.kt @@ -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''") } } }