Support of custom 'equals' and 'hashCode' in inline classes

'equals' from any made available for overriding in inline classes
'typed' equals made available for definition in inline classes
'typed' equals definition made compulsory if 'untyped' is overridden
'operator' keyword is allowed in 'typed' equals definition

^KT-24874: Fixed
This commit is contained in:
vladislav.grechko
2022-10-04 20:22:11 +02:00
committed by teamcity
parent 527e8dde27
commit e0c8142106
47 changed files with 684 additions and 53 deletions
@@ -3876,6 +3876,13 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirErrors.INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS) { firDiagnostic ->
InefficientEqualsOverridingInInlineClassImpl(
firDiagnostic.a,
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirErrors.CANNOT_ALL_UNDER_IMPORT_FROM_SINGLETON) { firDiagnostic ->
CannotAllUnderImportFromSingletonImpl(
firDiagnostic.a,
@@ -2704,6 +2704,11 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = RedundantInlineSuspendFunctionType::class
}
abstract class InefficientEqualsOverridingInInlineClass : KtFirDiagnostic<KtDeclaration>() {
override val diagnosticClass get() = InefficientEqualsOverridingInInlineClass::class
abstract val className: String
}
abstract class CannotAllUnderImportFromSingleton : KtFirDiagnostic<KtImportDirective>() {
override val diagnosticClass get() = CannotAllUnderImportFromSingleton::class
abstract val objectName: Name
@@ -3261,6 +3261,12 @@ internal class RedundantInlineSuspendFunctionTypeImpl(
override val token: KtLifetimeToken,
) : KtFirDiagnostic.RedundantInlineSuspendFunctionType(), KtAbstractFirDiagnostic<KtElement>
internal class InefficientEqualsOverridingInInlineClassImpl(
override val className: String,
override val firDiagnostic: KtPsiDiagnostic,
override val token: KtLifetimeToken,
) : KtFirDiagnostic.InefficientEqualsOverridingInInlineClass(), KtAbstractFirDiagnostic<KtDeclaration>
internal class CannotAllUnderImportFromSingletonImpl(
override val objectName: Name,
override val firDiagnostic: KtPsiDiagnostic,
@@ -17854,6 +17854,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag
runTest("compiler/testData/diagnostics/tests/inlineClasses/identityComparisonWithInlineClasses.kt");
}
@Test
@TestMetadata("illegalEqualsOverridingInInlineClass.kt")
public void testIllegalEqualsOverridingInInlineClass() throws Exception {
runTest("compiler/testData/diagnostics/tests/inlineClasses/illegalEqualsOverridingInInlineClass.kt");
}
@Test
@TestMetadata("inlineClassCanImplementInterfaceByDelegation.kt")
public void testInlineClassCanImplementInterfaceByDelegation() throws Exception {
@@ -17962,6 +17968,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag
runTest("compiler/testData/diagnostics/tests/inlineClasses/synchronizedForbidden.kt");
}
@Test
@TestMetadata("typedEqualsOperatorModifierInInlineClass.kt")
public void testTypedEqualsOperatorModifierInInlineClass() throws Exception {
runTest("compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.kt");
}
@Test
@TestMetadata("unsignedLiteralsWithoutArtifactOnClasspath.kt")
public void testUnsignedLiteralsWithoutArtifactOnClasspath() throws Exception {
@@ -1,3 +1,5 @@
// !LANGUAGE: +CustomEqualsInInlineClasses
<!VALUE_CLASS_WITHOUT_JVM_INLINE_ANNOTATION!>value<!> class BackingFields(val x: Int) {
<!PROPERTY_WITH_BACKING_FIELD_INSIDE_VALUE_CLASS!>val y<!> = 0
var z: String
@@ -16,8 +18,8 @@ inline class ReversedMembers(val x: Int) {
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>box<!>() {}
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>unbox<!>() {}
override fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>equals<!>(other: Any?) = true
override fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>hashCode<!>() = 1
<!INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS!>override fun equals(other: Any?)<!> = true
override fun hashCode() = 1
}
inline class SecondaryConstructors(val x: Int) {
@@ -17854,6 +17854,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti
runTest("compiler/testData/diagnostics/tests/inlineClasses/identityComparisonWithInlineClasses.kt");
}
@Test
@TestMetadata("illegalEqualsOverridingInInlineClass.kt")
public void testIllegalEqualsOverridingInInlineClass() throws Exception {
runTest("compiler/testData/diagnostics/tests/inlineClasses/illegalEqualsOverridingInInlineClass.kt");
}
@Test
@TestMetadata("inlineClassCanImplementInterfaceByDelegation.kt")
public void testInlineClassCanImplementInterfaceByDelegation() throws Exception {
@@ -17962,6 +17968,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti
runTest("compiler/testData/diagnostics/tests/inlineClasses/synchronizedForbidden.kt");
}
@Test
@TestMetadata("typedEqualsOperatorModifierInInlineClass.kt")
public void testTypedEqualsOperatorModifierInInlineClass() throws Exception {
runTest("compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.kt");
}
@Test
@TestMetadata("unsignedLiteralsWithoutArtifactOnClasspath.kt")
public void testUnsignedLiteralsWithoutArtifactOnClasspath() throws Exception {
@@ -17854,6 +17854,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac
runTest("compiler/testData/diagnostics/tests/inlineClasses/identityComparisonWithInlineClasses.kt");
}
@Test
@TestMetadata("illegalEqualsOverridingInInlineClass.kt")
public void testIllegalEqualsOverridingInInlineClass() throws Exception {
runTest("compiler/testData/diagnostics/tests/inlineClasses/illegalEqualsOverridingInInlineClass.kt");
}
@Test
@TestMetadata("inlineClassCanImplementInterfaceByDelegation.kt")
public void testInlineClassCanImplementInterfaceByDelegation() throws Exception {
@@ -17962,6 +17968,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac
runTest("compiler/testData/diagnostics/tests/inlineClasses/synchronizedForbidden.kt");
}
@Test
@TestMetadata("typedEqualsOperatorModifierInInlineClass.kt")
public void testTypedEqualsOperatorModifierInInlineClass() throws Exception {
runTest("compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.kt");
}
@Test
@TestMetadata("unsignedLiteralsWithoutArtifactOnClasspath.kt")
public void testUnsignedLiteralsWithoutArtifactOnClasspath() throws Exception {
@@ -1397,6 +1397,10 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
val INLINE_SUSPEND_FUNCTION_TYPE_UNSUPPORTED by error<KtParameter>()
val REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE by warning<KtElement>(PositioningStrategy.SUSPEND_MODIFIER)
val INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS by warning<KtDeclaration>(PositioningStrategy.DECLARATION_SIGNATURE) {
parameter<String>("className")
}
}
val IMPORTS by object : DiagnosticGroup("Imports") {
@@ -723,6 +723,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<KtDeclaration, String>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE)
// Imports
val CANNOT_ALL_UNDER_IMPORT_FROM_SINGLETON by error1<KtImportDirective, Name>(SourceElementPositioningStrategies.IMPORT_LAST_NAME)
@@ -32,7 +32,8 @@ import org.jetbrains.kotlin.name.StandardClassIds
object FirInlineClassDeclarationChecker : FirRegularClassChecker() {
private val reservedFunctionNames = setOf("box", "unbox", "equals", "hashCode")
private val boxAndUnboxNames = setOf("box", "unbox")
private val equalsAndHashCodeNames = setOf("equals", "hashCode")
private val javaLangFqName = FqName("java.lang")
private val cloneableFqName = FqName("Cloneable")
@@ -92,7 +93,10 @@ object FirInlineClassDeclarationChecker : FirRegularClassChecker() {
is FirSimpleFunction -> {
val functionName = innerDeclaration.name.asString()
if (functionName in reservedFunctionNames) {
if (functionName in boxAndUnboxNames
|| (functionName in equalsAndHashCodeNames
&& !context.languageVersionSettings.supportsFeature(LanguageFeature.CustomEqualsInInlineClasses))
) {
reporter.reportOn(
innerDeclaration.source, FirErrors.RESERVED_MEMBER_INSIDE_VALUE_CLASS, functionName, context
)
@@ -193,6 +197,20 @@ object FirInlineClassDeclarationChecker : FirRegularClassChecker() {
}
}
}
if (context.languageVersionSettings.supportsFeature(LanguageFeature.CustomEqualsInInlineClasses)) {
val simpleFunctions = declaration.declarations.filterIsInstance<FirSimpleFunction>()
simpleFunctions.singleOrNull() { it.overridesEqualsFromAny() }?.apply {
if (declaration.symbol.isInline && simpleFunctions.none { it.isTypedEqualsInInlineClass(context.session) }) {
reporter.reportOn(
source,
FirErrors.INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS,
declaration.name.asString(),
context
)
}
}
}
}
private fun FirProperty.isRelatedToParameter(parameter: FirValueParameter?) =
@@ -10,6 +10,7 @@
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.Checks.Returns
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.Checks.ValueParametersCount
@@ -25,6 +26,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.containingClass
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.toFirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.ConeClassLikeLookupTagImpl
@@ -185,10 +187,23 @@ private object OperatorFunctionChecks {
checkFor(
EQUALS,
member,
Checks.full("must override ''equals()'' in Any") { ctx, function ->
val containingClassSymbol = function.containingClass()?.toFirRegularClassSymbol(ctx.session) ?: return@full true
function.overriddenFunctions(containingClassSymbol, ctx).any {
it.containingClass()?.classId == StandardClassIds.Any
object : Check {
override fun check(context: CheckerContext, function: FirSimpleFunction): String? {
val containingClassSymbol = function.containingClass()?.toFirRegularClassSymbol(context.session) ?: return null
val customEqualsSupported = context.languageVersionSettings.supportsFeature(LanguageFeature.CustomEqualsInInlineClasses)
if (function.overriddenFunctions(containingClassSymbol, context)
.any { it.containingClass()?.classId == StandardClassIds.Any }
|| (customEqualsSupported && function.isTypedEqualsInInlineClass(context.session))
) {
return null
}
return buildString {
append("must override ''equals()'' in Any")
if (customEqualsSupported && containingClassSymbol.isInline) {
append(" or define ''equals(other: ${containingClassSymbol.name}): Boolean''")
}
}
}
}
)
@@ -7,15 +7,23 @@ package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.descriptors.ClassKind
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.hasModifier
import org.jetbrains.kotlin.fir.analysis.checkers.modality
import org.jetbrains.kotlin.fir.containingClass
import org.jetbrains.kotlin.fir.containingClassForStaticMemberAttr
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.*
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.isNullableAny
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.util.OperatorNameConventions
internal fun isInsideExpectClass(containingClass: FirClass, context: CheckerContext): Boolean {
return isInsideSpecificClass(containingClass, context) { klass -> klass is FirRegularClass && klass.isExpect }
@@ -110,3 +118,20 @@ fun FirClassSymbol<*>.primaryConstructorSymbol(): FirConstructorSymbol? {
}
return null
}
fun FirSimpleFunction.isTypedEqualsInInlineClass(session: FirSession): Boolean =
containingClass()?.toFirRegularClassSymbol(session)?.run {
this@isTypedEqualsInInlineClass.contextReceivers.isEmpty()
&& this@isTypedEqualsInInlineClass.receiverTypeRef == null
&& this@isTypedEqualsInInlineClass.name == OperatorNameConventions.EQUALS
&& this@isTypedEqualsInInlineClass.returnTypeRef.isBoolean
&& isInline
&& this@isTypedEqualsInInlineClass.valueParameters.size == 1
&& this@isTypedEqualsInInlineClass.valueParameters[0].returnTypeRef.coneType.classId == classId
} ?: false
fun FirSimpleFunction.overridesEqualsFromAny(): Boolean {
return name == OperatorNameConventions.EQUALS && returnTypeRef.isBoolean
&& valueParameters.size == 1 && valueParameters[0].returnTypeRef.isNullableAny
&& contextReceivers.isEmpty() && receiverTypeRef == null
}
@@ -134,6 +134,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CYCLIC_CONSTRUCTO
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CYCLIC_GENERIC_UPPER_BOUND
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CYCLIC_INHERITANCE_HIERARCHY
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DANGEROUS_CHARACTERS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DATA_CLASS_NOT_PROPERTY_PARAMETER
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DATA_CLASS_OVERRIDE_CONFLICT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DATA_CLASS_VARARG_PARAMETER
@@ -2003,6 +2004,12 @@ object FirErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
"Redundant 'suspend' modifier: lambda parameters of suspend function type uses existing continuation."
)
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
)
//imports
map.put(
CANNOT_ALL_UNDER_IMPORT_FROM_SINGLETON,
@@ -21353,6 +21353,12 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassAsLastExpressionInInLambdaGeneric.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("inlineClassBothEqualsOverride.kt")
public void testInlineClassBothEqualsOverride() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassBothEqualsOverride.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("inlineClassEqualityShouldUseTotalOrderForFloatingPointData.kt")
public void testInlineClassEqualityShouldUseTotalOrderForFloatingPointData() throws Exception {
@@ -21365,6 +21371,24 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassEqualityShouldUseTotalOrderForFloatingPointDataGeneric.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("inlineClassEqualsConsistency.kt")
public void testInlineClassEqualsConsistency() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassEqualsConsistency.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("inlineClassEqualsOverride.kt")
public void testInlineClassEqualsOverride() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("inlineClassEqualsOverridenForCollections.kt")
public void testInlineClassEqualsOverridenForCollections() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverridenForCollections.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("inlineClassFieldHandling.kt")
public void testInlineClassFieldHandling() throws Exception {
@@ -21389,6 +21413,12 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassFunctionInvokeGeneric.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("inlineClassHashCodeOverride.kt")
public void testInlineClassHashCodeOverride() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassHashCodeOverride.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("inlineClassImplementsCollection.kt")
public void testInlineClassImplementsCollection() throws Exception {
@@ -21437,6 +21467,12 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassPropertyReferenceGetAndSetGeneric.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("inlineClassUntypedEqualsOverriden.kt")
public void testInlineClassUntypedEqualsOverriden() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassUntypedEqualsOverriden.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("inlineClassValueCapturedInInlineLambda.kt")
public void testInlineClassValueCapturedInInlineLambda() throws Exception {
@@ -428,6 +428,8 @@ 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<KtDeclaration, String> INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS =
DiagnosticFactory1.create(WARNING, DECLARATION_SIGNATURE);
// Result class
@@ -787,6 +787,9 @@ public class DefaultErrorMessages {
MAP.put(VALUE_CLASS_CANNOT_BE_CLONEABLE, "Value class cannot be Cloneable");
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);
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.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.isTypedEqualsInInlineClass
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.lexer.KtTokens
@@ -44,11 +45,15 @@ object OperatorModifierChecker {
val checkResult = OperatorChecks.check(functionDescriptor)
if (checkResult.isSuccess) {
when (functionDescriptor.name) {
in REM_TO_MOD_OPERATION_NAMES.keys ->
when {
functionDescriptor.name in REM_TO_MOD_OPERATION_NAMES.keys ->
checkSupportsFeature(LanguageFeature.OperatorRem, languageVersionSettings, diagnosticHolder, modifier)
OperatorNameConventions.PROVIDE_DELEGATE ->
functionDescriptor.name == OperatorNameConventions.PROVIDE_DELEGATE ->
checkSupportsFeature(LanguageFeature.OperatorProvideDelegate, languageVersionSettings, diagnosticHolder, modifier)
functionDescriptor.isTypedEqualsInInlineClass() ->
checkSupportsFeature(LanguageFeature.CustomEqualsInInlineClasses, languageVersionSettings, diagnosticHolder, modifier)
}
if (functionDescriptor.name in REM_TO_MOD_OPERATION_NAMES.values &&
@@ -161,6 +161,21 @@ object InlineClassDeclarationChecker : DeclarationChecker {
trace.report(Errors.VALUE_CLASS_CANNOT_BE_CLONEABLE.on(inlineOrValueKeyword))
return
}
fun getFunctionDescriptor(declaration: KtNamedFunction): SimpleFunctionDescriptor? =
context.trace.bindingContext.get(BindingContext.FUNCTION, declaration)
fun isUntypedEquals(declaration: KtNamedFunction): Boolean = getFunctionDescriptor(declaration)?.overridesEqualsFromAny() ?: false
fun isTypedEquals(declaration: KtNamedFunction): Boolean = getFunctionDescriptor(declaration)?.isTypedEqualsInInlineClass() ?: false
fun KtClass.namedFunctions() = declarations.filterIsInstance<KtNamedFunction>()
if (context.languageVersionSettings.supportsFeature(LanguageFeature.CustomEqualsInInlineClasses)) {
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()))
}
}
}
}
private fun KotlinType.isInapplicableParameterType() =
@@ -213,7 +228,8 @@ class InnerClassInsideInlineClass : DeclarationChecker {
class ReservedMembersAndConstructsForInlineClass : DeclarationChecker {
companion object {
private val reservedFunctions = setOf("box", "unbox", "equals", "hashCode")
private val boxAndUnboxNames = setOf("box", "unbox")
private val equalsAndHashCodeNames = setOf("equals", "hashCode")
}
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
@@ -226,7 +242,10 @@ class ReservedMembersAndConstructsForInlineClass : DeclarationChecker {
is SimpleFunctionDescriptor -> {
val ktFunction = declaration as? KtFunction ?: return
val functionName = descriptor.name.asString()
if (functionName in reservedFunctions) {
if (functionName in boxAndUnboxNames
|| (functionName in equalsAndHashCodeNames
&& !context.languageVersionSettings.supportsFeature(LanguageFeature.CustomEqualsInInlineClasses))
) {
val nameIdentifier = ktFunction.nameIdentifier ?: return
context.trace.report(Errors.RESERVED_MEMBER_INSIDE_VALUE_CLASS.on(nameIdentifier, functionName))
}
@@ -24,14 +24,12 @@ import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
import org.jetbrains.kotlin.ir.transformStatement
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.types.isNullable
import org.jetbrains.kotlin.ir.types.makeNotNull
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.JVM_INLINE_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.util.OperatorNameConventions.EQUALS
val jvmInlineClassPhase = makeIrFilePhase(
::JvmInlineClassLowering,
@@ -487,15 +485,52 @@ private class JvmInlineClassLowering(context: JvmBackendContext) : JvmValueClass
val right = function.valueParameters[1]
val type = left.type.unboxInlineClass()
val typedEqualsStaticReplacement = findStaticReplacementForTypedEquals(valueClass)
val untypedEquals = valueClass.functions.single { overridesEqualsFromAny(it) }
function.body = context.createIrBuilder(valueClass.symbol).run {
irExprBody(
irEquals(
coerceInlineClasses(irGet(left), left.type, type),
coerceInlineClasses(irGet(right), right.type, type)
)
if (typedEqualsStaticReplacement == null) {
if (untypedEquals.origin == IrDeclarationOrigin.DEFINED) {
val boxFunction = this@JvmInlineClassLowering.context.inlineClassReplacements.getBoxFunction(valueClass)
fun irBox(expr: IrExpression) = irCall(boxFunction).apply { putValueArgument(0, expr) }
irCall(untypedEquals).apply {
dispatchReceiver = irBox(irGet(left))
putValueArgument(0, irBox(irGet(right)))
}
} else {
irEquals(coerceInlineClasses(irGet(left), left.type, type), coerceInlineClasses(irGet(right), right.type, type))
}
} else {
irCall(typedEqualsStaticReplacement).apply {
putValueArgument(0, irGet(left))
putValueArgument(1, irGet(right))
}
}
)
}
valueClass.declarations += function
}
private fun overridesEqualsFromAny(irFunction: IrFunction): Boolean {
return irFunction.run {
name == EQUALS && returnType.isBoolean() && valueParameters.size == 1
&& valueParameters[0].type.isNullableAny() && contextReceiverParametersCount == 0 && extensionReceiverParameter == null
}
}
private fun findStaticReplacementForTypedEquals(valueClass: IrClass): IrFunction? {
fun isTypedEquals(irFunction: IrFunction): Boolean {
return irFunction.run {
name == EQUALS && returnType.isBoolean() && valueParameters.size == 1
&& (valueParameters[0].type.classFqName?.run { valueClass.hasEqualFqName(this) } ?: false)
&& contextReceiverParametersCount == 0 && extensionReceiverParameter == null
}
}
return valueClass.functions
.singleOrNull { context.inlineClassReplacements.originalFunctionForStaticReplacement[it]?.run { isTypedEquals(this) } ?: false }
}
}
@@ -128,6 +128,17 @@ abstract class DataClassMembersGenerator(
fun generateEqualsMethodBody(properties: List<IrProperty>) {
val irType = irClass.defaultType
val typedEqualsFunction = irClass.functions.singleOrNull { it.descriptor.isTypedEqualsInInlineClass() }
if (irClass.isSingleFieldValueClass && typedEqualsFunction != null) {
+irIfThenReturnFalse(irNotIs(irOther(), irType))
val otherCasted = irImplicitCast(irOther(), irType)
+irReturn(irCall(typedEqualsFunction).apply {
putArgument(typedEqualsFunction.dispatchReceiverParameter!!, irThis())
putValueArgument(0, otherCasted)
})
return
}
if (!irClass.isValue) {
+irIfThenReturnTrue(irEqeqeq(irThis(), irOther()))
}
@@ -0,0 +1,55 @@
// WITH_STDLIB
// WORKS_WHEN_VALUE_CLASS
// LANGUAGE: +ValueClasses, +CustomEqualsInInlineClasses
// TARGET_BACKEND: JVM_IR
interface I {
fun getVal(): Int
}
OPTIONAL_JVM_INLINE_ANNOTATION
value class IC1(val x: Int) : I {
override fun getVal(): Int {
return x
}
fun equals(other: IC1): Boolean {
return x == other.x
}
override fun equals(other: Any?): Boolean {
if (other !is I) {
return false
}
return getVal() == other.getVal()
}
override fun hashCode(): Int {
return getVal()
}
}
OPTIONAL_JVM_INLINE_ANNOTATION
value class IC2(val y: Int) : I {
override fun getVal(): Int {
return y * 10
}
fun equals(other: IC2): Boolean {
return y == other.y
}
override fun equals(other: Any?): Boolean {
if (other !is I) {
return false
}
return getVal() == other.getVal()
}
override fun hashCode(): Int {
return getVal()
}
}
fun box(): String = if (setOf(IC1(10), IC2(1)).size == 1) "OK" else "Fail"
@@ -0,0 +1,47 @@
// WITH_STDLIB
// WORKS_WHEN_VALUE_CLASS
// LANGUAGE: +ValueClasses, +CustomEqualsInInlineClasses
// TARGET_BACKEND: JVM_IR
import java.lang.AssertionError
import kotlin.math.abs
OPTIONAL_JVM_INLINE_ANNOTATION
value class IC1(val x: Double) {
fun equals(other: IC1): Boolean {
return abs(x - other.x) < 0.5
}
}
OPTIONAL_JVM_INLINE_ANNOTATION
value class IC2(val x: Int) {
override fun equals(other: Any?): Boolean {
if (other !is IC2) {
return false
}
return abs(x - other.x) < 2
}
}
fun box(): String {
val a1Typed: IC1 = IC1(1.0)
val b1Typed: IC1 = IC1(1.1)
val c1Typed: IC1 = IC1(5.0)
val a1Untyped: Any = a1Typed
val b1Untyped: Any = b1Typed
val c1Untyped: Any = c1Typed
val a2Typed: IC2 = IC2(1)
val b2Typed: IC2 = IC2(2)
val c2Typed: IC2 = IC2(5)
val a2Untyped: Any = a2Typed
val b2Untyped: Any = b2Typed
val c2Untyped: Any = c2Typed
if ((a1Typed == b1Typed) != (a1Untyped == b1Untyped)) throw AssertionError()
if ((a1Typed == c1Typed) != (a1Untyped == c1Untyped)) throw AssertionError()
if ((a2Typed == b2Typed) != (a2Untyped == b2Untyped)) throw AssertionError()
if ((a2Typed == c2Typed) != (a2Untyped == c2Untyped)) throw AssertionError()
return "OK"
}
@@ -0,0 +1,35 @@
// WITH_STDLIB
// WORKS_WHEN_VALUE_CLASS
// LANGUAGE: +ValueClasses, +CustomEqualsInInlineClasses
// TARGET_BACKEND: JVM_IR
import kotlin.math.abs
OPTIONAL_JVM_INLINE_ANNOTATION
value class IC1(val value: Double) {
fun equals(other: IC1): Boolean {
return abs(value - other.value) < 0.1
}
}
interface I {
fun equals(param: IC2): Boolean
}
OPTIONAL_JVM_INLINE_ANNOTATION
value class IC2(val value: Int) : I {
override fun equals(param: IC2): Boolean {
return abs(value - param.value) < 2
}
}
OPTIONAL_JVM_INLINE_ANNOTATION
value class IC3(val value: Int) {
}
fun box() =
if (IC1(1.0) == IC1(1.05) && IC1(1.0) != IC1(1.2)
&& IC2(5) == IC2(6) && IC2(5) != IC2(7)
&& IC3(5) == IC3(5) && IC3(5) != IC3(6)
&& IC1(1.0) != Any()) "OK" else "Fail"
@@ -0,0 +1,22 @@
// WITH_STDLIB
// WORKS_WHEN_VALUE_CLASS
// LANGUAGE: +ValueClasses, +CustomEqualsInInlineClasses
// TARGET_BACKEND: JVM_IR
import kotlin.math.abs
OPTIONAL_JVM_INLINE_ANNOTATION
value class IC(val x: Double) {
fun equals(other: IC): Boolean {
return abs(x - other.x) < 0.1
}
override fun hashCode(): Int {
return 0
}
}
fun box(): String {
val set = setOf(IC(1.0), IC(1.5), IC(1.501))
return if (set.size == 2) "OK" else "Fail"
}
@@ -0,0 +1,14 @@
// WITH_STDLIB
// WORKS_WHEN_VALUE_CLASS
// LANGUAGE: +ValueClasses, +CustomEqualsInInlineClasses
OPTIONAL_JVM_INLINE_ANNOTATION
value class A(val value: MyClass) {
override fun hashCode() = 42
}
class MyClass() {
override fun hashCode() = -1
}
fun box(): String = if (A(MyClass()).hashCode() == 42) "OK" else "Fail"
@@ -0,0 +1,23 @@
// WITH_STDLIB
// WORKS_WHEN_VALUE_CLASS
// LANGUAGE: +ValueClasses, +CustomEqualsInInlineClasses
// TARGET_BACKEND: JVM_IR
import kotlin.math.abs
OPTIONAL_JVM_INLINE_ANNOTATION
value class IC(val x: Int) {
override fun equals(other: Any?): Boolean {
if (other !is IC) {
return false
}
return abs(x - other.x) < 2
}
override fun hashCode() = 0
}
fun box(): String {
val set = setOf(IC(1), IC(2), IC(5))
return if (set.size == 2 && IC(1) == IC(1) && IC(1) == IC(2) && IC(1) != IC(5)) "OK" else "Fail"
}
@@ -1,7 +1,4 @@
// IGNORE_BACKEND_FIR: JVM_IR
// FIR status: not supported in JVM
// IGNORE_BACKEND: JVM
// IGNORE_BACKEND: JVM_IR
// WITH_STDLIB
// WORKS_WHEN_VALUE_CLASS
// LANGUAGE: +ValueClasses
@@ -1,7 +1,4 @@
// IGNORE_BACKEND_FIR: JVM_IR
// FIR status: not supported in JVM
// IGNORE_BACKEND: JVM
// IGNORE_BACKEND: JVM_IR
// WITH_STDLIB
// WORKS_WHEN_VALUE_CLASS
// LANGUAGE: +ValueClasses, +GenericInlineClassParameter
@@ -0,0 +1,28 @@
// FIR_IDENTICAL
// WITH_STDLIB
// !DIAGNOSTICS: -DEBUG_INFO_SMARTCAST
// LANGUAGE: +CustomEqualsInInlineClasses
@JvmInline
value class IC1(val x: Int) {
<!INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS!>override fun equals(other: Any?): Boolean<!> {
if (other !is IC1) {
return false
}
return x == other.x
}
}
@JvmInline
value class IC2(val x: Int) {
override fun hashCode() = 0
}
@JvmInline
value class IC3(val x: Int) {
override fun equals(other: Any?) = true
fun equals(other: IC3) = true
override fun hashCode() = 0
}
@@ -0,0 +1,26 @@
package
@kotlin.jvm.JvmInline public final value class IC1 {
public constructor IC1(/*0*/ x: kotlin.Int)
public final val x: kotlin.Int
public open override /*1*/ 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 IC2 {
public constructor IC2(/*0*/ x: kotlin.Int)
public final val x: kotlin.Int
public open override /*1*/ /*synthesized*/ 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 IC3 {
public constructor IC3(/*0*/ x: kotlin.Int)
public final val x: kotlin.Int
public final 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
}
@@ -1,4 +1,4 @@
// !LANGUAGE: +InlineClasses, -JvmInlineValueClasses
// !LANGUAGE: +InlineClasses, -JvmInlineValueClasses, +CustomEqualsInInlineClasses
// !DIAGNOSTICS: -UNUSED_PARAMETER
inline class IC1(val x: Any) {
@@ -8,8 +8,8 @@ inline class IC1(val x: Any) {
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>unbox<!>() {}
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>unbox<!>(x: Any) {}
override fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>equals<!>(other: Any?): Boolean = true
override fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>hashCode<!>(): Int = 0
<!INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS!>override fun equals(other: Any?): Boolean<!> = true
override fun hashCode(): Int = 0
}
inline class IC2(val x: Any) {
@@ -19,15 +19,15 @@ inline class IC2(val x: Any) {
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>unbox<!>(x: Any) {}
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>unbox<!>(): Any = TODO()
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>equals<!>(my: Any, other: Any): Boolean = true
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>hashCode<!>(a: Any): Int = 0
fun equals(my: Any, other: Any): Boolean = true
fun hashCode(a: Any): Int = 0
}
inline class IC3(val x: Any) {
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>box<!>(x: Any): Any = TODO()
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>unbox<!>(x: Any): Any = TODO()
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>equals<!>(): Boolean = true
fun equals(): Boolean = true
}
interface WithBox {
@@ -1,4 +1,4 @@
// !LANGUAGE: +InlineClasses, -JvmInlineValueClasses
// !LANGUAGE: +InlineClasses, -JvmInlineValueClasses, +CustomEqualsInInlineClasses
// !DIAGNOSTICS: -UNUSED_PARAMETER
inline class IC1(val x: Any) {
@@ -8,8 +8,8 @@ inline class IC1(val x: Any) {
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>unbox<!>() {}
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>unbox<!>(x: Any) {}
override fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>equals<!>(other: Any?): Boolean = true
override fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>hashCode<!>(): Int = 0
<!INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS!>override fun equals(other: Any?): Boolean<!> = true
override fun hashCode(): Int = 0
}
inline class IC2(val x: Any) {
@@ -19,15 +19,15 @@ inline class IC2(val x: Any) {
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>unbox<!>(x: Any) {}
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>unbox<!>(): Any = TODO()
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>equals<!>(my: Any, other: Any): Boolean = true
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>hashCode<!>(a: Any): Int = 0
fun equals(my: Any, other: Any): Boolean = true
fun hashCode(a: Any): Int = 0
}
inline class IC3(val x: Any) {
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>box<!>(x: Any): Any = TODO()
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>unbox<!>(x: Any): Any = TODO()
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>equals<!>(): Boolean = true
fun equals(): Boolean = true
}
interface WithBox {
@@ -0,0 +1,22 @@
// 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) {
}
}
@@ -0,0 +1,20 @@
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
}
@@ -1,5 +1,5 @@
// !SKIP_JAVAC
// !LANGUAGE: +InlineClasses
// !LANGUAGE: +InlineClasses, +CustomEqualsInInlineClasses
// ALLOW_KOTLIN_PACKAGE
// !DIAGNOSTICS: -UNUSED_PARAMETER
@@ -15,8 +15,8 @@ value class IC1(val x: Any) {
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>unbox<!>() {}
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>unbox<!>(x: Any) {}
override fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>equals<!>(other: Any?): Boolean = true
override fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>hashCode<!>(): Int = 0
<!INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS!>override fun equals(other: Any?): Boolean<!> = true
override fun hashCode(): Int = 0
}
@JvmInline
@@ -27,8 +27,8 @@ value class IC2(val x: Any) {
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>unbox<!>(x: Any) {}
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>unbox<!>(): Any = TODO()
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>equals<!>(my: Any, other: Any): Boolean = true
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>hashCode<!>(a: Any): Int = 0
fun equals(my: Any, other: Any): Boolean = true
fun hashCode(a: Any): Int = 0
}
@JvmInline
@@ -36,7 +36,7 @@ value class IC3(val x: Any) {
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>box<!>(x: Any): Any = TODO()
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>unbox<!>(x: Any): Any = TODO()
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>equals<!>(): Boolean = true
fun equals(): Boolean = true
}
interface WithBox {
@@ -1,5 +1,5 @@
// !SKIP_JAVAC
// !LANGUAGE: +InlineClasses
// !LANGUAGE: +InlineClasses, +CustomEqualsInInlineClasses
// ALLOW_KOTLIN_PACKAGE
// !DIAGNOSTICS: -UNUSED_PARAMETER
@@ -15,8 +15,8 @@ value class IC1(val x: Any) {
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>unbox<!>() {}
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>unbox<!>(x: Any) {}
override fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>equals<!>(other: Any?): Boolean = true
override fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>hashCode<!>(): Int = 0
<!INEFFICIENT_EQUALS_OVERRIDING_IN_INLINE_CLASS!>override fun equals(other: Any?): Boolean<!> = true
override fun hashCode(): Int = 0
}
@JvmInline
@@ -27,8 +27,8 @@ value class IC2(val x: Any) {
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>unbox<!>(x: Any) {}
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>unbox<!>(): Any = TODO()
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>equals<!>(my: Any, other: Any): Boolean = true
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>hashCode<!>(a: Any): Int = 0
fun equals(my: Any, other: Any): Boolean = true
fun hashCode(a: Any): Int = 0
}
@JvmInline
@@ -36,7 +36,7 @@ value class IC3(val x: Any) {
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>box<!>(x: Any): Any = TODO()
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>unbox<!>(x: Any): Any = TODO()
fun <!RESERVED_MEMBER_INSIDE_VALUE_CLASS!>equals<!>(): Boolean = true
fun equals(): Boolean = true
}
interface WithBox {
@@ -17860,6 +17860,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
runTest("compiler/testData/diagnostics/tests/inlineClasses/identityComparisonWithInlineClasses.kt");
}
@Test
@TestMetadata("illegalEqualsOverridingInInlineClass.kt")
public void testIllegalEqualsOverridingInInlineClass() throws Exception {
runTest("compiler/testData/diagnostics/tests/inlineClasses/illegalEqualsOverridingInInlineClass.kt");
}
@Test
@TestMetadata("inlineClassCanImplementInterfaceByDelegation.kt")
public void testInlineClassCanImplementInterfaceByDelegation() throws Exception {
@@ -17968,6 +17974,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
runTest("compiler/testData/diagnostics/tests/inlineClasses/synchronizedForbidden.kt");
}
@Test
@TestMetadata("typedEqualsOperatorModifierInInlineClass.kt")
public void testTypedEqualsOperatorModifierInInlineClass() throws Exception {
runTest("compiler/testData/diagnostics/tests/inlineClasses/typedEqualsOperatorModifierInInlineClass.kt");
}
@Test
@TestMetadata("unsignedLiteralsWithoutArtifactOnClasspath.kt")
public void testUnsignedLiteralsWithoutArtifactOnClasspath() throws Exception {
@@ -20705,6 +20705,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassFunctionInvokeGeneric.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("inlineClassHashCodeOverride.kt")
public void testInlineClassHashCodeOverride() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassHashCodeOverride.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("inlineClassImplementsCollection.kt")
public void testInlineClassImplementsCollection() throws Exception {
@@ -21353,6 +21353,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassAsLastExpressionInInLambdaGeneric.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("inlineClassBothEqualsOverride.kt")
public void testInlineClassBothEqualsOverride() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassBothEqualsOverride.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("inlineClassEqualityShouldUseTotalOrderForFloatingPointData.kt")
public void testInlineClassEqualityShouldUseTotalOrderForFloatingPointData() throws Exception {
@@ -21365,6 +21371,24 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassEqualityShouldUseTotalOrderForFloatingPointDataGeneric.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("inlineClassEqualsConsistency.kt")
public void testInlineClassEqualsConsistency() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassEqualsConsistency.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("inlineClassEqualsOverride.kt")
public void testInlineClassEqualsOverride() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("inlineClassEqualsOverridenForCollections.kt")
public void testInlineClassEqualsOverridenForCollections() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverridenForCollections.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("inlineClassFieldHandling.kt")
public void testInlineClassFieldHandling() throws Exception {
@@ -21389,6 +21413,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassFunctionInvokeGeneric.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("inlineClassHashCodeOverride.kt")
public void testInlineClassHashCodeOverride() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassHashCodeOverride.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("inlineClassImplementsCollection.kt")
public void testInlineClassImplementsCollection() throws Exception {
@@ -21437,6 +21467,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassPropertyReferenceGetAndSetGeneric.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("inlineClassUntypedEqualsOverriden.kt")
public void testInlineClassUntypedEqualsOverriden() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassUntypedEqualsOverriden.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("inlineClassValueCapturedInInlineLambda.kt")
public void testInlineClassValueCapturedInInlineLambda() throws Exception {
@@ -17420,6 +17420,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassFunctionInvokeGeneric.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@TestMetadata("inlineClassHashCodeOverride.kt")
public void testInlineClassHashCodeOverride() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassHashCodeOverride.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@TestMetadata("inlineClassImplementsCollection.kt")
public void testInlineClassImplementsCollection() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassImplementsCollection.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
@@ -284,6 +284,8 @@ enum class LanguageFeature(
DataObjects(KOTLIN_1_9), // KT-4107
ProhibitAccessToEnumCompanionMembersInEnumConstructorCall(KOTLIN_1_9, kind = BUG_FIX), // KT-49110
ReferencesToSyntheticJavaProperties(KOTLIN_1_9), // KT-8575
CustomEqualsInInlineClasses(KOTLIN_1_9, kind = UNSTABLE_FEATURE), // KT-24874
// Disabled for indefinite time. See KT-53751
IgnoreNullabilityForErasedValueParameters(sinceVersion = null, kind = BUG_FIX),
@@ -11,9 +11,13 @@ 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.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.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.sure
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
@@ -88,3 +92,14 @@ fun DeclarationDescriptor.containingPackage(): FqName? {
}
object DeserializedDeclarationsFromSupertypeConflictDataKey : CallableDescriptor.UserDataKey<CallableMemberDescriptor>
fun FunctionDescriptor.isTypedEqualsInInlineClass(): Boolean = name == OperatorNameConventions.EQUALS
&& (returnType?.isBoolean() ?: false) && containingDeclaration.isInlineClass()
&& valueParameters.size == 1 && valueParameters[0].type == (containingDeclaration as? ClassDescriptor)?.defaultType
&& contextReceiverParameters.isEmpty() && extensionReceiverParameter == null
fun FunctionDescriptor.overridesEqualsFromAny(): Boolean = name == OperatorNameConventions.EQUALS
&& (returnType?.isBoolean() ?: false)
&& valueParameters.size == 1 && valueParameters[0].type.isNullableAny()
&& contextReceiverParameters.isEmpty() && extensionReceiverParameter == null
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.resolve.descriptorUtil.declaresOrInheritsDefaultValue
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.isInlineClass
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitClassReceiver
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
@@ -198,8 +199,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() }) {
"must override ''equals()'' in Any"
ensure(containingDeclaration.isAny() || overriddenDescriptors.any { it.containingDeclaration.isAny() }
|| (containingDeclaration.isInlineClass() && isTypedEqualsInInlineClass())) {
buildString {
append("must override ''equals()'' in Any")
if (containingDeclaration.isInlineClass()) {
append(" or define ''equals(other: ${containingDeclaration.name}): Boolean''")
}
}
}
},
Checks(COMPARE_TO, MemberOrExtension, ReturnsInt, SingleValueParameter, NoDefaultAndVarargsCheck),
@@ -16291,6 +16291,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassFunctionInvokeGeneric.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
}
@Test
@TestMetadata("inlineClassHashCodeOverride.kt")
public void testInlineClassHashCodeOverride() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassHashCodeOverride.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
}
@Test
@TestMetadata("inlineClassInInitBlock.kt")
public void testInlineClassInInitBlock() throws Exception {
@@ -16309,6 +16309,12 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassFunctionInvokeGeneric.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
}
@Test
@TestMetadata("inlineClassHashCodeOverride.kt")
public void testInlineClassHashCodeOverride() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassHashCodeOverride.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
}
@Test
@TestMetadata("inlineClassInInitBlock.kt")
public void testInlineClassInInitBlock() throws Exception {
@@ -14475,6 +14475,11 @@ public class IrCodegenBoxWasmTestGenerated extends AbstractIrCodegenBoxWasmTest
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassFunctionInvokeGeneric.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
}
@TestMetadata("inlineClassHashCodeOverride.kt")
public void testInlineClassHashCodeOverride() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassHashCodeOverride.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
}
@TestMetadata("inlineClassInInitBlock.kt")
public void testInlineClassInInitBlock() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassInInitBlock.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
@@ -16706,6 +16706,7 @@ public class NativeCodegenBoxTestGenerated extends AbstractNativeCodegenBoxTest
register("compiler/testData/codegen/box/inlineClasses/inlineClassFieldHandlingGeneric.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
register("compiler/testData/codegen/box/inlineClasses/inlineClassFunctionInvoke.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
register("compiler/testData/codegen/box/inlineClasses/inlineClassFunctionInvokeGeneric.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
register("compiler/testData/codegen/box/inlineClasses/inlineClassHashCodeOverride.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
register("compiler/testData/codegen/box/inlineClasses/inlineClassInInitBlock.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
register("compiler/testData/codegen/box/inlineClasses/inlineClassInInitBlockGeneric.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
register("compiler/testData/codegen/box/inlineClasses/inlineClassInStringTemplate.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
@@ -17935,6 +17936,13 @@ public class NativeCodegenBoxTestGenerated extends AbstractNativeCodegenBoxTest
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassFunctionInvokeGeneric.kt");
}
@Test
@TestMetadata("inlineClassHashCodeOverride.kt")
public void testInlineClassHashCodeOverride() throws Exception {
// There is a registered source transformer for the testcase: TransformersFunctions.getRemoveOptionalJvmInlineAnnotation()
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassHashCodeOverride.kt");
}
@Test
@TestMetadata("inlineClassInInitBlock.kt")
public void testInlineClassInInitBlock() throws Exception {