From bbbc775181f4644a05464f1fba296de3df42a128 Mon Sep 17 00:00:00 2001 From: Nikita Bobko Date: Mon, 16 Oct 2023 14:46:54 +0200 Subject: [PATCH] [FIR, IR] Drop code that don't belong to AbstractExpectActualMatcher - The Matcher must only do the "matching" (aka "strong compatibility") - Drop all "checking" related (aka "weak compatibility" related) logic - `checkClassScopesCompatibility` parameter belongs to the "expect-actual checker" not to the "expect-actual matcher". Drop it Review: https://jetbrains.team/p/kt/reviews/12750/timeline --- .../calls/mpp/AbstractExpectActualMatcher.kt | 451 +----------------- 1 file changed, 14 insertions(+), 437 deletions(-) diff --git a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualMatcher.kt b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualMatcher.kt index 74b3819a8d0..c151fd6baef 100644 --- a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualMatcher.kt +++ b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualMatcher.kt @@ -6,9 +6,6 @@ package org.jetbrains.kotlin.resolve.calls.mpp import org.jetbrains.kotlin.descriptors.ClassKind -import org.jetbrains.kotlin.descriptors.Modality -import org.jetbrains.kotlin.descriptors.Visibilities -import org.jetbrains.kotlin.descriptors.Visibility import org.jetbrains.kotlin.mpp.* import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.SpecialNames @@ -17,11 +14,8 @@ import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility.Inco import org.jetbrains.kotlin.types.model.KotlinTypeMarker import org.jetbrains.kotlin.types.model.TypeSubstitutorMarker import org.jetbrains.kotlin.utils.SmartList -import org.jetbrains.kotlin.utils.addToStdlib.enumMapOf -import org.jetbrains.kotlin.utils.addToStdlib.enumSetOf import org.jetbrains.kotlin.utils.keysToMap import org.jetbrains.kotlin.utils.zipIfSizesAreEqual -import java.util.* /** * This object is responsible for matching of expect-actual pairs. @@ -33,19 +27,6 @@ import java.util.* * See `/docs/fir/k2_kmp.md` for details */ object AbstractExpectActualMatcher { - fun getClassifiersCompatibility( - expectClassSymbol: RegularClassSymbolMarker, - actualClassLikeSymbol: ClassLikeSymbolMarker, - checkClassScopesCompatibility: Boolean, - context: ExpectActualMatchingContext, - ): ExpectActualCompatibility { - val result = with(context) { - getClassifiersCompatibility(expectClassSymbol, actualClassLikeSymbol, parentSubstitutor = null, checkClassScopesCompatibility) - } - @Suppress("UNCHECKED_CAST") - return result as ExpectActualCompatibility - } - fun getCallablesCompatibility( expectDeclaration: CallableSymbolMarker, actualDeclaration: CallableSymbolMarker, @@ -72,7 +53,6 @@ object AbstractExpectActualMatcher { expectDeclaration: DeclarationSymbolMarker, actualDeclarations: List, context: ExpectActualMatchingContext, - checkClassScopesCompatibility: Boolean, ) { with(context) { matchSingleExpectAgainstPotentialActuals( @@ -82,143 +62,31 @@ object AbstractExpectActualMatcher { expectClassSymbol = null, actualClassSymbol = null, unfulfilled = null, - checkClassScopesCompatibility = checkClassScopesCompatibility, ) } } - context(ExpectActualMatchingContext<*>) - @Suppress("warnings") - private fun getClassifiersCompatibility( + fun matchClassifiers( expectClassSymbol: RegularClassSymbolMarker, actualClassLikeSymbol: ClassLikeSymbolMarker, - parentSubstitutor: TypeSubstitutorMarker?, - checkClassScopes: Boolean, - ): ExpectActualCompatibility<*> = getClassifiersIncompatibility(expectClassSymbol, actualClassLikeSymbol, parentSubstitutor, checkClassScopes) - ?: ExpectActualCompatibility.Compatible - - context(ExpectActualMatchingContext<*>) - @Suppress("warnings") - private fun getClassifiersIncompatibility( - expectClassSymbol: RegularClassSymbolMarker, - actualClassLikeSymbol: ClassLikeSymbolMarker, - parentSubstitutor: TypeSubstitutorMarker?, - checkClassScopesCompatibility: Boolean, - ): ExpectActualCompatibility.Incompatible.WeakIncompatible<*>? { + context: ExpectActualMatchingContext, + ): ExpectActualCompatibility = with(context) { // Can't check FQ names here because nested expected class may be implemented via actual typealias's expansion with the other FQ name - require(expectClassSymbol.name == actualClassLikeSymbol.name) { + check(expectClassSymbol.name == actualClassLikeSymbol.name) { "This function should be invoked only for declarations with the same name: $expectClassSymbol, $actualClassLikeSymbol" } - - val actualClass = when (actualClassLikeSymbol) { - is RegularClassSymbolMarker -> actualClassLikeSymbol - is TypeAliasSymbolMarker -> actualClassLikeSymbol.expandToRegularClass() - ?: return null // do not report extra error on erroneous typealias - else -> error("Incorrect actual classifier for $expectClassSymbol: $actualClassLikeSymbol") + check(actualClassLikeSymbol is RegularClassSymbolMarker || actualClassLikeSymbol is TypeAliasSymbolMarker) { + "Incorrect actual classifier for $expectClassSymbol: $actualClassLikeSymbol" } - - if (!areCompatibleClassKinds(expectClassSymbol, actualClass)) return Incompatible.ClassKind - - if (!equalBy(expectClassSymbol, actualClass) { listOf(it.isCompanion, it.isInner, it.isInline || it.isValue) }) { - return Incompatible.ClassModifiers - } - - if (expectClassSymbol.isFun && !actualClass.isFun && actualClass.isNotSamInterface()) { - return Incompatible.FunInterfaceModifier - } - - val expectTypeParameterSymbols = expectClassSymbol.typeParameters - val actualTypeParameterSymbols = actualClass.typeParameters - if (expectTypeParameterSymbols.size != actualTypeParameterSymbols.size) { - return Incompatible.ClassTypeParameterCount - } - - if (!areCompatibleModalities(expectClassSymbol.modality, actualClass.modality)) { - return Incompatible.Modality - } - - if (!areCompatibleClassVisibilities(expectClassSymbol, actualClass)) { - return Incompatible.Visibility - } - - val substitutor = createExpectActualTypeParameterSubstitutor( - (expectTypeParameterSymbols zipIfSizesAreEqual actualTypeParameterSymbols) - ?: error("expect/actual type parameters sizes are checked earlier"), - parentSubstitutor - ) - - if (!areCompatibleTypeParameterUpperBounds(expectTypeParameterSymbols, actualTypeParameterSymbols, substitutor)) { - return Incompatible.ClassTypeParameterUpperBounds - } - - getTypeParametersVarianceOrReifiedIncompatibility(expectTypeParameterSymbols, actualTypeParameterSymbols) - ?.let { return it } - - if (!areCompatibleSupertypes(expectClassSymbol, actualClass, substitutor)) { - return Incompatible.Supertypes - } - - if (checkClassScopesCompatibility) { - getClassScopesIncompatibility(expectClassSymbol, actualClass, substitutor)?.let { return it } - } - - return null + ExpectActualCompatibility.Compatible } context(ExpectActualMatchingContext<*>) - private fun areCompatibleSupertypes( + private fun matchClassScopes( expectClassSymbol: RegularClassSymbolMarker, actualClassSymbol: RegularClassSymbolMarker, substitutor: TypeSubstitutorMarker, - ): Boolean { - return when (allowTransitiveSupertypesActualization) { - false -> areCompatibleSupertypesOneByOne(expectClassSymbol, actualClassSymbol, substitutor) - true -> areCompatibleSupertypesTransitive(expectClassSymbol, actualClassSymbol, substitutor) - } - } - - context(ExpectActualMatchingContext<*>) - private fun areCompatibleSupertypesOneByOne( - expectClassSymbol: RegularClassSymbolMarker, - actualClassSymbol: RegularClassSymbolMarker, - substitutor: TypeSubstitutorMarker, - ): Boolean { - // Subtract kotlin.Any from supertypes because it's implicitly added if no explicit supertype is specified, - // and not added if an explicit supertype _is_ specified - val expectSupertypes = expectClassSymbol.superTypes.filterNot { it.typeConstructor().isAnyConstructor() } - val actualSupertypes = actualClassSymbol.superTypes.filterNot { it.typeConstructor().isAnyConstructor() } - return expectSupertypes.all { expectSupertype -> - val substitutedExpectType = substitutor.safeSubstitute(expectSupertype) - actualSupertypes.any { actualSupertype -> - areCompatibleExpectActualTypes(substitutedExpectType, actualSupertype, parameterOfAnnotationComparisonMode = false) - } - } - } - - context(ExpectActualMatchingContext<*>) - private fun areCompatibleSupertypesTransitive( - expectClassSymbol: RegularClassSymbolMarker, - actualClassSymbol: RegularClassSymbolMarker, - substitutor: TypeSubstitutorMarker, - ): Boolean { - val expectSupertypes = expectClassSymbol.superTypes.filterNot { it.typeConstructor().isAnyConstructor() } - val actualType = actualClassSymbol.defaultType - return expectSupertypes.all { expectSupertype -> - actualTypeIsSubtypeOfExpectType( - expectType = substitutor.safeSubstitute(expectSupertype), - actualType = actualType - ) - } - } - - context(ExpectActualMatchingContext<*>) - private fun getClassScopesIncompatibility( - expectClassSymbol: RegularClassSymbolMarker, - actualClassSymbol: RegularClassSymbolMarker, - substitutor: TypeSubstitutorMarker, - ): Incompatible.WeakIncompatible<*>? { - val unfulfilled = arrayListOf, List>>>() - + ) { val actualMembersByName = actualClassSymbol.collectAllMembers(isActualDeclaration = true).groupBy { it.name } outer@ for (expectMember in expectClassSymbol.collectAllMembers(isActualDeclaration = false)) { @@ -235,23 +103,11 @@ object AbstractExpectActualMatcher { substitutor, expectClassSymbol, actualClassSymbol, - unfulfilled, - checkClassScopesCompatibility = true, + unfulfilled = null, ) } - if (expectClassSymbol.classKind == ClassKind.ENUM_CLASS) { - val aEntries = expectClassSymbol.collectEnumEntryNames() - val bEntries = actualClassSymbol.collectEnumEntryNames() - - if (!bEntries.containsAll(aEntries)) return Incompatible.EnumEntries - } - // TODO: check static scope? - - if (unfulfilled.isEmpty()) return null - - return Incompatible.ClassScopes(unfulfilled) } context(ExpectActualMatchingContext<*>) @@ -262,7 +118,6 @@ object AbstractExpectActualMatcher { expectClassSymbol: RegularClassSymbolMarker?, actualClassSymbol: RegularClassSymbolMarker?, unfulfilled: MutableList, List>>>?, - checkClassScopesCompatibility: Boolean, ) { val mapping = actualMembers.keysToMap { actualMember -> when (expectMember) { @@ -275,13 +130,7 @@ object AbstractExpectActualMatcher { ) is RegularClassSymbolMarker -> { - val parentSubstitutor = substitutor?.takeIf { !innerClassesCapturesOuterTypeParameters } - getClassifiersCompatibility( - expectMember, - actualMember as ClassLikeSymbolMarker, - parentSubstitutor, - checkClassScopesCompatibility, - ) + matchClassifiers(expectMember, actualMember as ClassLikeSymbolMarker, this@ExpectActualMatchingContext) } else -> error("Unsupported declaration: $expectMember ($actualMembers)") } @@ -331,17 +180,8 @@ object AbstractExpectActualMatcher { return ExpectActualCompatibility.Compatible } - // We must prioritize to return STRONG incompatible over WEAK incompatible (because STRONG incompatibility allows to search for overloads) val annotationMode = expectContainingClass?.classKind == ClassKind.ANNOTATION_CLASS return getCallablesStrongIncompatibility(expectDeclaration, actualDeclaration, annotationMode, parentSubstitutor) - ?: getCallablesWeakIncompatibility( - expectDeclaration, - actualDeclaration, - annotationMode, - parentSubstitutor, - expectContainingClass, - actualContainingClass - ) ?: ExpectActualCompatibility.Compatible } @@ -401,111 +241,6 @@ object AbstractExpectActualMatcher { return null } - context(ExpectActualMatchingContext<*>) - private fun getCallablesWeakIncompatibility( - expectDeclaration: CallableSymbolMarker, - actualDeclaration: CallableSymbolMarker, - insideAnnotationClass: Boolean, - parentSubstitutor: TypeSubstitutorMarker?, - expectContainingClass: RegularClassSymbolMarker?, - actualContainingClass: RegularClassSymbolMarker?, - ): Incompatible.WeakIncompatible<*>? { - val expectedTypeParameters = expectDeclaration.typeParameters - val actualTypeParameters = actualDeclaration.typeParameters - val expectedValueParameters = expectDeclaration.valueParameters - val actualValueParameters = actualDeclaration.valueParameters - - if (shouldCheckReturnTypesOfCallables) { - val substitutor = createExpectActualTypeParameterSubstitutor( - (expectedTypeParameters zipIfSizesAreEqual actualTypeParameters) - ?: error("expect/actual type parameters sizes are checked earlier"), - parentSubstitutor - ) - - if (!areCompatibleExpectActualTypes( - substitutor.safeSubstitute(expectDeclaration.returnType), - actualDeclaration.returnType, - parameterOfAnnotationComparisonMode = insideAnnotationClass - ) - ) { - return Incompatible.ReturnType - } - } - - if (actualDeclaration.hasStableParameterNames && !equalsBy(expectedValueParameters, actualValueParameters) { it.name }) { - return Incompatible.ParameterNames - } - - if (!equalsBy(expectedTypeParameters, actualTypeParameters) { it.name }) { - return Incompatible.TypeParameterNames - } - - val expectModality = expectDeclaration.modality - val actualModality = actualDeclaration.modality - if ( - !areCompatibleModalities( - expectModality, - actualModality, - expectContainingClass?.modality, - actualContainingClass?.modality - ) - ) { - return Incompatible.Modality - } - - if (!areCompatibleCallableVisibilities(expectDeclaration.visibility, expectModality, actualDeclaration.visibility)) { - return Incompatible.Visibility - } - - getTypeParametersVarianceOrReifiedIncompatibility(expectedTypeParameters, actualTypeParameters)?.let { return it } - - if (shouldCheckAbsenceOfDefaultParamsInActual) { - // "Default parameters in actual" check is required only for functions, because only functions can have parameters - if (actualDeclaration is FunctionSymbolMarker && expectDeclaration is FunctionSymbolMarker) { - // Actual annotation constructors can have default argument values; their consistency with arguments in the expected annotation - // is checked in ExpectedActualDeclarationChecker.checkAnnotationConstructors - if (!actualDeclaration.isAnnotationConstructor() && - // If default params came from common supertypes of actual class and expect class then it's a valid code. - // Here we filter out such default params. - (actualDeclaration.allOverriddenDeclarationsRecursive() - expectDeclaration.allOverriddenDeclarationsRecursive().toSet()) - .flatMap { it.valueParameters }.any { it.hasDefaultValue } - ) { - return Incompatible.ActualFunctionWithDefaultParameters - } - } - } - - if (!equalsBy(expectedValueParameters, actualValueParameters) { it.isVararg }) { - return Incompatible.ValueParameterVararg - } - - // Adding noinline/crossinline to parameters is disallowed, except if the expected declaration was not inline at all - if (expectDeclaration is SimpleFunctionSymbolMarker && expectDeclaration.isInline) { - if (expectedValueParameters.indices.any { i -> !expectedValueParameters[i].isNoinline && actualValueParameters[i].isNoinline }) { - return Incompatible.ValueParameterNoinline - } - if (expectedValueParameters.indices.any { i -> !expectedValueParameters[i].isCrossinline && actualValueParameters[i].isCrossinline }) { - return Incompatible.ValueParameterCrossinline - } - } - - when { - expectDeclaration is FunctionSymbolMarker && actualDeclaration is FunctionSymbolMarker -> - getFunctionsIncompatibility(expectDeclaration, actualDeclaration)?.let { return it } - - expectDeclaration is PropertySymbolMarker && actualDeclaration is PropertySymbolMarker -> - getPropertiesIncompatibility(expectDeclaration, actualDeclaration)?.let { return it } - - expectDeclaration is EnumEntrySymbolMarker && actualDeclaration is EnumEntrySymbolMarker -> { - // do nothing, entries are matched only by name - } - - else -> error("Unsupported declarations: $expectDeclaration, $actualDeclaration") - } - - return null - } - context(ExpectActualMatchingContext<*>) private fun valueParametersCountCompatible( expectDeclaration: CallableSymbolMarker, @@ -539,80 +274,6 @@ object AbstractExpectActualMatcher { return true } - context(ExpectActualMatchingContext<*>) - private fun areCompatibleClassKinds( - expectClass: RegularClassSymbolMarker, - actualClass: RegularClassSymbolMarker, - ): Boolean { - if (expectClass.classKind == actualClass.classKind) return true - - if (expectClass.classKind == ClassKind.CLASS && expectClass.isFinal && expectClass.isCtorless) { - if (actualClass.classKind == ClassKind.OBJECT) return true - } - - return false - } - - private fun areCompatibleModalities( - expectModality: Modality?, - actualModality: Modality?, - expectContainingClassModality: Modality? = null, - actualContainingClassModality: Modality? = null, - ): Boolean { - val expectEffectiveModality = effectiveModality(expectModality, expectContainingClassModality) - val actualEffectiveModality = effectiveModality(actualModality, actualContainingClassModality) - - return actualEffectiveModality in compatibleModalityMap.getValue(expectEffectiveModality) - } - - /* - * If containing class is final then all declarations in it effectively final - */ - private fun effectiveModality(declarationModality: Modality?, containingClassModality: Modality?): Modality? { - return when (containingClassModality) { - Modality.FINAL -> Modality.FINAL - else -> declarationModality - } - } - - /* - * Key is expect modality, value is a set of compatible actual modalities - */ - private val compatibleModalityMap: EnumMap> = enumMapOf( - Modality.ABSTRACT to enumSetOf(Modality.ABSTRACT), - Modality.OPEN to enumSetOf(Modality.OPEN), - Modality.FINAL to enumSetOf(Modality.OPEN, Modality.FINAL), - Modality.SEALED to enumSetOf(Modality.SEALED), - ) - - private fun areCompatibleCallableVisibilities( - expectVisibility: Visibility, - expectModality: Modality?, - actualVisibility: Visibility, - ): Boolean { - val compare = Visibilities.compare(expectVisibility, actualVisibility) - return if (expectModality != Modality.FINAL) { - // For overridable declarations visibility should match precisely, see KT-19664 - compare == 0 - } else { - // For non-overridable declarations actuals are allowed to have more permissive visibility - compare != null && compare <= 0 - } - } - - context(ExpectActualMatchingContext<*>) - private fun areCompatibleClassVisibilities( - expectClassSymbol: RegularClassSymbolMarker, - actualClassSymbol: RegularClassSymbolMarker, - ): Boolean { - val expectVisibility = expectClassSymbol.visibility - val actualVisibility = actualClassSymbol.visibility - if (expectVisibility == actualVisibility) return true - if (!allowClassActualizationWithWiderVisibility) return false - val result = Visibilities.compare(actualVisibility, expectVisibility) - return result != null && result > 0 - } - context(ExpectActualMatchingContext<*>) private fun areCompatibleTypeParameterUpperBounds( expectTypeParameterSymbols: List, @@ -633,89 +294,8 @@ object AbstractExpectActualMatcher { return true } - context(ExpectActualMatchingContext<*>) - private fun getTypeParametersVarianceOrReifiedIncompatibility( - expectTypeParameterSymbols: List, - actualTypeParameterSymbols: List, - ): Incompatible.WeakIncompatible<*>? { - if (!equalsBy(expectTypeParameterSymbols, actualTypeParameterSymbols) { it.variance }) { - return Incompatible.TypeParameterVariance - } - - // Removing "reified" from an expected function's type parameter is fine - if ( - expectTypeParameterSymbols.indices.any { i -> - !expectTypeParameterSymbols[i].isReified && actualTypeParameterSymbols[i].isReified - } - ) { - return Incompatible.TypeParameterReified - } - - return null - } - - context(ExpectActualMatchingContext<*>) - private fun getFunctionsIncompatibility( - expectFunction: CallableSymbolMarker, - actualFunction: CallableSymbolMarker, - ): Incompatible.WeakIncompatible<*>? { - if (!equalBy(expectFunction, actualFunction) { f -> f.isSuspend }) { - return Incompatible.FunctionModifiersDifferent - } - - if ( - expectFunction.isInfix && !actualFunction.isInfix || - expectFunction.isInline && !actualFunction.isInline || - expectFunction.isOperator && !actualFunction.isOperator - ) { - return Incompatible.FunctionModifiersNotSubset - } - - return null - } - - context(ExpectActualMatchingContext<*>) - private fun getPropertiesIncompatibility( - expected: PropertySymbolMarker, - actual: PropertySymbolMarker, - ): Incompatible.WeakIncompatible<*>? { - return when { - !equalBy(expected, actual) { p -> p.isVar } -> Incompatible.PropertyKind - !equalBy(expected, actual) { p -> p.isLateinit } -> Incompatible.PropertyLateinitModifier - expected.isConst && !actual.isConst -> Incompatible.PropertyConstModifier - !arePropertySettersWithCompatibleVisibilities(expected, actual) -> Incompatible.PropertySetterVisibility - else -> null - } - } - - context(ExpectActualMatchingContext<*>) - private fun arePropertySettersWithCompatibleVisibilities( - expected: PropertySymbolMarker, - actual: PropertySymbolMarker, - ): Boolean { - val expectedSetter = expected.setter ?: return true - val actualSetter = actual.setter ?: return true - return areCompatibleCallableVisibilities(expectedSetter.visibility, expectedSetter.modality, actualSetter.visibility) - } - // ---------------------------------------- Utils ---------------------------------------- - context(ExpectActualMatchingContext<*>) - private fun List.toTypeList(substitutor: TypeSubstitutorMarker): List { - return this.map { substitutor.safeSubstitute(it.returnType) } - } - - private inline fun equalsBy(first: List, second: List, selector: (T) -> K): Boolean { - for (i in first.indices) { - if (selector(first[i]) != selector(second[i])) return false - } - - return true - } - - private inline fun equalBy(first: T, second: T, selector: (T) -> K): Boolean = - selector(first) == selector(second) - context(ExpectActualMatchingContext<*>) private val DeclarationSymbolMarker.name: Name get() = when (this) { @@ -729,10 +309,7 @@ object AbstractExpectActualMatcher { } context(ExpectActualMatchingContext<*>) - private val RegularClassSymbolMarker.isCtorless: Boolean - get() = getMembersForExpectClass(SpecialNames.INIT).isEmpty() - - context(ExpectActualMatchingContext<*>) - private val RegularClassSymbolMarker.isFinal: Boolean - get() = modality == Modality.FINAL + private fun List.toTypeList(substitutor: TypeSubstitutorMarker): List { + return this.map { substitutor.safeSubstitute(it.returnType) } + } }