diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 5aea91f0f45..c4735ef249d 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -26,7 +26,7 @@ import org.jetbrains.kotlin.resolve.VarianceConflictDiagnosticData; import org.jetbrains.kotlin.resolve.calls.inference.InferenceErrorData; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.calls.tower.WrongResolutionToClassifier; -import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker.Compatibility.Incompatible; +import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver.Compatibility.Incompatible; import org.jetbrains.kotlin.serialization.deserialization.IncompatibleVersionErrorData; import org.jetbrains.kotlin.serialization.deserialization.descriptors.VersionRequirement; import org.jetbrains.kotlin.types.KotlinType; diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/PositioningStrategies.kt b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/PositioningStrategies.kt index 8ec7d38d694..2ee4539f0e9 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/PositioningStrategies.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/PositioningStrategies.kt @@ -27,8 +27,8 @@ import org.jetbrains.kotlin.lexer.KtModifierKeywordToken import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.* -import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker.Compatibility.Incompatible -import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker.Compatibility.Incompatible.* +import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver.Compatibility.Incompatible +import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver.Compatibility.Incompatible.* import org.jetbrains.kotlin.utils.sure object PositioningStrategies { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/PlatformIncompatibilityDiagnosticRenderer.kt b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/PlatformIncompatibilityDiagnosticRenderer.kt index 65a9a334979..c945527f268 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/PlatformIncompatibilityDiagnosticRenderer.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/PlatformIncompatibilityDiagnosticRenderer.kt @@ -18,7 +18,7 @@ package org.jetbrains.kotlin.diagnostics.rendering import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.MemberDescriptor -import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker.Compatibility.Incompatible +import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver.Compatibility.Incompatible class PlatformIncompatibilityDiagnosticRenderer( private val mode: MultiplatformDiagnosticRenderingMode diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt index be24ecd7336..dee2a26af04 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt @@ -17,39 +17,26 @@ package org.jetbrains.kotlin.resolve.checkers import com.intellij.openapi.vfs.VfsUtilCore -import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.config.AnalysisFlag import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.diagnostics.DiagnosticSink import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.incremental.components.ExpectActualTracker -import org.jetbrains.kotlin.incremental.components.NoLookupLocation -import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtConstructor import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtNamedDeclaration import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.DescriptorUtils -import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker.Compatibility.Compatible -import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker.Compatibility.Incompatible -import org.jetbrains.kotlin.resolve.descriptorUtil.classId +import org.jetbrains.kotlin.resolve.descriptorUtil.isAnnotationConstructor import org.jetbrains.kotlin.resolve.descriptorUtil.module -import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter -import org.jetbrains.kotlin.resolve.scopes.MemberScope -import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered +import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver +import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver.Compatibility +import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver.Compatibility.Compatible +import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver.Compatibility.Incompatible import org.jetbrains.kotlin.resolve.source.PsiSourceFile -import org.jetbrains.kotlin.types.KotlinType -import org.jetbrains.kotlin.types.TypeConstructor -import org.jetbrains.kotlin.types.TypeConstructorSubstitution -import org.jetbrains.kotlin.types.TypeSubstitutor -import org.jetbrains.kotlin.types.checker.NewKotlinTypeChecker -import org.jetbrains.kotlin.types.checker.TypeCheckerContext -import org.jetbrains.kotlin.types.typeUtil.asTypeProjection -import org.jetbrains.kotlin.utils.SmartList import org.jetbrains.kotlin.utils.addToStdlib.safeAs -import org.jetbrains.kotlin.utils.keysToMap import java.io.File object ExpectedActualDeclarationChecker : DeclarationChecker { @@ -77,12 +64,12 @@ object ExpectedActualDeclarationChecker : DeclarationChecker { // Only look for top level actual members; class members will be handled as a part of that expected class if (descriptor.containingDeclaration !is PackageFragmentDescriptor) return - val compatibility = findActualForExpected(descriptor, platformModule) ?: return + val compatibility = ExpectedActualResolver.findActualForExpected(descriptor, platformModule) ?: return val shouldReportError = compatibility.allStrongIncompatibilities() || Compatible !in compatibility && compatibility.values.flatMapTo(hashSetOf()) { it }.all { actual -> - val expectedOnes = findExpectedForActual(actual, descriptor.module) + val expectedOnes = ExpectedActualResolver.findExpectedForActual(actual, descriptor.module) expectedOnes != null && Compatible in expectedOnes.keys } @@ -117,42 +104,16 @@ object ExpectedActualDeclarationChecker : DeclarationChecker { .safeAs() ?.run { VfsUtilCore.virtualToIoFile(psiFile.virtualFile) } - fun Map>.allStrongIncompatibilities(): Boolean = + private fun Map>.allStrongIncompatibilities(): Boolean = this.keys.all { it is Incompatible && it.kind == Compatibility.IncompatibilityKind.STRONG } - private fun findActualForExpected( - expected: MemberDescriptor, - platformModule: ModuleDescriptor - ): Map>? { - return when (expected) { - is CallableMemberDescriptor -> { - expected.findNamesakesFromModule(platformModule).filter { actual -> - expected != actual && !actual.isExpect && - // TODO: support non-source definitions (e.g. from Java) - actual.source.containingFile != SourceFile.NO_SOURCE_FILE - }.groupBy { actual -> - areCompatibleCallables(expected, actual) - } - } - is ClassDescriptor -> { - expected.findClassifiersFromModule(platformModule).filter { actual -> - expected != actual && !actual.isExpect && - actual.source.containingFile != SourceFile.NO_SOURCE_FILE - }.groupBy { actual -> - areCompatibleClassifiers(expected, actual) - } - } - else -> null - } - } - private fun checkActualDeclarationHasExpected( reportOn: KtNamedDeclaration, descriptor: MemberDescriptor, diagnosticHolder: DiagnosticSink, checkActual: Boolean ) { // Using the platform module instead of the common module is sort of fine here because the former always depends on the latter. // However, it would be clearer to find the common module this platform module implements and look for expected there instead. // TODO: use common module here - val compatibility = findExpectedForActual(descriptor, descriptor.module) ?: return + val compatibility = ExpectedActualResolver.findExpectedForActual(descriptor, descriptor.module) ?: return val hasActualModifier = descriptor.isActual && reportOn.hasActualModifier() if (!hasActualModifier) { @@ -189,7 +150,10 @@ object ExpectedActualDeclarationChecker : DeclarationChecker { return actualMember != null && actualMember.isExplicitActualDeclaration() && !incompatibility.allStrongIncompatibilities() && - findExpectedForActual(actualMember, expectedMember.module)?.values?.singleOrNull()?.singleOrNull() == expectedMember + ExpectedActualResolver.findExpectedForActual( + actualMember, + expectedMember.module + )?.values?.singleOrNull()?.singleOrNull() == expectedMember } val nonTrivialUnfulfilled = singleIncompatibility.unfulfilled.filterNot(::hasSingleActualSuspect) @@ -220,478 +184,4 @@ object ExpectedActualDeclarationChecker : DeclarationChecker { is CallableMemberDescriptor -> kind == CallableMemberDescriptor.Kind.DECLARATION else -> true } - - private fun findExpectedForActual( - actual: MemberDescriptor, - commonModule: ModuleDescriptor - ): Map>? { - return when (actual) { - is CallableMemberDescriptor -> { - val container = actual.containingDeclaration - val candidates = when (container) { - is ClassDescriptor -> { - // TODO: replace with 'singleOrNull' as soon as multi-module diagnostic tests are refactored - val expectedClass = - findExpectedForActual(container, commonModule)?.values?.firstOrNull()?.firstOrNull() as? ClassDescriptor - expectedClass?.getMembers(actual.name)?.filterIsInstance().orEmpty() - } - is PackageFragmentDescriptor -> actual.findNamesakesFromModule(commonModule) - else -> return null // do not report anything for incorrect code, e.g. 'actual' local function - } - - candidates.filter { declaration -> - actual != declaration && declaration.isExpect - }.groupBy { declaration -> - // TODO: optimize by caching this per actual-expected class pair, do not create a new substitutor for each actual member - val substitutor = - if (container is ClassDescriptor) { - val expectedClass = declaration.containingDeclaration as ClassDescriptor - // TODO: this might not work for members of inner generic classes - Substitutor(expectedClass.declaredTypeParameters, container.declaredTypeParameters) - } else null - areCompatibleCallables(declaration, actual, parentSubstitutor = substitutor) - } - } - is ClassifierDescriptorWithTypeParameters -> { - actual.findClassifiersFromModule(commonModule).filter { declaration -> - actual != declaration && - declaration is ClassDescriptor && declaration.isExpect - }.groupBy { expected -> - areCompatibleClassifiers(expected as ClassDescriptor, actual) - } - } - else -> null - } - } - - fun MemberDescriptor.findCompatibleActualForExpected(platformModule: ModuleDescriptor): List = - findActualForExpected(this, platformModule)?.get(Compatible).orEmpty() - - fun MemberDescriptor.findAnyActualForExpected(platformModule: ModuleDescriptor): List { - val actualsGroupedByCompatibility = findActualForExpected(this, platformModule) - return actualsGroupedByCompatibility?.get(Compatible) - ?: actualsGroupedByCompatibility?.values?.flatten() - ?: emptyList() - } - - fun MemberDescriptor.findCompatibleExpectedForActual(commonModule: ModuleDescriptor): List = - findExpectedForActual(this, commonModule)?.get(Compatible).orEmpty() - - private fun CallableMemberDescriptor.findNamesakesFromModule(module: ModuleDescriptor): Collection { - val containingDeclaration = containingDeclaration - val scopes = when (containingDeclaration) { - is PackageFragmentDescriptor -> { - listOf(module.getPackage(containingDeclaration.fqName).memberScope) - } - is ClassDescriptor -> { - val classes = containingDeclaration.findClassifiersFromModule(module).filterIsInstance() - if (this is ConstructorDescriptor) return classes.flatMap { it.constructors } - - classes.map { it.unsubstitutedMemberScope } - } - else -> return emptyList() - } - - return when (this) { - is FunctionDescriptor -> scopes.flatMap { it.getContributedFunctions(name, NoLookupLocation.FOR_ALREADY_TRACKED) } - is PropertyDescriptor -> scopes.flatMap { it.getContributedVariables(name, NoLookupLocation.FOR_ALREADY_TRACKED) } - else -> throw AssertionError("Unsupported declaration: $this") - } - } - - private fun ClassifierDescriptorWithTypeParameters.findClassifiersFromModule( - module: ModuleDescriptor - ): Collection { - val classId = classId ?: return emptyList() - - fun MemberScope.getAllClassifiers(name: Name): Collection = - getDescriptorsFiltered(DescriptorKindFilter.CLASSIFIERS) { it == name } - .filterIsInstance() - - val segments = classId.relativeClassName.pathSegments() - var classifiers = module.getPackage(classId.packageFqName).memberScope.getAllClassifiers(segments.first()) - - for (name in segments.subList(1, segments.size)) { - classifiers = classifiers.mapNotNull { classifier -> - (classifier as? ClassDescriptor)?.unsubstitutedInnerClassesScope?.getContributedClassifier( - name, NoLookupLocation.FOR_ALREADY_TRACKED - ) as? ClassifierDescriptorWithTypeParameters - } - } - - return classifiers - } - - sealed class Compatibility { - // For IncompatibilityKind.STRONG `actual` declaration is considered as overload and error reports on expected declaration - enum class IncompatibilityKind { - WEAK, STRONG - } - - // Note that the reason is used in the diagnostic output, see PlatformIncompatibilityDiagnosticRenderer - sealed class Incompatible(val reason: String?, val kind: IncompatibilityKind = IncompatibilityKind.WEAK) : Compatibility() { - // Callables - - object ParameterShape : Incompatible("parameter shapes are different (extension vs non-extension)", IncompatibilityKind.STRONG) - - object ParameterCount : Incompatible("number of value parameters is different", IncompatibilityKind.STRONG) - object TypeParameterCount : Incompatible("number of type parameters is different", IncompatibilityKind.STRONG) - - object ParameterTypes : Incompatible("parameter types are different", IncompatibilityKind.STRONG) - object ReturnType : Incompatible("return type is different", IncompatibilityKind.STRONG) - - object ParameterNames : Incompatible("parameter names are different") - object TypeParameterNames : Incompatible("names of type parameters are different") - - object ValueParameterHasDefault : Incompatible("some parameters have default values") - object ValueParameterVararg : Incompatible("some value parameter is vararg in one declaration and non-vararg in the other") - object ValueParameterNoinline : - Incompatible("some value parameter is noinline in one declaration and not noinline in the other") - - object ValueParameterCrossinline : - Incompatible("some value parameter is crossinline in one declaration and not crossinline in the other") - - // Functions - - object FunctionModifiersDifferent : Incompatible("modifiers are different (suspend)") - object FunctionModifiersNotSubset : - Incompatible("some modifiers on expected declaration are missing on the actual one (external, infix, inline, operator, tailrec)") - - // Properties - - object PropertyKind : Incompatible("property kinds are different (val vs var)") - object PropertyModifiers : Incompatible("modifiers are different (const, lateinit)") - - // Classifiers - - object ClassKind : Incompatible("class kinds are different (class, interface, object, enum, annotation)") - - object ClassModifiers : Incompatible("modifiers are different (companion, inner)") - - object Supertypes : Incompatible("some supertypes are missing in the actual declaration") - - class ClassScopes( - val unfulfilled: List>>> - ) : Incompatible("some expected members have no actual ones") - - object EnumEntries : Incompatible("some entries from expected enum are missing in the actual enum") - - // Common - - object Modality : Incompatible("modality is different") - object Visibility : Incompatible("visibility is different") - - object TypeParameterUpperBounds : Incompatible("upper bounds of type parameters are different", IncompatibilityKind.STRONG) - object TypeParameterVariance : Incompatible("declaration-site variances of type parameters are different") - object TypeParameterReified : Incompatible("some type parameter is reified in one declaration and non-reified in the other") - - object Unknown : Incompatible(null) - } - - object Compatible : Compatibility() - } - - // a is the declaration in common code, b is the definition in the platform-specific code - private fun areCompatibleCallables( - a: CallableMemberDescriptor, - b: CallableMemberDescriptor, - platformModule: ModuleDescriptor = b.module, - parentSubstitutor: Substitutor? = null - ): Compatibility { - assert(a.name == b.name) { "This function should be invoked only for declarations with the same name: $a, $b" } - assert(a.containingDeclaration is ClassDescriptor == b.containingDeclaration is ClassDescriptor) { - "This function should be invoked only for declarations in the same kind of container (both members or both top level): $a, $b" - } - - val aExtensionReceiver = a.extensionReceiverParameter - val bExtensionReceiver = b.extensionReceiverParameter - if ((aExtensionReceiver != null) != (bExtensionReceiver != null)) return Incompatible.ParameterShape - - val aParams = a.valueParameters - val bParams = b.valueParameters - if (!valueParametersCountCompatible(a, b, aParams, bParams)) { - return Incompatible.ParameterCount - } - - val aTypeParams = a.typeParameters - val bTypeParams = b.typeParameters - if (aTypeParams.size != bTypeParams.size) return Incompatible.TypeParameterCount - - val substitutor = Substitutor(aTypeParams, bTypeParams, parentSubstitutor) - - if (!areCompatibleTypeLists(aParams.map { substitutor(it.type) }, bParams.map { it.type }, platformModule) || - !areCompatibleTypes(aExtensionReceiver?.type?.let(substitutor), bExtensionReceiver?.type, platformModule)) - return Incompatible.ParameterTypes - if (!areCompatibleTypes(substitutor(a.returnType), b.returnType, platformModule)) return Incompatible.ReturnType - - if (b.hasStableParameterNames() && !equalsBy( - aParams, - bParams, - ValueParameterDescriptor::getName - )) return Incompatible.ParameterNames - if (!equalsBy(aTypeParams, bTypeParams, TypeParameterDescriptor::getName)) return Incompatible.TypeParameterNames - - if (!areCompatibleModalities(a.modality, b.modality)) return Incompatible.Modality - if (a.visibility != b.visibility) return Incompatible.Visibility - - areCompatibleTypeParameters(aTypeParams, bTypeParams, platformModule, substitutor).let { if (it != Compatible) return it } - - if (!equalsBy(aParams, bParams, ValueParameterDescriptor::declaresDefaultValue)) return Incompatible.ValueParameterHasDefault - if (!equalsBy(aParams, bParams, { p -> listOf(p.varargElementType != null) })) return Incompatible.ValueParameterVararg - - // Adding noinline/crossinline to parameters is disallowed, except if the expected declaration was not inline at all - if (a is FunctionDescriptor && a.isInline) { - if (aParams.indices.any { i -> !aParams[i].isNoinline && bParams[i].isNoinline }) return Incompatible.ValueParameterNoinline - if (aParams.indices.any { i -> !aParams[i].isCrossinline && bParams[i].isCrossinline }) return Incompatible.ValueParameterCrossinline - } - - when { - a is FunctionDescriptor && b is FunctionDescriptor -> areCompatibleFunctions(a, b).let { if (it != Compatible) return it } - a is PropertyDescriptor && b is PropertyDescriptor -> areCompatibleProperties(a, b).let { if (it != Compatible) return it } - else -> throw AssertionError("Unsupported declarations: $a, $b") - } - - return Compatible - } - - private fun valueParametersCountCompatible( - a: CallableMemberDescriptor, - b: CallableMemberDescriptor, - aParams: List, - bParams: List - ): Boolean { - if (aParams.size == bParams.size) return true - - return if (a.isAnnotationConstructor() && b.isAnnotationConstructor()) - aParams.isEmpty() && bParams.all { it.declaresDefaultValue() } - else - false - } - - private fun MemberDescriptor.isAnnotationConstructor(): Boolean = - this is ConstructorDescriptor && DescriptorUtils.isAnnotationClass(this.constructedClass) - - private fun areCompatibleTypes(a: KotlinType?, b: KotlinType?, platformModule: ModuleDescriptor): Boolean { - if (a == null) return b == null - if (b == null) return false - - with(NewKotlinTypeChecker) { - val context = object : TypeCheckerContext(false) { - override fun areEqualTypeConstructors(a: TypeConstructor, b: TypeConstructor): Boolean { - return isExpectedClassAndActualTypeAlias(a, b, platformModule) || - isExpectedClassAndActualTypeAlias(b, a, platformModule) || - super.areEqualTypeConstructors(a, b) - } - } - return context.equalTypes(a.unwrap(), b.unwrap()) - } - } - - // For example, expectedTypeConstructor may be the expected class kotlin.text.StringBuilder, while actualTypeConstructor - // is java.lang.StringBuilder. For the purposes of type compatibility checking, we must consider these types equal here. - // Note that the case of an "actual class" works as expected though, because the actual class by definition has the same FQ name - // as the corresponding expected class, so their type constructors are equal as per AbstractClassTypeConstructor#equals - private fun isExpectedClassAndActualTypeAlias( - expectedTypeConstructor: TypeConstructor, - actualTypeConstructor: TypeConstructor, - platformModule: ModuleDescriptor - ): Boolean { - val expected = expectedTypeConstructor.declarationDescriptor - val actual = actualTypeConstructor.declarationDescriptor - return expected is ClassifierDescriptorWithTypeParameters && - expected.isExpect && - actual is ClassifierDescriptorWithTypeParameters && - expected.findClassifiersFromModule(platformModule).any { classifier -> - // Note that it's fine to only check that this "actual typealias" expands to the expected class, without checking - // whether the type arguments in the expansion are in the correct order or have the correct variance, because we only - // allow simple cases like "actual typealias Foo = FooImpl", see DeclarationsChecker#checkActualTypeAlias - (classifier as? TypeAliasDescriptor)?.classDescriptor == actual - } - } - - private fun areCompatibleTypeLists(a: List, b: List, platformModule: ModuleDescriptor): Boolean { - for (i in a.indices) { - if (!areCompatibleTypes(a[i], b[i], platformModule)) return false - } - return true - } - - private fun areCompatibleTypeParameters( - a: List, - b: List, - platformModule: ModuleDescriptor, - substitutor: Substitutor - ): Compatibility { - for (i in a.indices) { - val aBounds = a[i].upperBounds - val bBounds = b[i].upperBounds - if (aBounds.size != bBounds.size || !areCompatibleTypeLists(aBounds.map(substitutor), bBounds, platformModule)) { - return Incompatible.TypeParameterUpperBounds - } - } - - if (!equalsBy(a, b, TypeParameterDescriptor::getVariance)) return Incompatible.TypeParameterVariance - - // Removing "reified" from an expected function's type parameter is fine - if (a.indices.any { i -> !a[i].isReified && b[i].isReified }) return Incompatible.TypeParameterReified - - return Compatible - } - - private fun areCompatibleFunctions(a: FunctionDescriptor, b: FunctionDescriptor): Compatibility { - if (!equalBy(a, b) { f -> f.isSuspend }) return Incompatible.FunctionModifiersDifferent - - if (a.isExternal && !b.isExternal || - a.isInfix && !b.isInfix || - a.isInline && !b.isInline || - a.isOperator && !b.isOperator || - a.isTailrec && !b.isTailrec) return Incompatible.FunctionModifiersNotSubset - - return Compatible - } - - private fun areCompatibleProperties(a: PropertyDescriptor, b: PropertyDescriptor): Compatibility { - if (!equalBy(a, b) { p -> p.isVar }) return Incompatible.PropertyKind - if (!equalBy(a, b) { p -> listOf(p.isConst, p.isLateInit) }) return Incompatible.PropertyModifiers - - return Compatible - } - - private fun areCompatibleClassifiers(a: ClassDescriptor, other: ClassifierDescriptor): Compatibility { - // Can't check FQ names here because nested expected class may be implemented via actual typealias's expansion with the other FQ name - assert(a.name == other.name) { "This function should be invoked only for declarations with the same name: $a, $other" } - - val b = when (other) { - is ClassDescriptor -> other - is TypeAliasDescriptor -> other.classDescriptor ?: return Compatible // do not report extra error on erroneous typealias - else -> throw AssertionError("Incorrect actual classifier for $a: $other") - } - - if (a.kind != b.kind) return Incompatible.ClassKind - - if (!equalBy(a, b) { listOf(it.isCompanionObject, it.isInner) }) return Incompatible.ClassModifiers - - val aTypeParams = a.declaredTypeParameters - val bTypeParams = b.declaredTypeParameters - if (aTypeParams.size != bTypeParams.size) return Incompatible.TypeParameterCount - - if (!areCompatibleModalities(a.modality, b.modality)) return Incompatible.Modality - - if (a.visibility != b.visibility) return Incompatible.Visibility - - val platformModule = other.module - val substitutor = Substitutor(aTypeParams, bTypeParams) - areCompatibleTypeParameters(aTypeParams, bTypeParams, platformModule, substitutor).let { if (it != Compatible) return it } - - // 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 aSupertypes = a.typeConstructor.supertypes.filterNot(KotlinBuiltIns::isAny) - val bSupertypes = b.typeConstructor.supertypes.filterNot(KotlinBuiltIns::isAny) - if (aSupertypes.map(substitutor).any { aSupertype -> - bSupertypes.none { bSupertype -> areCompatibleTypes(aSupertype, bSupertype, platformModule) } - }) return Incompatible.Supertypes - - areCompatibleClassScopes(a, b, platformModule, substitutor).let { if (it != Compatible) return it } - - return Compatible - } - - private fun areCompatibleModalities(a: Modality, b: Modality): Boolean { - return a == Modality.FINAL && b == Modality.OPEN || - a == b - } - - - private fun areCompatibleClassScopes( - a: ClassDescriptor, - b: ClassDescriptor, - platformModule: ModuleDescriptor, - substitutor: Substitutor - ): Compatibility { - val unfulfilled = arrayListOf>>>() - - val bMembersByName = b.getMembers().groupBy { it.name } - - outer@ for (aMember in a.getMembers()) { - if (aMember is CallableMemberDescriptor && !aMember.kind.isReal) continue - - val bMembers = bMembersByName[aMember.name]?.filter { bMember -> - aMember is CallableMemberDescriptor && bMember is CallableMemberDescriptor || - aMember is ClassDescriptor && bMember is ClassDescriptor - }.orEmpty() - - val mapping = bMembers.keysToMap { bMember -> - when (aMember) { - is CallableMemberDescriptor -> - areCompatibleCallables(aMember, bMember as CallableMemberDescriptor, platformModule, substitutor) - is ClassDescriptor -> - areCompatibleClassifiers(aMember, bMember as ClassDescriptor) - else -> throw UnsupportedOperationException("Unsupported declaration: $aMember ($bMembers)") - } - } - if (mapping.values.any { it == Compatible }) continue - - val incompatibilityMap = mutableMapOf>() - for ((descriptor, compatibility) in mapping) { - when (compatibility) { - Compatible -> continue@outer - is Incompatible -> incompatibilityMap.getOrPut(compatibility) { SmartList() }.add(descriptor) - } - } - - unfulfilled.add(aMember to incompatibilityMap) - } - - if (a.kind == ClassKind.ENUM_CLASS) { - fun ClassDescriptor.enumEntries() = - unsubstitutedMemberScope.getDescriptorsFiltered().filter(DescriptorUtils::isEnumEntry).map { it.name } - - val aEntries = a.enumEntries() - val bEntries = b.enumEntries() - - if (!bEntries.containsAll(aEntries)) return Incompatible.EnumEntries - } - - // TODO: check static scope? - - if (unfulfilled.isEmpty()) return Compatible - - return Incompatible.ClassScopes(unfulfilled) - } - - private fun ClassDescriptor.getMembers(name: Name? = null): Collection { - val nameFilter = if (name != null) { it -> it == name } else MemberScope.ALL_NAME_FILTER - return defaultType.memberScope - .getDescriptorsFiltered(nameFilter = nameFilter) - .filterIsInstance() - .filterNot(DescriptorUtils::isEnumEntry) - .plus(constructors.filter { nameFilter(it.name) }) - } - - private inline fun equalBy(first: T, second: T, selector: (T) -> K): Boolean = - selector(first) == selector(second) - - 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 - } - - // This substitutor takes the type from A's signature and returns the type that should be in that place in B's signature - private class Substitutor( - aTypeParams: List, - bTypeParams: List, - private val parent: Substitutor? = null - ) : (KotlinType?) -> KotlinType? { - private val typeSubstitutor = TypeSubstitutor.create( - TypeConstructorSubstitution.createByParametersMap(aTypeParams.keysToMap { - bTypeParams[it.index].defaultType.asTypeProjection() - }) - ) - - override fun invoke(type: KotlinType?): KotlinType? = - (parent?.invoke(type) ?: type)?.asTypeProjection()?.let(typeSubstitutor::substitute)?.type - } } diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectedActualResolver.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectedActualResolver.kt new file mode 100644 index 00000000000..7a6cd1f2882 --- /dev/null +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectedActualResolver.kt @@ -0,0 +1,515 @@ +/* + * Copyright 2000-2018 JetBrains s.r.o. 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 org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.descriptorUtil.classId +import org.jetbrains.kotlin.resolve.descriptorUtil.isAnnotationConstructor +import org.jetbrains.kotlin.resolve.descriptorUtil.module +import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver.Compatibility.Compatible +import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver.Compatibility.Incompatible +import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter +import org.jetbrains.kotlin.resolve.scopes.MemberScope +import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.TypeConstructor +import org.jetbrains.kotlin.types.TypeConstructorSubstitution +import org.jetbrains.kotlin.types.TypeSubstitutor +import org.jetbrains.kotlin.types.checker.NewKotlinTypeChecker +import org.jetbrains.kotlin.types.checker.TypeCheckerContext +import org.jetbrains.kotlin.types.typeUtil.asTypeProjection +import org.jetbrains.kotlin.utils.SmartList +import org.jetbrains.kotlin.utils.keysToMap + +object ExpectedActualResolver { + fun MemberDescriptor.findCompatibleActualForExpected(platformModule: ModuleDescriptor): List = + findActualForExpected(this, platformModule)?.get(Compatible).orEmpty() + + fun MemberDescriptor.findAnyActualForExpected(platformModule: ModuleDescriptor): List { + val actualsGroupedByCompatibility = findActualForExpected(this, platformModule) + return actualsGroupedByCompatibility?.get(Compatible) + ?: actualsGroupedByCompatibility?.values?.flatten() + ?: emptyList() + } + + fun MemberDescriptor.findCompatibleExpectedForActual(commonModule: ModuleDescriptor): List = + findExpectedForActual(this, commonModule)?.get(Compatible).orEmpty() + + fun findActualForExpected(expected: MemberDescriptor, platformModule: ModuleDescriptor): Map>? { + return when (expected) { + is CallableMemberDescriptor -> { + expected.findNamesakesFromModule(platformModule).filter { actual -> + expected != actual && !actual.isExpect && + // TODO: support non-source definitions (e.g. from Java) + actual.source.containingFile != SourceFile.NO_SOURCE_FILE + }.groupBy { actual -> + areCompatibleCallables(expected, actual) + } + } + is ClassDescriptor -> { + expected.findClassifiersFromModule(platformModule).filter { actual -> + expected != actual && !actual.isExpect && + actual.source.containingFile != SourceFile.NO_SOURCE_FILE + }.groupBy { actual -> + areCompatibleClassifiers(expected, actual) + } + } + else -> null + } + } + + fun findExpectedForActual(actual: MemberDescriptor, commonModule: ModuleDescriptor): Map>? { + return when (actual) { + is CallableMemberDescriptor -> { + val container = actual.containingDeclaration + val candidates = when (container) { + is ClassDescriptor -> { + // TODO: replace with 'singleOrNull' as soon as multi-module diagnostic tests are refactored + val expectedClass = + findExpectedForActual(container, commonModule)?.values?.firstOrNull()?.firstOrNull() as? ClassDescriptor + expectedClass?.getMembers(actual.name)?.filterIsInstance().orEmpty() + } + is PackageFragmentDescriptor -> actual.findNamesakesFromModule(commonModule) + else -> return null // do not report anything for incorrect code, e.g. 'actual' local function + } + + candidates.filter { declaration -> + actual != declaration && declaration.isExpect + }.groupBy { declaration -> + // TODO: optimize by caching this per actual-expected class pair, do not create a new substitutor for each actual member + val substitutor = + if (container is ClassDescriptor) { + val expectedClass = declaration.containingDeclaration as ClassDescriptor + // TODO: this might not work for members of inner generic classes + Substitutor(expectedClass.declaredTypeParameters, container.declaredTypeParameters) + } + else null + areCompatibleCallables(declaration, actual, parentSubstitutor = substitutor) + } + } + is ClassifierDescriptorWithTypeParameters -> { + actual.findClassifiersFromModule(commonModule).filter { declaration -> + actual != declaration && + declaration is ClassDescriptor && declaration.isExpect + }.groupBy { expected -> + areCompatibleClassifiers(expected as ClassDescriptor, actual) + } + } + else -> null + } + } + + private fun CallableMemberDescriptor.findNamesakesFromModule(module: ModuleDescriptor): Collection { + val containingDeclaration = containingDeclaration + val scopes = when (containingDeclaration) { + is PackageFragmentDescriptor -> { + listOf(module.getPackage(containingDeclaration.fqName).memberScope) + } + is ClassDescriptor -> { + val classes = containingDeclaration.findClassifiersFromModule(module).filterIsInstance() + if (this is ConstructorDescriptor) return classes.flatMap { it.constructors } + + classes.map { it.unsubstitutedMemberScope } + } + else -> return emptyList() + } + + return when (this) { + is FunctionDescriptor -> scopes.flatMap { it.getContributedFunctions(name, NoLookupLocation.FOR_ALREADY_TRACKED) } + is PropertyDescriptor -> scopes.flatMap { it.getContributedVariables(name, NoLookupLocation.FOR_ALREADY_TRACKED) } + else -> throw AssertionError("Unsupported declaration: $this") + } + } + + private fun ClassifierDescriptorWithTypeParameters.findClassifiersFromModule( + module: ModuleDescriptor + ): Collection { + val classId = classId ?: return emptyList() + + fun MemberScope.getAllClassifiers(name: Name): Collection = + getDescriptorsFiltered(DescriptorKindFilter.CLASSIFIERS) { it == name } + .filterIsInstance() + + val segments = classId.relativeClassName.pathSegments() + var classifiers = module.getPackage(classId.packageFqName).memberScope.getAllClassifiers(segments.first()) + + for (name in segments.subList(1, segments.size)) { + classifiers = classifiers.mapNotNull { classifier -> + (classifier as? ClassDescriptor)?.unsubstitutedInnerClassesScope?.getContributedClassifier( + name, NoLookupLocation.FOR_ALREADY_TRACKED + ) as? ClassifierDescriptorWithTypeParameters + } + } + + return classifiers + } + + sealed class Compatibility { + // For IncompatibilityKind.STRONG `actual` declaration is considered as overload and error reports on expected declaration + enum class IncompatibilityKind { + WEAK, STRONG + } + + // Note that the reason is used in the diagnostic output, see PlatformIncompatibilityDiagnosticRenderer + sealed class Incompatible(val reason: String?, val kind: IncompatibilityKind = IncompatibilityKind.WEAK) : Compatibility() { + // Callables + + object ParameterShape : Incompatible("parameter shapes are different (extension vs non-extension)", IncompatibilityKind.STRONG) + + object ParameterCount : Incompatible("number of value parameters is different", IncompatibilityKind.STRONG) + object TypeParameterCount : Incompatible("number of type parameters is different", IncompatibilityKind.STRONG) + + object ParameterTypes : Incompatible("parameter types are different", IncompatibilityKind.STRONG) + object ReturnType : Incompatible("return type is different", IncompatibilityKind.STRONG) + + object ParameterNames : Incompatible("parameter names are different") + object TypeParameterNames : Incompatible("names of type parameters are different") + + object ValueParameterHasDefault : Incompatible("some parameters have default values") + object ValueParameterVararg : Incompatible("some value parameter is vararg in one declaration and non-vararg in the other") + object ValueParameterNoinline : Incompatible("some value parameter is noinline in one declaration and not noinline in the other") + object ValueParameterCrossinline : Incompatible("some value parameter is crossinline in one declaration and not crossinline in the other") + + // Functions + + object FunctionModifiersDifferent : Incompatible("modifiers are different (suspend)") + object FunctionModifiersNotSubset : Incompatible("some modifiers on expected declaration are missing on the actual one (external, infix, inline, operator, tailrec)") + + // Properties + + object PropertyKind : Incompatible("property kinds are different (val vs var)") + object PropertyModifiers : Incompatible("modifiers are different (const, lateinit)") + + // Classifiers + + object ClassKind : Incompatible("class kinds are different (class, interface, object, enum, annotation)") + + object ClassModifiers : Incompatible("modifiers are different (companion, inner)") + + object Supertypes : Incompatible("some supertypes are missing in the actual declaration") + + class ClassScopes( + val unfulfilled: List>>> + ) : Incompatible("some expected members have no actual ones") + + object EnumEntries : Incompatible("some entries from expected enum are missing in the actual enum") + + // Common + + object Modality : Incompatible("modality is different") + object Visibility : Incompatible("visibility is different") + + object TypeParameterUpperBounds : Incompatible("upper bounds of type parameters are different", IncompatibilityKind.STRONG) + object TypeParameterVariance : Incompatible("declaration-site variances of type parameters are different") + object TypeParameterReified : Incompatible("some type parameter is reified in one declaration and non-reified in the other") + + object Unknown : Incompatible(null) + } + + object Compatible : Compatibility() + } + + // a is the declaration in common code, b is the definition in the platform-specific code + private fun areCompatibleCallables( + a: CallableMemberDescriptor, + b: CallableMemberDescriptor, + platformModule: ModuleDescriptor = b.module, + parentSubstitutor: Substitutor? = null + ): Compatibility { + assert(a.name == b.name) { "This function should be invoked only for declarations with the same name: $a, $b" } + assert(a.containingDeclaration is ClassDescriptor == b.containingDeclaration is ClassDescriptor) { + "This function should be invoked only for declarations in the same kind of container (both members or both top level): $a, $b" + } + + val aExtensionReceiver = a.extensionReceiverParameter + val bExtensionReceiver = b.extensionReceiverParameter + if ((aExtensionReceiver != null) != (bExtensionReceiver != null)) return Incompatible.ParameterShape + + val aParams = a.valueParameters + val bParams = b.valueParameters + if (!valueParametersCountCompatible(a, b, aParams, bParams)) { + return Incompatible.ParameterCount + } + + val aTypeParams = a.typeParameters + val bTypeParams = b.typeParameters + if (aTypeParams.size != bTypeParams.size) return Incompatible.TypeParameterCount + + val substitutor = Substitutor(aTypeParams, bTypeParams, parentSubstitutor) + + if (!areCompatibleTypeLists(aParams.map { substitutor(it.type) }, bParams.map { it.type }, platformModule) || + !areCompatibleTypes(aExtensionReceiver?.type?.let(substitutor), bExtensionReceiver?.type, platformModule)) + return Incompatible.ParameterTypes + if (!areCompatibleTypes(substitutor(a.returnType), b.returnType, platformModule)) return Incompatible.ReturnType + + if (b.hasStableParameterNames() && !equalsBy(aParams, bParams, ValueParameterDescriptor::getName)) return Incompatible.ParameterNames + if (!equalsBy(aTypeParams, bTypeParams, TypeParameterDescriptor::getName)) return Incompatible.TypeParameterNames + + if (!areCompatibleModalities(a.modality, b.modality)) return Incompatible.Modality + if (a.visibility != b.visibility) return Incompatible.Visibility + + areCompatibleTypeParameters(aTypeParams, bTypeParams, platformModule, substitutor).let { if (it != Compatible) return it } + + if (!equalsBy(aParams, bParams, ValueParameterDescriptor::declaresDefaultValue)) return Incompatible.ValueParameterHasDefault + if (!equalsBy(aParams, bParams, { p -> listOf(p.varargElementType != null) })) return Incompatible.ValueParameterVararg + + // Adding noinline/crossinline to parameters is disallowed, except if the expected declaration was not inline at all + if (a is FunctionDescriptor && a.isInline) { + if (aParams.indices.any { i -> !aParams[i].isNoinline && bParams[i].isNoinline }) return Incompatible.ValueParameterNoinline + if (aParams.indices.any { i -> !aParams[i].isCrossinline && bParams[i].isCrossinline }) return Incompatible.ValueParameterCrossinline + } + + when { + a is FunctionDescriptor && b is FunctionDescriptor -> areCompatibleFunctions(a, b).let { if (it != Compatible) return it } + a is PropertyDescriptor && b is PropertyDescriptor -> areCompatibleProperties(a, b).let { if (it != Compatible) return it } + else -> throw AssertionError("Unsupported declarations: $a, $b") + } + + return Compatible + } + + private fun valueParametersCountCompatible( + a: CallableMemberDescriptor, + b: CallableMemberDescriptor, + aParams: List, + bParams: List + ): Boolean { + if (aParams.size == bParams.size) return true + + return if (a.isAnnotationConstructor() && b.isAnnotationConstructor()) + aParams.isEmpty() && bParams.all { it.declaresDefaultValue() } + else + false + } + + private fun areCompatibleTypes(a: KotlinType?, b: KotlinType?, platformModule: ModuleDescriptor): Boolean { + if (a == null) return b == null + if (b == null) return false + + with(NewKotlinTypeChecker) { + val context = object : TypeCheckerContext(false) { + override fun areEqualTypeConstructors(a: TypeConstructor, b: TypeConstructor): Boolean { + return isExpectedClassAndActualTypeAlias(a, b, platformModule) || + isExpectedClassAndActualTypeAlias(b, a, platformModule) || + super.areEqualTypeConstructors(a, b) + } + } + return context.equalTypes(a.unwrap(), b.unwrap()) + } + } + + // For example, expectedTypeConstructor may be the expected class kotlin.text.StringBuilder, while actualTypeConstructor + // is java.lang.StringBuilder. For the purposes of type compatibility checking, we must consider these types equal here. + // Note that the case of an "actual class" works as expected though, because the actual class by definition has the same FQ name + // as the corresponding expected class, so their type constructors are equal as per AbstractClassTypeConstructor#equals + private fun isExpectedClassAndActualTypeAlias( + expectedTypeConstructor: TypeConstructor, + actualTypeConstructor: TypeConstructor, + platformModule: ModuleDescriptor + ): Boolean { + val expected = expectedTypeConstructor.declarationDescriptor + val actual = actualTypeConstructor.declarationDescriptor + return expected is ClassifierDescriptorWithTypeParameters && + expected.isExpect && + actual is ClassifierDescriptorWithTypeParameters && + expected.findClassifiersFromModule(platformModule).any { classifier -> + // Note that it's fine to only check that this "actual typealias" expands to the expected class, without checking + // whether the type arguments in the expansion are in the correct order or have the correct variance, because we only + // allow simple cases like "actual typealias Foo = FooImpl", see DeclarationsChecker#checkActualTypeAlias + (classifier as? TypeAliasDescriptor)?.classDescriptor == actual + } + } + + private fun areCompatibleTypeLists(a: List, b: List, platformModule: ModuleDescriptor): Boolean { + for (i in a.indices) { + if (!areCompatibleTypes(a[i], b[i], platformModule)) return false + } + return true + } + + private fun areCompatibleTypeParameters( + a: List, + b: List, + platformModule: ModuleDescriptor, + substitutor: Substitutor + ): Compatibility { + for (i in a.indices) { + val aBounds = a[i].upperBounds + val bBounds = b[i].upperBounds + if (aBounds.size != bBounds.size || !areCompatibleTypeLists(aBounds.map(substitutor), bBounds, platformModule)) { + return Incompatible.TypeParameterUpperBounds + } + } + + if (!equalsBy(a, b, TypeParameterDescriptor::getVariance)) return Incompatible.TypeParameterVariance + + // Removing "reified" from an expected function's type parameter is fine + if (a.indices.any { i -> !a[i].isReified && b[i].isReified }) return Incompatible.TypeParameterReified + + return Compatible + } + + private fun areCompatibleFunctions(a: FunctionDescriptor, b: FunctionDescriptor): Compatibility { + if (!equalBy(a, b) { f -> f.isSuspend }) return Incompatible.FunctionModifiersDifferent + + if (a.isExternal && !b.isExternal || + a.isInfix && !b.isInfix || + a.isInline && !b.isInline || + a.isOperator && !b.isOperator || + a.isTailrec && !b.isTailrec) return Incompatible.FunctionModifiersNotSubset + + return Compatible + } + + private fun areCompatibleProperties(a: PropertyDescriptor, b: PropertyDescriptor): Compatibility { + if (!equalBy(a, b) { p -> p.isVar }) return Incompatible.PropertyKind + if (!equalBy(a, b) { p -> listOf(p.isConst, p.isLateInit) }) return Incompatible.PropertyModifiers + + return Compatible + } + + private fun areCompatibleClassifiers(a: ClassDescriptor, other: ClassifierDescriptor): Compatibility { + // Can't check FQ names here because nested expected class may be implemented via actual typealias's expansion with the other FQ name + assert(a.name == other.name) { "This function should be invoked only for declarations with the same name: $a, $other" } + + val b = when (other) { + is ClassDescriptor -> other + is TypeAliasDescriptor -> other.classDescriptor ?: return Compatible // do not report extra error on erroneous typealias + else -> throw AssertionError("Incorrect actual classifier for $a: $other") + } + + if (a.kind != b.kind) return Incompatible.ClassKind + + if (!equalBy(a, b) { listOf(it.isCompanionObject, it.isInner) }) return Incompatible.ClassModifiers + + val aTypeParams = a.declaredTypeParameters + val bTypeParams = b.declaredTypeParameters + if (aTypeParams.size != bTypeParams.size) return Incompatible.TypeParameterCount + + if (!areCompatibleModalities(a.modality, b.modality)) return Incompatible.Modality + + if (a.visibility != b.visibility) return Incompatible.Visibility + + val platformModule = other.module + val substitutor = Substitutor(aTypeParams, bTypeParams) + areCompatibleTypeParameters(aTypeParams, bTypeParams, platformModule, substitutor).let { if (it != Compatible) return it } + + // 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 aSupertypes = a.typeConstructor.supertypes.filterNot(KotlinBuiltIns::isAny) + val bSupertypes = b.typeConstructor.supertypes.filterNot(KotlinBuiltIns::isAny) + if (aSupertypes.map(substitutor).any { aSupertype -> + bSupertypes.none { bSupertype -> areCompatibleTypes(aSupertype, bSupertype, platformModule) } + }) return Incompatible.Supertypes + + areCompatibleClassScopes(a, b, platformModule, substitutor).let { if (it != Compatible) return it } + + return Compatible + } + + private fun areCompatibleModalities(a: Modality, b: Modality): Boolean { + return a == Modality.FINAL && b == Modality.OPEN || + a == b + } + + + private fun areCompatibleClassScopes( + a: ClassDescriptor, + b: ClassDescriptor, + platformModule: ModuleDescriptor, + substitutor: Substitutor + ): Compatibility { + val unfulfilled = arrayListOf>>>() + + val bMembersByName = b.getMembers().groupBy { it.name } + + outer@ for (aMember in a.getMembers()) { + if (aMember is CallableMemberDescriptor && !aMember.kind.isReal) continue + + val bMembers = bMembersByName[aMember.name]?.filter { bMember -> + aMember is CallableMemberDescriptor && bMember is CallableMemberDescriptor || + aMember is ClassDescriptor && bMember is ClassDescriptor + }.orEmpty() + + val mapping = bMembers.keysToMap { bMember -> + when (aMember) { + is CallableMemberDescriptor -> + areCompatibleCallables(aMember, bMember as CallableMemberDescriptor, platformModule, substitutor) + is ClassDescriptor -> + areCompatibleClassifiers(aMember, bMember as ClassDescriptor) + else -> throw UnsupportedOperationException("Unsupported declaration: $aMember ($bMembers)") + } + } + if (mapping.values.any { it == Compatible }) continue + + val incompatibilityMap = mutableMapOf>() + for ((descriptor, compatibility) in mapping) { + when (compatibility) { + Compatible -> continue@outer + is Incompatible -> incompatibilityMap.getOrPut(compatibility) { SmartList() }.add(descriptor) + } + } + + unfulfilled.add(aMember to incompatibilityMap) + } + + if (a.kind == ClassKind.ENUM_CLASS) { + fun ClassDescriptor.enumEntries() = + unsubstitutedMemberScope.getDescriptorsFiltered().filter(DescriptorUtils::isEnumEntry).map { it.name } + + val aEntries = a.enumEntries() + val bEntries = b.enumEntries() + + if (!bEntries.containsAll(aEntries)) return Incompatible.EnumEntries + } + + // TODO: check static scope? + + if (unfulfilled.isEmpty()) return Compatible + + return Incompatible.ClassScopes(unfulfilled) + } + + private fun ClassDescriptor.getMembers(name: Name? = null): Collection { + val nameFilter = if (name != null) { it -> it == name } else MemberScope.ALL_NAME_FILTER + return defaultType.memberScope + .getDescriptorsFiltered(nameFilter = nameFilter) + .filterIsInstance() + .filterNot(DescriptorUtils::isEnumEntry) + .plus(constructors.filter { nameFilter(it.name) }) + } + + private inline fun equalBy(first: T, second: T, selector: (T) -> K): Boolean = + selector(first) == selector(second) + + 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 + } + + // This substitutor takes the type from A's signature and returns the type that should be in that place in B's signature + private class Substitutor( + aTypeParams: List, + bTypeParams: List, + private val parent: Substitutor? = null + ) : (KotlinType?) -> KotlinType? { + private val typeSubstitutor = TypeSubstitutor.create( + TypeConstructorSubstitution.createByParametersMap(aTypeParams.keysToMap { + bTypeParams[it.index].defaultType.asTypeProjection() + }) + ) + + override fun invoke(type: KotlinType?): KotlinType? = + (parent?.invoke(type) ?: type)?.asTypeProjection()?.let(typeSubstitutor::substitute)?.type + } +} diff --git a/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt b/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt index 9f8cc838968..830d643ad11 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt @@ -43,7 +43,6 @@ import org.jetbrains.kotlin.types.typeUtil.isAnyOrNullableAny import org.jetbrains.kotlin.types.typeUtil.makeNullable import org.jetbrains.kotlin.utils.DFS import org.jetbrains.kotlin.utils.SmartList -import org.jetbrains.kotlin.utils.addToStdlib.safeAs private val RETENTION_PARAMETER_NAME = Name.identifier("value") @@ -426,7 +425,8 @@ fun MemberDescriptor.isEffectivelyExternal(): Boolean { return containingClass != null && containingClass.isEffectivelyExternal() } -fun isParameterOfAnnotation(parameterDescriptor: ParameterDescriptor): Boolean { - val constructedClass = parameterDescriptor.containingDeclaration.safeAs()?.constructedClass - return DescriptorUtils.isAnnotationClass(constructedClass) -} +fun isParameterOfAnnotation(parameterDescriptor: ParameterDescriptor): Boolean = + parameterDescriptor.containingDeclaration.isAnnotationConstructor() + +fun DeclarationDescriptor.isAnnotationConstructor(): Boolean = + this is ConstructorDescriptor && DescriptorUtils.isAnnotationClass(this.constructedClass) diff --git a/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/HasActualMarker.kt b/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/HasActualMarker.kt index 9a8681bf792..691b84e5937 100644 --- a/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/HasActualMarker.kt +++ b/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/HasActualMarker.kt @@ -27,16 +27,16 @@ import org.jetbrains.kotlin.idea.facet.implementingDescriptors import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.MultiTargetPlatform -import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker import org.jetbrains.kotlin.resolve.descriptorUtil.module import org.jetbrains.kotlin.resolve.getMultiTargetPlatform +import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver import java.awt.event.MouseEvent fun ModuleDescriptor.hasActualsFor(descriptor: MemberDescriptor) = actualsFor(descriptor).isNotEmpty() fun ModuleDescriptor.actualsFor(descriptor: MemberDescriptor, checkCompatible: Boolean = true): List = - with(ExpectedActualDeclarationChecker) { + with(ExpectedActualResolver) { if (checkCompatible) { descriptor.findCompatibleActualForExpected(this@actualsFor) } diff --git a/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/HasExpectedMarker.kt b/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/HasExpectedMarker.kt index 446156504b8..be3529f865b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/HasExpectedMarker.kt +++ b/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/HasExpectedMarker.kt @@ -27,14 +27,14 @@ import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject import org.jetbrains.kotlin.psi.psiUtil.hasExpectModifier import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils -import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker import org.jetbrains.kotlin.resolve.descriptorUtil.module +import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver import org.jetbrains.kotlin.utils.addToStdlib.safeAs fun ModuleDescriptor.hasDeclarationOf(descriptor: MemberDescriptor) = declarationOf(descriptor) != null private fun ModuleDescriptor.declarationOf(descriptor: MemberDescriptor): DeclarationDescriptor? = - with(ExpectedActualDeclarationChecker) { + with(ExpectedActualResolver) { descriptor.findCompatibleExpectedForActual(this@declarationOf).firstOrNull() }