From cc70f7802779537c4b45bb9d243babfce9d4f273 Mon Sep 17 00:00:00 2001 From: Nikita Bobko Date: Wed, 4 Oct 2023 16:11:56 +0200 Subject: [PATCH] 1/2 Extract K1 KMP matcher in its own subsystem. Copy files This commit is literally this shell command: ``` cp compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualAnnotationMatchChecker.kt compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1AbstractExpectActualAnnotationMatchChecker.kt cp compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualCompatibilityChecker.kt compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1AbstractExpectActualCompatibilityChecker.kt cp compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/ExpectActualCollectionArgumentsCompatibilityCheckStrategy.kt compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1ExpectActualCollectionArgumentsCompatibilityCheckStrategy.kt cp core/compiler.common/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectActualCompatibility.kt compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1ExpectActualCompatibility.kt cp compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/ExpectActualMatchingContext.kt compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1ExpectActualMatchingContext.kt cp core/compiler.common/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectActualMemberDiff.kt compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1ExpectActualMemberDiff.kt ``` Motivation: KMP is going to evolve in K2 a lot. But we don't want to touch K1 version of KMP. That's why it's easeir to just copy-paste expect-actual matcher into K1 flavor --- ...tractExpectActualAnnotationMatchChecker.kt | 286 ++++++++ ...bstractExpectActualCompatibilityChecker.kt | 693 ++++++++++++++++++ ...tionArgumentsCompatibilityCheckStrategy.kt | 37 + .../K1ExpectActualCompatibility.kt | 115 +++ .../K1ExpectActualMatchingContext.kt | 219 ++++++ .../multiplatform/K1ExpectActualMemberDiff.kt | 87 +++ 6 files changed, 1437 insertions(+) create mode 100644 compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1AbstractExpectActualAnnotationMatchChecker.kt create mode 100644 compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1AbstractExpectActualCompatibilityChecker.kt create mode 100644 compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1ExpectActualCollectionArgumentsCompatibilityCheckStrategy.kt create mode 100644 compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1ExpectActualCompatibility.kt create mode 100644 compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1ExpectActualMatchingContext.kt create mode 100644 compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1ExpectActualMemberDiff.kt diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1AbstractExpectActualAnnotationMatchChecker.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1AbstractExpectActualAnnotationMatchChecker.kt new file mode 100644 index 00000000000..40f5c353fd5 --- /dev/null +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1AbstractExpectActualAnnotationMatchChecker.kt @@ -0,0 +1,286 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.resolve.calls.mpp + +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.mpp.* +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.name.StandardClassIds +import org.jetbrains.kotlin.resolve.checkers.OptInNames +import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility +import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualAnnotationsIncompatibilityType as IncompatibilityType + +object AbstractExpectActualAnnotationMatchChecker { + private val SKIPPED_CLASS_IDS = setOf( + StandardClassIds.Annotations.Deprecated, + StandardClassIds.Annotations.DeprecatedSinceKotlin, + StandardClassIds.Annotations.ImplicitlyActualizedByJvmDeclaration, + StandardClassIds.Annotations.OptionalExpectation, + StandardClassIds.Annotations.RequireKotlin, + StandardClassIds.Annotations.SinceKotlin, + StandardClassIds.Annotations.Suppress, + StandardClassIds.Annotations.WasExperimental, + OptInNames.OPT_IN_CLASS_ID, + ) + + class Incompatibility( + /** + * [expectSymbol] and [actualSymbol] are declaration symbols where annotation been mismatched. + * They are needed for writing whole declarations in diagnostic text. + * They are not the same as symbols passed to checker as arguments in [areAnnotationsCompatible] in following cases: + * 1. If [actualSymbol] is typealias, it will be expanded. + * 2. If problem is in class member, [expectSymbol] will be mismatched member, not the original class. + * 3. If annotation mismatched on function value parameter, symbols will be whole functions, not value parameter symbols. + */ + val expectSymbol: DeclarationSymbolMarker, + val actualSymbol: DeclarationSymbolMarker, + + /** + * Link to source code element (possibly holding null, if no source) from actual declaration + * where mismatched actual annotation is set (or should be set if it is missing). + * Needed for the implementation of IDE intention. + */ + val actualAnnotationTargetElement: SourceElementMarker, + val type: IncompatibilityType, + ) + + fun areAnnotationsCompatible( + expectSymbol: DeclarationSymbolMarker, + actualSymbol: DeclarationSymbolMarker, + context: ExpectActualMatchingContext<*>, + ): Incompatibility? = with(context) { + areAnnotationsCompatible(expectSymbol, actualSymbol) + } + + context (ExpectActualMatchingContext<*>) + private fun areAnnotationsCompatible( + expectSymbol: DeclarationSymbolMarker, + actualSymbol: DeclarationSymbolMarker, + ): Incompatibility? { + return when (expectSymbol) { + is CallableSymbolMarker -> { + areCallableAnnotationsCompatible(expectSymbol, actualSymbol as CallableSymbolMarker) + } + is RegularClassSymbolMarker -> { + areClassAnnotationsCompatible(expectSymbol, actualSymbol as ClassLikeSymbolMarker) + } + else -> error("Incorrect types: $expectSymbol $actualSymbol") + } + } + + context (ExpectActualMatchingContext<*>) + private fun areCallableAnnotationsCompatible( + expectSymbol: CallableSymbolMarker, + actualSymbol: CallableSymbolMarker, + ): Incompatibility? { + commonForClassAndCallableChecks(expectSymbol, actualSymbol)?.let { return it } + areAnnotationsOnValueParametersCompatible(expectSymbol, actualSymbol)?.let { return it } + + if (expectSymbol is PropertySymbolMarker && actualSymbol is PropertySymbolMarker) { + arePropertyGetterAndSetterAnnotationsCompatible(expectSymbol, actualSymbol)?.let { return it } + } + + return null + } + + context (ExpectActualMatchingContext<*>) + private fun arePropertyGetterAndSetterAnnotationsCompatible( + expectSymbol: PropertySymbolMarker, + actualSymbol: PropertySymbolMarker, + ): Incompatibility? { + listOf( + expectSymbol.getter to actualSymbol.getter, + expectSymbol.setter to actualSymbol.setter, + ).forEach { (expectAccessor, actualAccessor) -> + if (expectAccessor != null && actualAccessor != null) { + areAnnotationsSetOnDeclarationsCompatible(expectAccessor, actualAccessor)?.let { + // Write containing declarations into diagnostic + return Incompatibility(expectSymbol, actualSymbol, actualAccessor.getSourceElement(), it.type) + } + } + } + return null + } + + context (ExpectActualMatchingContext<*>) + private fun areClassAnnotationsCompatible( + expectSymbol: RegularClassSymbolMarker, + actualSymbol: ClassLikeSymbolMarker, + ): Incompatibility? { + if (actualSymbol is TypeAliasSymbolMarker) { + val expanded = actualSymbol.expandToRegularClass() ?: return null + return areClassAnnotationsCompatible(expectSymbol, expanded) + } + check(actualSymbol is RegularClassSymbolMarker) + + commonForClassAndCallableChecks(expectSymbol, actualSymbol)?.let { return it } + + if (checkClassScopesForAnnotationCompatibility) { + checkAnnotationsInClassMemberScope(expectSymbol, actualSymbol)?.let { return it } + } + if (expectSymbol.classKind == ClassKind.ENUM_CLASS && actualSymbol.classKind == ClassKind.ENUM_CLASS) { + checkAnnotationsOnEnumEntries(expectSymbol, actualSymbol)?.let { return it } + } + + return null + } + + context (ExpectActualMatchingContext<*>) + private fun commonForClassAndCallableChecks( + expectSymbol: DeclarationSymbolMarker, + actualSymbol: DeclarationSymbolMarker, + ): Incompatibility? { + areAnnotationsSetOnDeclarationsCompatible(expectSymbol, actualSymbol)?.let { return it } + areAnnotationsOnTypeParametersCompatible(expectSymbol, actualSymbol)?.let { return it } + + return null + } + + context (ExpectActualMatchingContext<*>) + private fun areAnnotationsOnValueParametersCompatible( + expectSymbol: CallableSymbolMarker, + actualSymbol: CallableSymbolMarker, + ): Incompatibility? { + val expectParams = expectSymbol.valueParameters + val actualParams = actualSymbol.valueParameters + + if (expectParams.size != actualParams.size) return null + + return expectParams.zip(actualParams).firstNotNullOfOrNull { (expectParam, actualParam) -> + areAnnotationsSetOnDeclarationsCompatible(expectParam, actualParam)?.let { + // Write containing declarations into diagnostic + Incompatibility(expectSymbol, actualSymbol, actualParam.getSourceElement(), it.type) + } + } + } + + context (ExpectActualMatchingContext<*>) + private fun areAnnotationsOnTypeParametersCompatible( + expectSymbol: DeclarationSymbolMarker, + actualSymbol: DeclarationSymbolMarker, + ): Incompatibility? { + fun DeclarationSymbolMarker.getTypeParameters(): List? { + return when (this) { + is FunctionSymbolMarker -> typeParameters + is RegularClassSymbolMarker -> typeParameters + else -> null + } + } + + val expectParams = expectSymbol.getTypeParameters() ?: return null + val actualParams = actualSymbol.getTypeParameters() ?: return null + if (expectParams.size != actualParams.size) return null + + return expectParams.zip(actualParams).firstNotNullOfOrNull { (expectParam, actualParam) -> + areAnnotationsSetOnDeclarationsCompatible(expectParam, actualParam)?.let { + // Write containing declarations into diagnostic + Incompatibility(expectSymbol, actualSymbol, actualParam.getSourceElement(), it.type) + } + } + } + + context (ExpectActualMatchingContext<*>) + private fun areAnnotationsSetOnDeclarationsCompatible( + expectSymbol: DeclarationSymbolMarker, + actualSymbol: DeclarationSymbolMarker, + ): Incompatibility? { + // TODO(Roman.Efremov, KT-60671): check annotations set on types + + val skipSourceAnnotations = actualSymbol.hasSourceAnnotationsErased + val actualAnnotationsByName = actualSymbol.annotations.groupBy { it.classId } + + for (expectAnnotation in expectSymbol.annotations) { + val expectClassId = expectAnnotation.classId ?: continue + if (expectClassId in SKIPPED_CLASS_IDS || expectAnnotation.isOptIn) { + continue + } + if (expectAnnotation.isRetentionSource && skipSourceAnnotations) { + continue + } + val actualAnnotationsWithSameClassId = actualAnnotationsByName[expectClassId] ?: emptyList() + if (actualAnnotationsWithSameClassId.isEmpty()) { + return Incompatibility( + expectSymbol, + actualSymbol, + actualSymbol.getSourceElement(), + IncompatibilityType.MissingOnActual(expectAnnotation) + ) + } + val collectionCompatibilityChecker = getAnnotationCollectionArgumentsCompatibilityChecker(expectClassId) + if (actualAnnotationsWithSameClassId.none { + areAnnotationArgumentsEqual(expectAnnotation, it, collectionCompatibilityChecker) + }) { + val incompatibilityType = if (actualAnnotationsWithSameClassId.size == 1) { + IncompatibilityType.DifferentOnActual(expectAnnotation, actualAnnotationsWithSameClassId.single()) + } else { + // In the case of repeatable annotations, we can't choose on which to report + IncompatibilityType.MissingOnActual(expectAnnotation) + } + return Incompatibility(expectSymbol, actualSymbol, actualSymbol.getSourceElement(), incompatibilityType) + } + } + return null + } + + private fun getAnnotationCollectionArgumentsCompatibilityChecker(annotationClassId: ClassId): + ExpectActualCollectionArgumentsCompatibilityCheckStrategy { + return if (annotationClassId == StandardClassIds.Annotations.Target) { + ExpectActualCollectionArgumentsCompatibilityCheckStrategy.ExpectIsSubsetOfActual + } else { + ExpectActualCollectionArgumentsCompatibilityCheckStrategy.Default + } + } + + context (ExpectActualMatchingContext<*>) + private fun checkAnnotationsInClassMemberScope( + expectClass: RegularClassSymbolMarker, + actualClass: RegularClassSymbolMarker, + ): Incompatibility? { + for (actualMember in actualClass.collectAllMembers(isActualDeclaration = true)) { + if (skipCheckingAnnotationsOfActualClassMember(actualMember)) { + continue + } + val expectToCompatibilityMap = findPotentialExpectClassMembersForActual( + expectClass, actualClass, actualMember, + // Optimization: don't check class scopes, because: + // 1. Annotation checker runs no matter if found expect class is compatible or not. + // 2. Class always has at most one corresponding `expect` class (unlike for functions, which may have several overrides), + // so we are sure that we found the right member. + checkClassScopesCompatibility = false, + ) + val expectMember = expectToCompatibilityMap.filter { it.value == ExpectActualCompatibility.Compatible }.keys.singleOrNull() + // Check also incompatible members if only one is found + ?: expectToCompatibilityMap.keys.singleOrNull() + ?: continue + areAnnotationsCompatible(expectMember, actualMember)?.let { return it } + } + return null + } + + context (ExpectActualMatchingContext<*>) + private fun checkAnnotationsOnEnumEntries( + expectClassSymbol: RegularClassSymbolMarker, + actualClassSymbol: RegularClassSymbolMarker, + ): Incompatibility? { + fun DeclarationSymbolMarker.getEnumEntryName(): Name = + when (this) { + is CallableSymbolMarker -> callableId.callableName + is RegularClassSymbolMarker -> classId.shortClassName + else -> error("Unexpected type $this") + } + + val expectEnumEntries = expectClassSymbol.collectEnumEntries() + val actualEnumEntriesByName = actualClassSymbol.collectEnumEntries().associateBy { it.getEnumEntryName() } + + for (expectEnumEntry in expectEnumEntries) { + val actualEnumEntry = actualEnumEntriesByName[expectEnumEntry.getEnumEntryName()] ?: continue + areAnnotationsSetOnDeclarationsCompatible(expectEnumEntry, actualEnumEntry) + ?.let { return it } + } + return null + } +} \ No newline at end of file diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1AbstractExpectActualCompatibilityChecker.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1AbstractExpectActualCompatibilityChecker.kt new file mode 100644 index 00000000000..1a27593c876 --- /dev/null +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1AbstractExpectActualCompatibilityChecker.kt @@ -0,0 +1,693 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +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 +import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility +import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility.Incompatible +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 java.util.* + +object AbstractExpectActualCompatibilityChecker { + 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, + parentSubstitutor: TypeSubstitutorMarker?, + expectContainingClass: RegularClassSymbolMarker?, + actualContainingClass: RegularClassSymbolMarker?, + context: ExpectActualMatchingContext, + ): ExpectActualCompatibility { + val result = with(context) { + getCallablesCompatibility(expectDeclaration, actualDeclaration, parentSubstitutor, expectContainingClass, actualContainingClass) + } + @Suppress("UNCHECKED_CAST") + return result as ExpectActualCompatibility + } + + fun matchSingleExpectTopLevelDeclarationAgainstPotentialActuals( + expectDeclaration: DeclarationSymbolMarker, + actualDeclarations: List, + context: ExpectActualMatchingContext, + ) { + with(context) { + matchSingleExpectAgainstPotentialActuals( + expectDeclaration, + actualDeclarations, + substitutor = null, + expectClassSymbol = null, + actualClassSymbol = null, + unfulfilled = null, + checkClassScopesCompatibility = true, + ) + } + } + + context(ExpectActualMatchingContext<*>) + @Suppress("warnings") + private fun getClassifiersCompatibility( + 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<*>? { + // 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) { + "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") + } + + 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, + actualTypeParameterSymbols, + 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 + } + + context(ExpectActualMatchingContext<*>) + private fun areCompatibleSupertypes( + 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) + } + } + } + + 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)) { + if (expectMember is CallableSymbolMarker && expectMember.shouldSkipMatching(expectClassSymbol)) continue + + val actualMembers = actualMembersByName[expectMember.name]?.filter { actualMember -> + expectMember is CallableSymbolMarker && actualMember is CallableSymbolMarker || + expectMember is RegularClassSymbolMarker && actualMember is RegularClassSymbolMarker + }.orEmpty() + + matchSingleExpectAgainstPotentialActuals( + expectMember, + actualMembers, + substitutor, + expectClassSymbol, + actualClassSymbol, + unfulfilled, + checkClassScopesCompatibility = true, + ) + } + + 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<*>) + private fun matchSingleExpectAgainstPotentialActuals( + expectMember: DeclarationSymbolMarker, + actualMembers: List, + substitutor: TypeSubstitutorMarker?, + expectClassSymbol: RegularClassSymbolMarker?, + actualClassSymbol: RegularClassSymbolMarker?, + unfulfilled: MutableList, List>>>?, + checkClassScopesCompatibility: Boolean, + ) { + val mapping = actualMembers.keysToMap { actualMember -> + when (expectMember) { + is CallableSymbolMarker -> getCallablesCompatibility( + expectMember, + actualMember as CallableSymbolMarker, + substitutor, + expectClassSymbol, + actualClassSymbol + ) + + is RegularClassSymbolMarker -> { + val parentSubstitutor = substitutor?.takeIf { !innerClassesCapturesOuterTypeParameters } + getClassifiersCompatibility( + expectMember, + actualMember as ClassLikeSymbolMarker, + parentSubstitutor, + checkClassScopesCompatibility, + ) + } + else -> error("Unsupported declaration: $expectMember ($actualMembers)") + } + } + + val incompatibilityMap = mutableMapOf, MutableList>() + for ((actualMember, compatibility) in mapping) { + when (compatibility) { + ExpectActualCompatibility.Compatible -> { + onMatchedMembers(expectMember, actualMember, expectClassSymbol, actualClassSymbol) + return + } + + is Incompatible -> incompatibilityMap.getOrPut(compatibility) { SmartList() }.add(actualMember) + } + } + + unfulfilled?.add(expectMember to incompatibilityMap) + onMismatchedMembersFromClassScope(expectMember, incompatibilityMap, expectClassSymbol, actualClassSymbol) + } + + context(ExpectActualMatchingContext<*>) + private fun getCallablesCompatibility( + expectDeclaration: CallableSymbolMarker, + actualDeclaration: CallableSymbolMarker, + parentSubstitutor: TypeSubstitutorMarker?, + expectContainingClass: RegularClassSymbolMarker?, + actualContainingClass: RegularClassSymbolMarker?, + ): ExpectActualCompatibility<*> { + require( + (expectDeclaration is ConstructorSymbolMarker && actualDeclaration is ConstructorSymbolMarker) || + expectDeclaration.callableId.callableName == actualDeclaration.callableId.callableName + ) { + "This function should be invoked only for declarations with the same name: $expectDeclaration, $actualDeclaration" + } + require((expectDeclaration.dispatchReceiverType == null) == (actualDeclaration.dispatchReceiverType == null)) { + "This function should be invoked only for declarations in the same kind of container (both members or both top level): $expectDeclaration, $actualDeclaration" + } + + if ( + enumConstructorsAreAlwaysCompatible && + expectContainingClass?.classKind == ClassKind.ENUM_CLASS && + actualContainingClass?.classKind == ClassKind.ENUM_CLASS && + expectDeclaration is ConstructorSymbolMarker && + actualDeclaration is ConstructorSymbolMarker + ) { + return ExpectActualCompatibility.Compatible + } + + // We must prioritize to return STRONG incompatible over WEAK incompatible (because STRONG incompatibility allows to search for overloads) + return getCallablesStrongIncompatibility(expectDeclaration, actualDeclaration, parentSubstitutor) + ?: getCallablesWeakIncompatibility(expectDeclaration, actualDeclaration, expectContainingClass, actualContainingClass) + ?: ExpectActualCompatibility.Compatible + } + + context(ExpectActualMatchingContext<*>) + private fun getCallablesStrongIncompatibility( + expectDeclaration: CallableSymbolMarker, + actualDeclaration: CallableSymbolMarker, + parentSubstitutor: TypeSubstitutorMarker?, + ): Incompatible.StrongIncompatible<*>? { + if (expectDeclaration is FunctionSymbolMarker != actualDeclaration is FunctionSymbolMarker) { + return Incompatible.CallableKind + } + + val expectedReceiverType = expectDeclaration.extensionReceiverType + val actualReceiverType = actualDeclaration.extensionReceiverType + if ((expectedReceiverType != null) != (actualReceiverType != null)) { + return Incompatible.ParameterShape + } + + val expectedValueParameters = expectDeclaration.valueParameters + val actualValueParameters = actualDeclaration.valueParameters + if (!valueParametersCountCompatible(expectDeclaration, actualDeclaration, expectedValueParameters, actualValueParameters)) { + return Incompatible.ParameterCount + } + + val expectedTypeParameters = expectDeclaration.typeParameters + val actualTypeParameters = actualDeclaration.typeParameters + if (expectedTypeParameters.size != actualTypeParameters.size) { + return Incompatible.FunctionTypeParameterCount + } + + val substitutor = createExpectActualTypeParameterSubstitutor( + expectedTypeParameters, + actualTypeParameters, + parentSubstitutor + ) + + if ( + !areCompatibleTypeLists( + expectedValueParameters.toTypeList(substitutor), + actualValueParameters.toTypeList(createEmptySubstitutor()) + ) || + !areCompatibleExpectActualTypes( + expectedReceiverType?.let { substitutor.safeSubstitute(it) }, + actualReceiverType + ) + ) { + return Incompatible.ParameterTypes + } + + if (shouldCheckReturnTypesOfCallables) { + if (!areCompatibleExpectActualTypes(substitutor.safeSubstitute(expectDeclaration.returnType), actualDeclaration.returnType)) { + return Incompatible.ReturnType + } + } + + if (!areCompatibleTypeParameterUpperBounds(expectedTypeParameters, actualTypeParameters, substitutor)) { + return Incompatible.FunctionTypeParameterUpperBounds + } + + return null + } + + context(ExpectActualMatchingContext<*>) + private fun getCallablesWeakIncompatibility( + expectDeclaration: CallableSymbolMarker, + actualDeclaration: CallableSymbolMarker, + expectContainingClass: RegularClassSymbolMarker?, + actualContainingClass: RegularClassSymbolMarker?, + ): Incompatible.WeakIncompatible<*>? { + val expectedTypeParameters = expectDeclaration.typeParameters + val actualTypeParameters = actualDeclaration.typeParameters + val expectedValueParameters = expectDeclaration.valueParameters + val actualValueParameters = actualDeclaration.valueParameters + + 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, + actualDeclaration: CallableSymbolMarker, + expectValueParameters: List, + actualValueParameters: List, + ): Boolean { + if (expectValueParameters.size == actualValueParameters.size) return true + + return if (expectDeclaration.isAnnotationConstructor() && actualDeclaration.isAnnotationConstructor()) { + expectValueParameters.isEmpty() && actualValueParameters.all { it.hasDefaultValue } + } else { + false + } + } + + context(ExpectActualMatchingContext<*>) + private fun areCompatibleTypeLists( + expectedTypes: List, + actualTypes: List, + ): Boolean { + for (i in expectedTypes.indices) { + if (!areCompatibleExpectActualTypes(expectedTypes[i], actualTypes[i])) { + return false + } + } + 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, + actualTypeParameterSymbols: List, + substitutor: TypeSubstitutorMarker, + ): Boolean { + for (i in expectTypeParameterSymbols.indices) { + val expectBounds = expectTypeParameterSymbols[i].bounds + val actualBounds = actualTypeParameterSymbols[i].bounds + if ( + expectBounds.size != actualBounds.size || + !areCompatibleTypeLists(expectBounds.map { substitutor.safeSubstitute(it) }, actualBounds) + ) { + return false + } + } + + 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) { + is ConstructorSymbolMarker -> SpecialNames.INIT + is ValueParameterSymbolMarker -> parameterName + is CallableSymbolMarker -> callableId.callableName + is RegularClassSymbolMarker -> classId.shortClassName + is TypeAliasSymbolMarker -> classId.shortClassName + is TypeParameterSymbolMarker -> parameterName + else -> error("Unsupported declaration: $this") + } + + context(ExpectActualMatchingContext<*>) + private val RegularClassSymbolMarker.isCtorless: Boolean + get() = getMembersForExpectClass(SpecialNames.INIT).isEmpty() + + context(ExpectActualMatchingContext<*>) + private val RegularClassSymbolMarker.isFinal: Boolean + get() = modality == Modality.FINAL +} diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1ExpectActualCollectionArgumentsCompatibilityCheckStrategy.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1ExpectActualCollectionArgumentsCompatibilityCheckStrategy.kt new file mode 100644 index 00000000000..17228274e34 --- /dev/null +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1ExpectActualCollectionArgumentsCompatibilityCheckStrategy.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.resolve.calls.mpp + + +sealed class ExpectActualCollectionArgumentsCompatibilityCheckStrategy { + abstract fun areCompatible( + expectArg: Collection, + actualArg: Collection, + elementsEqual: (T, T) -> Boolean, + ): Boolean + + data object Default : ExpectActualCollectionArgumentsCompatibilityCheckStrategy() { + override fun areCompatible( + expectArg: Collection, + actualArg: Collection, + elementsEqual: (T, T) -> Boolean, + ): Boolean { + return expectArg.size == actualArg.size && expectArg.zip(actualArg).all { (e1, e2) -> elementsEqual(e1, e2) } + } + } + + internal data object ExpectIsSubsetOfActual : ExpectActualCollectionArgumentsCompatibilityCheckStrategy() { + override fun areCompatible( + expectArg: Collection, + actualArg: Collection, + elementsEqual: (T, T) -> Boolean, + ): Boolean { + return expectArg.all { e1 -> + actualArg.any { e2 -> elementsEqual(e1, e2) } + } + } + } +} diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1ExpectActualCompatibility.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1ExpectActualCompatibility.kt new file mode 100644 index 00000000000..b79e3163c62 --- /dev/null +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1ExpectActualCompatibility.kt @@ -0,0 +1,115 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.resolve.multiplatform + +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + +sealed class ExpectActualCompatibility { + + // Note that the reason is used in the diagnostic output, see PlatformIncompatibilityDiagnosticRenderer + sealed class Incompatible(val reason: String?) : ExpectActualCompatibility() { + + sealed class WeakIncompatible(reason: String?) : Incompatible(reason) + + // For StrongIncompatible `actual` declaration is considered as overload and error reports on expected declaration + sealed class StrongIncompatible(reason: String?) : Incompatible(reason) + + // Callables + + object CallableKind : StrongIncompatible("callable kinds are different (function vs property)") + + object ParameterShape : StrongIncompatible("parameter shapes are different (extension vs non-extension)") + + object ParameterCount : StrongIncompatible("number of value parameters is different") + + // FunctionTypeParameterCount is strong because functions can be overloaded by type parameter count + object FunctionTypeParameterCount : StrongIncompatible("number of type parameters is different") + + // ClassTypeParameterCount is weak because classes cannot be overloaded + object ClassTypeParameterCount : WeakIncompatible(FunctionTypeParameterCount.reason) + + object ParameterTypes : StrongIncompatible("parameter types are different") + object ReturnType : StrongIncompatible("return type is different") + + object ParameterNames : WeakIncompatible("parameter names are different") + object TypeParameterNames : WeakIncompatible("names of type parameters are different") + + object ValueParameterVararg : WeakIncompatible("some value parameter is vararg in one declaration and non-vararg in the other") + object ValueParameterNoinline : WeakIncompatible( + "some value parameter is noinline in one declaration and not noinline in the other" + ) + + object ValueParameterCrossinline : WeakIncompatible( + "some value parameter is crossinline in one declaration and not crossinline in the other" + ) + + // Functions + + object FunctionModifiersDifferent : WeakIncompatible("modifiers are different (suspend)") + object FunctionModifiersNotSubset : WeakIncompatible( + "some modifiers on expected declaration are missing on the actual one (infix, inline, operator)" + ) + object ActualFunctionWithDefaultParameters : + WeakIncompatible("actual function cannot have default argument values, they should be declared in the expected function") + + // Properties + + object PropertyKind : WeakIncompatible("property kinds are different (val vs var)") + object PropertyLateinitModifier : WeakIncompatible("modifiers are different (lateinit)") + object PropertyConstModifier : WeakIncompatible("modifiers are different (const)") + object PropertySetterVisibility : WeakIncompatible("setter visibility is different") + + // Classifiers + + object ClassKind : WeakIncompatible("class kinds are different (class, interface, object, enum, annotation)") + + object ClassModifiers : WeakIncompatible("modifiers are different (companion, inner, inline, value)") + + object FunInterfaceModifier : WeakIncompatible("actual declaration for fun expect interface is not a functional interface") + + object Supertypes : WeakIncompatible("some supertypes are missing in the actual declaration") + + class ClassScopes( + val unfulfilled: List, Collection>>> + ) : WeakIncompatible("some expected members have no actual ones") + + object EnumEntries : WeakIncompatible("some entries from expected enum are missing in the actual enum") + + // Common + + object Modality : WeakIncompatible("modality is different") + object Visibility : WeakIncompatible("visibility is different") + + // FunctionTypeParameterUpperBounds is weak because functions can be overloaded by type parameter upper bounds + object FunctionTypeParameterUpperBounds : StrongIncompatible("upper bounds of type parameters are different") + + // ClassTypeParameterUpperBounds is strong because classes cannot be overloaded + object ClassTypeParameterUpperBounds : WeakIncompatible(FunctionTypeParameterUpperBounds.reason) + + object TypeParameterVariance : WeakIncompatible("declaration-site variances of type parameters are different") + object TypeParameterReified : WeakIncompatible( + "some type parameter is reified in one declaration and non-reified in the other" + ) + } + + object Compatible : ExpectActualCompatibility() +} + +val ExpectActualCompatibility<*>.isCompatibleOrWeaklyIncompatible: Boolean + get() = this is ExpectActualCompatibility.Compatible + || this is ExpectActualCompatibility.Incompatible.WeakIncompatible + +val ExpectActualCompatibility<*>.compatible: Boolean + get() = this == ExpectActualCompatibility.Compatible + +@OptIn(ExperimentalContracts::class) +fun ExpectActualCompatibility<*>.isIncompatible(): Boolean { + contract { + returns(true) implies (this@isIncompatible is ExpectActualCompatibility.Incompatible<*>) + } + return !compatible +} diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1ExpectActualMatchingContext.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1ExpectActualMatchingContext.kt new file mode 100644 index 00000000000..463a88b859f --- /dev/null +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1ExpectActualMatchingContext.kt @@ -0,0 +1,219 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.resolve.calls.mpp + +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.descriptors.Visibility +import org.jetbrains.kotlin.mpp.* +import org.jetbrains.kotlin.name.CallableId +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility +import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.types.model.KotlinTypeMarker +import org.jetbrains.kotlin.types.model.TypeSubstitutorMarker +import org.jetbrains.kotlin.types.model.TypeSystemContext + +interface ExpectActualMatchingContext : TypeSystemContext { + val shouldCheckReturnTypesOfCallables: Boolean + + /* + * This flag indicates how are type parameters of inner classes stored in the specific implementation of RegularClassSymbolMarker + * + * class Outer { + * inner class Inner + * } + * + * If flag is set to `true` then `typeParameters` for class `Outer.Inner` contains both parameters: [U, R] + * Otherwise it contains only parameters of itself: [U] + * + * This flag is needed for proper calculation of substitutions for components of inner classes + */ + val innerClassesCapturesOuterTypeParameters: Boolean + get() = true + + val enumConstructorsAreAlwaysCompatible: Boolean + get() = false + + // Try to drop it once KT-61105 is fixed + val shouldCheckAbsenceOfDefaultParamsInActual: Boolean + + /** + * This flag determines, how visibilities for classes/typealiases will be matched + * - `false` means that visibilities should be identical + * - `true` means that visibility of actual class should be the same or wider comparing to expect visibility + * this means that following actualizations will be additionally allowed: + * - protected -> public + * - internal -> public + */ + val allowClassActualizationWithWiderVisibility: Boolean + get() = false + + /** + * This flag determines strategy for matching supertypes between expect and actual class + * - `false` means that expect and actual supertypes are matched one by one + * - `true` means that type of actual class should be subtype of each expect supertype of the expect class + */ + val allowTransitiveSupertypesActualization: Boolean + get() = false + + val RegularClassSymbolMarker.classId: ClassId + val TypeAliasSymbolMarker.classId: ClassId + val CallableSymbolMarker.callableId: CallableId + val TypeParameterSymbolMarker.parameterName: Name + val ValueParameterSymbolMarker.parameterName: Name + + fun TypeAliasSymbolMarker.expandToRegularClass(): RegularClassSymbolMarker? + + val RegularClassSymbolMarker.classKind: ClassKind + + val RegularClassSymbolMarker.isCompanion: Boolean + val RegularClassSymbolMarker.isInner: Boolean + val RegularClassSymbolMarker.isInline: Boolean + val RegularClassSymbolMarker.isValue: Boolean + val RegularClassSymbolMarker.isFun: Boolean + val ClassLikeSymbolMarker.typeParameters: List + + val ClassLikeSymbolMarker.modality: Modality? + val ClassLikeSymbolMarker.visibility: Visibility + + val CallableSymbolMarker.modality: Modality? + val CallableSymbolMarker.visibility: Visibility + + val RegularClassSymbolMarker.superTypes: List + val RegularClassSymbolMarker.defaultType: KotlinTypeMarker + + val CallableSymbolMarker.isExpect: Boolean + val CallableSymbolMarker.isInline: Boolean + val CallableSymbolMarker.isSuspend: Boolean + val CallableSymbolMarker.isExternal: Boolean + val CallableSymbolMarker.isInfix: Boolean + val CallableSymbolMarker.isOperator: Boolean + val CallableSymbolMarker.isTailrec: Boolean + + val PropertySymbolMarker.isVar: Boolean + val PropertySymbolMarker.isLateinit: Boolean + val PropertySymbolMarker.isConst: Boolean + + val PropertySymbolMarker.getter: FunctionSymbolMarker? + val PropertySymbolMarker.setter: FunctionSymbolMarker? + + fun createExpectActualTypeParameterSubstitutor( + expectTypeParameters: List, + actualTypeParameters: List, + parentSubstitutor: TypeSubstitutorMarker? + ): TypeSubstitutorMarker + + fun RegularClassSymbolMarker.collectAllMembers(isActualDeclaration: Boolean): List + fun RegularClassSymbolMarker.getMembersForExpectClass(name: Name): List + + fun RegularClassSymbolMarker.collectEnumEntryNames(): List + fun RegularClassSymbolMarker.collectEnumEntries(): List + + val CallableSymbolMarker.dispatchReceiverType: KotlinTypeMarker? + val CallableSymbolMarker.extensionReceiverType: KotlinTypeMarker? + val CallableSymbolMarker.returnType: KotlinTypeMarker + val CallableSymbolMarker.typeParameters: List + val FunctionSymbolMarker.valueParameters: List + + /** + * Returns all symbols that are overridden by [this] symbol + */ + fun FunctionSymbolMarker.allOverriddenDeclarationsRecursive(): Sequence + + val CallableSymbolMarker.valueParameters: List + get() = (this as? FunctionSymbolMarker)?.valueParameters ?: emptyList() + + val ValueParameterSymbolMarker.isVararg: Boolean + val ValueParameterSymbolMarker.isNoinline: Boolean + val ValueParameterSymbolMarker.isCrossinline: Boolean + val ValueParameterSymbolMarker.hasDefaultValue: Boolean + + fun CallableSymbolMarker.isAnnotationConstructor(): Boolean + + val TypeParameterSymbolMarker.bounds: List + val TypeParameterSymbolMarker.variance: Variance + val TypeParameterSymbolMarker.isReified: Boolean + + fun areCompatibleExpectActualTypes( + expectType: KotlinTypeMarker?, + actualType: KotlinTypeMarker?, + ): Boolean + + fun actualTypeIsSubtypeOfExpectType( + expectType: KotlinTypeMarker, + actualType: KotlinTypeMarker + ): Boolean + + fun RegularClassSymbolMarker.isNotSamInterface(): Boolean + + /* + * Determines should some declaration from expect class scope be checked + * - FE 1.0: skip fake overrides + * - FIR: skip fake overrides + * - IR: skip nothing + */ + fun CallableSymbolMarker.shouldSkipMatching(containingExpectClass: RegularClassSymbolMarker): Boolean + + val CallableSymbolMarker.hasStableParameterNames: Boolean + + fun onMatchedMembers( + expectSymbol: DeclarationSymbolMarker, + actualSymbol: DeclarationSymbolMarker, + containingExpectClassSymbol: RegularClassSymbolMarker?, + containingActualClassSymbol: RegularClassSymbolMarker?, + ) {} + + fun onMismatchedMembersFromClassScope( + expectSymbol: DeclarationSymbolMarker, + actualSymbolsByIncompatibility: Map, List>, + containingExpectClassSymbol: RegularClassSymbolMarker?, + containingActualClassSymbol: RegularClassSymbolMarker?, + ) {} + + val DeclarationSymbolMarker.annotations: List + + fun areAnnotationArgumentsEqual( + expectAnnotation: AnnotationCallInfo, + actualAnnotation: AnnotationCallInfo, + collectionArgumentsCompatibilityCheckStrategy: ExpectActualCollectionArgumentsCompatibilityCheckStrategy, + ): Boolean + + val DeclarationSymbolMarker.hasSourceAnnotationsErased: Boolean + + interface AnnotationCallInfo { + val annotationSymbol: Any + val classId: ClassId? + val isRetentionSource: Boolean + val isOptIn: Boolean + } + + val checkClassScopesForAnnotationCompatibility: Boolean + + /** + * Determines whether it is needed to skip checking annotations on class member in [AbstractExpectActualAnnotationMatchChecker]. + * + * This is needed to prevent checking member twice if it is real `actual` member (not fake override or member of + * class being typealiased). + * Example: + * ``` + * actual class A { + * actual fun foo() {} // 1: checked itself, 2: checked as member of A + * } + * ``` + */ + fun skipCheckingAnnotationsOfActualClassMember(actualMember: DeclarationSymbolMarker): Boolean + + fun findPotentialExpectClassMembersForActual( + expectClass: RegularClassSymbolMarker, + actualClass: RegularClassSymbolMarker, + actualMember: DeclarationSymbolMarker, + checkClassScopesCompatibility: Boolean, + ): Map> + + fun DeclarationSymbolMarker.getSourceElement(): SourceElementMarker +} diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1ExpectActualMemberDiff.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1ExpectActualMemberDiff.kt new file mode 100644 index 00000000000..c21cbd71c33 --- /dev/null +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1ExpectActualMemberDiff.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.resolve.multiplatform + +// This class will be later used by K2. That's why it's placed in compiler.common module +data class ExpectActualMemberDiff(val kind: Kind, val actualMember: M, val expectClass: C) { + /** + * Diff kinds that are legal for fake-overrides in final `expect class`, but illegal for non-final `expect class` + * + * Also see: [toMemberDiffKind] + */ + enum class Kind(val rawMessage: String) { + NonPrivateCallableAdded( + "{0}: non-private member must be declared in both the actual class and the expect class. " + + "This error happens because the expect class ''{1}'' is non-final" + ), + ReturnTypeChangedInOverride( + "{0}: the return type of this member must be the same in the expect class and the actual class. " + + "This error happens because the expect class ''{1}'' is non-final" + ), + ModalityChangedInOverride( + "{0}: the modality of this member must be the same in the expect class and the actual class. " + + "This error happens because the expect class ''{1}'' is non-final" + ), + VisibilityChangedInOverride( + "{0}: the visibility of this member must be the same in the expect class and the actual class. " + + "This error happens because the expect class ''{1}'' is non-final" + ), + SetterVisibilityChangedInOverride( + "{0}: the setter visibility of this member must be the same in the expect class and the actual class. " + + "This error happens because the expect class ''{1}'' is non-final" + ), + ParameterNameChangedInOverride( + "{0}: the parameter names of this member must be the same in the expect class and the actual class. " + + "This error happens because the expect class ''{1}'' is non-final" + ), + PropertyKindChangedInOverride( + "{0}: the property kind (val vs var) of this member must be the same in the expect class and the actual class. " + + "This error happens because the expect class ''{1}'' is non-final" + ), + LateinitChangedInOverride( + "{0}: the property modifiers (lateinit) of this member must be the same in the expect class and the actual class. " + + "This error happens because the expect class ''{1}'' is non-final" + ), + TypeParameterNamesChangedInOverride( + "{0}: the type parameter names of this member must be the same in the expect class and the actual class. " + + "This error happens because the expect class ''{1}'' is non-final" + ), + } +} + +fun ExpectActualCompatibility.Incompatible<*>.toMemberDiffKind(): ExpectActualMemberDiff.Kind? = when (this) { + ExpectActualCompatibility.Incompatible.CallableKind -> ExpectActualMemberDiff.Kind.NonPrivateCallableAdded + ExpectActualCompatibility.Incompatible.ParameterCount -> ExpectActualMemberDiff.Kind.NonPrivateCallableAdded + ExpectActualCompatibility.Incompatible.ParameterShape -> ExpectActualMemberDiff.Kind.NonPrivateCallableAdded + ExpectActualCompatibility.Incompatible.ParameterTypes -> ExpectActualMemberDiff.Kind.NonPrivateCallableAdded + ExpectActualCompatibility.Incompatible.ReturnType -> ExpectActualMemberDiff.Kind.ReturnTypeChangedInOverride + ExpectActualCompatibility.Incompatible.FunctionTypeParameterCount -> ExpectActualMemberDiff.Kind.NonPrivateCallableAdded + ExpectActualCompatibility.Incompatible.ClassTypeParameterCount -> error("Not applicable because ExpectActualMemberDiff is about members") + ExpectActualCompatibility.Incompatible.FunctionTypeParameterUpperBounds -> ExpectActualMemberDiff.Kind.NonPrivateCallableAdded + ExpectActualCompatibility.Incompatible.ClassTypeParameterUpperBounds -> error("Not applicable because ExpectActualMemberDiff is about members") + ExpectActualCompatibility.Incompatible.ActualFunctionWithDefaultParameters -> null // It's not possible to add default parameters in override + ExpectActualCompatibility.Incompatible.ClassKind -> error("Not applicable because ExpectActualMemberDiff is about members") + ExpectActualCompatibility.Incompatible.ClassModifiers -> error("Not applicable because ExpectActualMemberDiff is about members") + is ExpectActualCompatibility.Incompatible.ClassScopes -> error("Not applicable because ExpectActualMemberDiff is about members") + ExpectActualCompatibility.Incompatible.EnumEntries -> error("Not applicable because ExpectActualMemberDiff is about members") + ExpectActualCompatibility.Incompatible.FunInterfaceModifier -> error("Not applicable because ExpectActualMemberDiff is about members") + ExpectActualCompatibility.Incompatible.FunctionModifiersDifferent -> null // It's not possible to override with different function modifier (suspend) + ExpectActualCompatibility.Incompatible.FunctionModifiersNotSubset -> null // It's not possible to override with different function modifier (infix, inline, operator) + ExpectActualCompatibility.Incompatible.Modality -> ExpectActualMemberDiff.Kind.ModalityChangedInOverride + ExpectActualCompatibility.Incompatible.ParameterNames -> ExpectActualMemberDiff.Kind.ParameterNameChangedInOverride + ExpectActualCompatibility.Incompatible.PropertyConstModifier -> null // const fun can't be overridden + ExpectActualCompatibility.Incompatible.PropertyKind -> ExpectActualMemberDiff.Kind.PropertyKindChangedInOverride + ExpectActualCompatibility.Incompatible.PropertyLateinitModifier -> ExpectActualMemberDiff.Kind.LateinitChangedInOverride + ExpectActualCompatibility.Incompatible.PropertySetterVisibility -> ExpectActualMemberDiff.Kind.SetterVisibilityChangedInOverride + ExpectActualCompatibility.Incompatible.Supertypes -> error("Not applicable because ExpectActualMemberDiff is about members") + ExpectActualCompatibility.Incompatible.TypeParameterNames -> ExpectActualMemberDiff.Kind.TypeParameterNamesChangedInOverride + ExpectActualCompatibility.Incompatible.TypeParameterReified -> null // inline fun can't be overridden + ExpectActualCompatibility.Incompatible.TypeParameterVariance -> null // Members are not allowed to have variance + ExpectActualCompatibility.Incompatible.ValueParameterCrossinline -> null // inline fun can't be overridden + ExpectActualCompatibility.Incompatible.ValueParameterNoinline -> null // inline fun can't be overridden + ExpectActualCompatibility.Incompatible.ValueParameterVararg -> ExpectActualMemberDiff.Kind.NonPrivateCallableAdded + ExpectActualCompatibility.Incompatible.Visibility -> ExpectActualMemberDiff.Kind.VisibilityChangedInOverride +}