From d465f5fd8e5c5fc2c7aa2b2c2419493e142a53fa Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Fri, 12 Jan 2018 19:34:53 +0100 Subject: [PATCH] Extract ExpectedActualResolver out of ExpectedActualDeclarationChecker The point is that it's placed in module 'resolution' where it can be accessed for example in ArgumentsToParametersMapper to load default argument values from expected function --- .../jetbrains/kotlin/diagnostics/Errors.java | 2 +- .../diagnostics/PositioningStrategies.kt | 4 +- ...atformIncompatibilityDiagnosticRenderer.kt | 2 +- .../ExpectedActualDeclarationChecker.kt | 536 +----------------- .../multiplatform/ExpectedActualResolver.kt | 515 +++++++++++++++++ .../kotlin/resolve/DescriptorUtils.kt | 10 +- .../highlighter/markers/HasActualMarker.kt | 4 +- .../highlighter/markers/HasExpectedMarker.kt | 4 +- 8 files changed, 541 insertions(+), 536 deletions(-) create mode 100644 compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectedActualResolver.kt 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() }