Improve support of custom equals in inline classes

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

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

- Forbid type parameters in typed equals operator declaration

^KT-54909 fixed
^KT-54910 fixed
This commit is contained in:
vladislav.grechko
2022-11-11 16:44:03 +01:00
parent 02484baf07
commit 36b8ba8df3
38 changed files with 369 additions and 136 deletions
@@ -10,14 +10,11 @@ import org.jetbrains.kotlin.incremental.components.LookupLocation
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.isInlineClass
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.KotlinTypeFactory
import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
import org.jetbrains.kotlin.types.typeUtil.isBoolean
import org.jetbrains.kotlin.types.typeUtil.isNullableAny
import org.jetbrains.kotlin.types.typeUtil.*
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.sure
import kotlin.contracts.ExperimentalContracts
@@ -94,10 +91,16 @@ fun DeclarationDescriptor.containingPackage(): FqName? {
object DeserializedDeclarationsFromSupertypeConflictDataKey : CallableDescriptor.UserDataKey<CallableMemberDescriptor>
fun FunctionDescriptor.isTypedEqualsInInlineClass() = name == OperatorNameConventions.EQUALS
&& (returnType?.isBoolean() ?: false) && containingDeclaration.isInlineClass()
&& valueParameters.size == 1 && valueParameters[0].type.constructor.declarationDescriptor.classId == (containingDeclaration as? ClassDescriptor)?.classId
&& contextReceiverParameters.isEmpty() && extensionReceiverParameter == null
fun FunctionDescriptor.isTypedEqualsInInlineClass(): Boolean {
val inlineClassStarProjection =
(containingDeclaration as? ClassDescriptor)?.takeIf { it.isInlineClass() }?.defaultType?.replaceArgumentsWithStarProjections()
?: return false
val returnType = returnType ?: return false
return name == OperatorNameConventions.EQUALS
&& (returnType.isBoolean() || returnType.isNothing())
&& valueParameters.size == 1 && valueParameters[0].type == inlineClassStarProjection
&& contextReceiverParameters.isEmpty() && extensionReceiverParameter == null
}
fun FunctionDescriptor.overridesEqualsFromAny(): Boolean = name == OperatorNameConventions.EQUALS
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.ReflectionTypes
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.resolve.descriptorUtil.declaresOrInheritsDefaultValue
@@ -29,6 +30,7 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitClassReceiver
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
import org.jetbrains.kotlin.types.typeUtil.replaceArgumentsWithStarProjections
import org.jetbrains.kotlin.util.MemberKindCheck.Member
import org.jetbrains.kotlin.util.MemberKindCheck.MemberOrExtension
import org.jetbrains.kotlin.util.OperatorNameConventions.ASSIGNMENT_OPERATIONS
@@ -198,12 +200,14 @@ object OperatorChecks : AbstractModifierChecks() {
Checks(RANGE_UNTIL, MemberOrExtension, SingleValueParameter, NoDefaultAndVarargsCheck),
Checks(EQUALS, Member) {
fun DeclarationDescriptor.isAny() = this is ClassDescriptor && KotlinBuiltIns.isAny(this)
ensure(containingDeclaration.isAny() || overriddenDescriptors.any { it.containingDeclaration.isAny() }
|| (containingDeclaration.isInlineClass() && isTypedEqualsInInlineClass())) {
ensure(containingDeclaration.isAny() || overriddenDescriptors.any { it.containingDeclaration.isAny() } || isTypedEqualsInInlineClass()) {
buildString {
append("must override ''equals()'' in Any")
if (containingDeclaration.isInlineClass()) {
append(" or define ''equals(other: ${containingDeclaration.name}): Boolean''")
val expectedParameterTypeRendered = DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(
(containingDeclaration as ClassDescriptor).defaultType.replaceArgumentsWithStarProjections()
)
append(" or define ''equals(other: $expectedParameterTypeRendered): Boolean''")
}
}
}