[MPP] workaround inc/dec operator checks in expect classes

Implicit receiver type inside expect class is inconsistent with the
type resolved from an explicit reference inside that class. The former
one is a default class type, i.e. expect; the latter one resolves to
an actual type when either expect and actual are in the same module, or
when the sources are compiled for a particular platform.

The workaround is to manually actualize implicit receiver type for one
particular check affected by the broken subtyping.

KT-49714
This commit is contained in:
Pavel Kirpichenkov
2022-01-27 18:40:34 +03:00
committed by teamcity
parent 90abbb4a9f
commit 0362d4ac9f
3 changed files with 39 additions and 17 deletions
@@ -1,10 +0,0 @@
// !LANGUAGE: +MultiPlatformProjects
// SKIP_TXT
// Issue: KT-49714
expect class Counter {
operator fun inc(): Counter
operator fun dec(): Counter
}
actual typealias Counter = Int
@@ -1,10 +1,11 @@
// FIR_IDENTICAL
// !LANGUAGE: +MultiPlatformProjects
// SKIP_TXT
// Issue: KT-49714
expect class Counter {
<!INAPPLICABLE_OPERATOR_MODIFIER!>operator<!> fun inc(): Counter
<!INAPPLICABLE_OPERATOR_MODIFIER!>operator<!> fun dec(): Counter
operator fun inc(): Counter
operator fun dec(): Counter
}
actual typealias Counter = Int
@@ -18,13 +18,13 @@ package org.jetbrains.kotlin.util
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.ReflectionTypes
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.name.Name
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.scopes.receivers.ImplicitClassReceiver
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
@@ -52,6 +52,7 @@ import org.jetbrains.kotlin.util.OperatorNameConventions.SIMPLE_UNARY_OPERATION_
import org.jetbrains.kotlin.util.ReturnsCheck.*
import org.jetbrains.kotlin.util.ValueParameterCountCheck.NoValueParameters
import org.jetbrains.kotlin.util.ValueParameterCountCheck.SingleValueParameter
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
sealed class CheckResult(val isSuccess: Boolean) {
class IllegalSignature(val error: String) : CheckResult(false)
@@ -195,13 +196,43 @@ object OperatorChecks : AbstractModifierChecks() {
Checks(SIMPLE_UNARY_OPERATION_NAMES, MemberOrExtension, NoValueParameters),
Checks(listOf(INC, DEC), MemberOrExtension) {
val receiver = dispatchReceiverParameter ?: extensionReceiverParameter
ensure(receiver != null && (returnType?.isSubtypeOf(receiver.type) ?: false)) {
ensure(receiver != null && ((returnType?.isSubtypeOf(receiver.type) ?: false) || incDecCheckForExpectClass(receiver))) {
"receiver must be a supertype of the return type"
}
},
Checks(ASSIGNMENT_OPERATIONS, MemberOrExtension, ReturnsUnit, SingleValueParameter, NoDefaultAndVarargsCheck),
Checks(COMPONENT_REGEX, MemberOrExtension, NoValueParameters)
) }
)
/**
* See KT-49714
* Workaround for mismatching types of an implicit dispatch receiver inside an `expect` class
* and a type resolved from a reference to this class. During compilation all actual type aliases are known,
* so the explicit return type is `actual`. But the implicit receiver type inside the class remains `expect`
* because it's received from the default type of the containing class, which is not affected by the `actual` type alias.
*
* `actual` classes are not affected, since non-parameterized type constructors with equal fqNames are considered
* equal, so subtyping check passes in this case despite mismatching expect/actual in the corresponding declaration descriptors.
*/
private fun FunctionDescriptor.incDecCheckForExpectClass(receiver: ReceiverParameterDescriptor): Boolean {
val receiverValue = receiver.value
if (receiverValue !is ImplicitClassReceiver) return false
val classDescriptor = receiverValue.classDescriptor
if (!classDescriptor.isExpect) return false
val potentialActualAliasId = classDescriptor.classId ?: return false
val actualReceiverTypeAlias =
classDescriptor.module.findClassifierAcrossModuleDependencies(potentialActualAliasId).safeAs<TypeAliasDescriptor>()
?: return false
returnType?.let { returnType ->
return returnType.isSubtypeOf(actualReceiverTypeAlias.expandedType)
}
return false
}
}
object InfixChecks : AbstractModifierChecks() {
override val checks = listOf(