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
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+13
-523
@@ -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<PsiSourceFile>()
|
||||
?.run { VfsUtilCore.virtualToIoFile(psiFile.virtualFile) }
|
||||
|
||||
fun Map<out Compatibility, Collection<MemberDescriptor>>.allStrongIncompatibilities(): Boolean =
|
||||
private fun Map<out Compatibility, Collection<MemberDescriptor>>.allStrongIncompatibilities(): Boolean =
|
||||
this.keys.all { it is Incompatible && it.kind == Compatibility.IncompatibilityKind.STRONG }
|
||||
|
||||
private fun findActualForExpected(
|
||||
expected: MemberDescriptor,
|
||||
platformModule: ModuleDescriptor
|
||||
): Map<Compatibility, List<MemberDescriptor>>? {
|
||||
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<Compatibility, List<MemberDescriptor>>? {
|
||||
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<CallableMemberDescriptor>().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<MemberDescriptor> =
|
||||
findActualForExpected(this, platformModule)?.get(Compatible).orEmpty()
|
||||
|
||||
fun MemberDescriptor.findAnyActualForExpected(platformModule: ModuleDescriptor): List<MemberDescriptor> {
|
||||
val actualsGroupedByCompatibility = findActualForExpected(this, platformModule)
|
||||
return actualsGroupedByCompatibility?.get(Compatible)
|
||||
?: actualsGroupedByCompatibility?.values?.flatten()
|
||||
?: emptyList()
|
||||
}
|
||||
|
||||
fun MemberDescriptor.findCompatibleExpectedForActual(commonModule: ModuleDescriptor): List<MemberDescriptor> =
|
||||
findExpectedForActual(this, commonModule)?.get(Compatible).orEmpty()
|
||||
|
||||
private fun CallableMemberDescriptor.findNamesakesFromModule(module: ModuleDescriptor): Collection<CallableMemberDescriptor> {
|
||||
val containingDeclaration = containingDeclaration
|
||||
val scopes = when (containingDeclaration) {
|
||||
is PackageFragmentDescriptor -> {
|
||||
listOf(module.getPackage(containingDeclaration.fqName).memberScope)
|
||||
}
|
||||
is ClassDescriptor -> {
|
||||
val classes = containingDeclaration.findClassifiersFromModule(module).filterIsInstance<ClassDescriptor>()
|
||||
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<ClassifierDescriptorWithTypeParameters> {
|
||||
val classId = classId ?: return emptyList()
|
||||
|
||||
fun MemberScope.getAllClassifiers(name: Name): Collection<ClassifierDescriptorWithTypeParameters> =
|
||||
getDescriptorsFiltered(DescriptorKindFilter.CLASSIFIERS) { it == name }
|
||||
.filterIsInstance<ClassifierDescriptorWithTypeParameters>()
|
||||
|
||||
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<Pair<MemberDescriptor, Map<Incompatible, Collection<MemberDescriptor>>>>
|
||||
) : 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<ValueParameterDescriptor>,
|
||||
bParams: List<ValueParameterDescriptor>
|
||||
): 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<A, B> = FooImpl<A, B>", see DeclarationsChecker#checkActualTypeAlias
|
||||
(classifier as? TypeAliasDescriptor)?.classDescriptor == actual
|
||||
}
|
||||
}
|
||||
|
||||
private fun areCompatibleTypeLists(a: List<KotlinType?>, b: List<KotlinType?>, platformModule: ModuleDescriptor): Boolean {
|
||||
for (i in a.indices) {
|
||||
if (!areCompatibleTypes(a[i], b[i], platformModule)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun areCompatibleTypeParameters(
|
||||
a: List<TypeParameterDescriptor>,
|
||||
b: List<TypeParameterDescriptor>,
|
||||
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<Pair<MemberDescriptor, Map<Incompatible, MutableCollection<MemberDescriptor>>>>()
|
||||
|
||||
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<Incompatible, MutableCollection<MemberDescriptor>>()
|
||||
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<MemberDescriptor> {
|
||||
val nameFilter = if (name != null) { it -> it == name } else MemberScope.ALL_NAME_FILTER
|
||||
return defaultType.memberScope
|
||||
.getDescriptorsFiltered(nameFilter = nameFilter)
|
||||
.filterIsInstance<MemberDescriptor>()
|
||||
.filterNot(DescriptorUtils::isEnumEntry)
|
||||
.plus(constructors.filter { nameFilter(it.name) })
|
||||
}
|
||||
|
||||
private inline fun <T, K> equalBy(first: T, second: T, selector: (T) -> K): Boolean =
|
||||
selector(first) == selector(second)
|
||||
|
||||
private inline fun <T, K> equalsBy(first: List<T>, second: List<T>, 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<TypeParameterDescriptor>,
|
||||
bTypeParams: List<TypeParameterDescriptor>,
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
+515
@@ -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<MemberDescriptor> =
|
||||
findActualForExpected(this, platformModule)?.get(Compatible).orEmpty()
|
||||
|
||||
fun MemberDescriptor.findAnyActualForExpected(platformModule: ModuleDescriptor): List<MemberDescriptor> {
|
||||
val actualsGroupedByCompatibility = findActualForExpected(this, platformModule)
|
||||
return actualsGroupedByCompatibility?.get(Compatible)
|
||||
?: actualsGroupedByCompatibility?.values?.flatten()
|
||||
?: emptyList()
|
||||
}
|
||||
|
||||
fun MemberDescriptor.findCompatibleExpectedForActual(commonModule: ModuleDescriptor): List<MemberDescriptor> =
|
||||
findExpectedForActual(this, commonModule)?.get(Compatible).orEmpty()
|
||||
|
||||
fun findActualForExpected(expected: MemberDescriptor, platformModule: ModuleDescriptor): Map<Compatibility, List<MemberDescriptor>>? {
|
||||
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<Compatibility, List<MemberDescriptor>>? {
|
||||
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<CallableMemberDescriptor>().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<CallableMemberDescriptor> {
|
||||
val containingDeclaration = containingDeclaration
|
||||
val scopes = when (containingDeclaration) {
|
||||
is PackageFragmentDescriptor -> {
|
||||
listOf(module.getPackage(containingDeclaration.fqName).memberScope)
|
||||
}
|
||||
is ClassDescriptor -> {
|
||||
val classes = containingDeclaration.findClassifiersFromModule(module).filterIsInstance<ClassDescriptor>()
|
||||
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<ClassifierDescriptorWithTypeParameters> {
|
||||
val classId = classId ?: return emptyList()
|
||||
|
||||
fun MemberScope.getAllClassifiers(name: Name): Collection<ClassifierDescriptorWithTypeParameters> =
|
||||
getDescriptorsFiltered(DescriptorKindFilter.CLASSIFIERS) { it == name }
|
||||
.filterIsInstance<ClassifierDescriptorWithTypeParameters>()
|
||||
|
||||
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<Pair<MemberDescriptor, Map<Incompatible, Collection<MemberDescriptor>>>>
|
||||
) : 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<ValueParameterDescriptor>,
|
||||
bParams: List<ValueParameterDescriptor>
|
||||
): 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<A, B> = FooImpl<A, B>", see DeclarationsChecker#checkActualTypeAlias
|
||||
(classifier as? TypeAliasDescriptor)?.classDescriptor == actual
|
||||
}
|
||||
}
|
||||
|
||||
private fun areCompatibleTypeLists(a: List<KotlinType?>, b: List<KotlinType?>, platformModule: ModuleDescriptor): Boolean {
|
||||
for (i in a.indices) {
|
||||
if (!areCompatibleTypes(a[i], b[i], platformModule)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun areCompatibleTypeParameters(
|
||||
a: List<TypeParameterDescriptor>,
|
||||
b: List<TypeParameterDescriptor>,
|
||||
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<Pair<MemberDescriptor, Map<Incompatible, MutableCollection<MemberDescriptor>>>>()
|
||||
|
||||
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<Incompatible, MutableCollection<MemberDescriptor>>()
|
||||
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<MemberDescriptor> {
|
||||
val nameFilter = if (name != null) { it -> it == name } else MemberScope.ALL_NAME_FILTER
|
||||
return defaultType.memberScope
|
||||
.getDescriptorsFiltered(nameFilter = nameFilter)
|
||||
.filterIsInstance<MemberDescriptor>()
|
||||
.filterNot(DescriptorUtils::isEnumEntry)
|
||||
.plus(constructors.filter { nameFilter(it.name) })
|
||||
}
|
||||
|
||||
private inline fun <T, K> equalBy(first: T, second: T, selector: (T) -> K): Boolean =
|
||||
selector(first) == selector(second)
|
||||
|
||||
private inline fun <T, K> equalsBy(first: List<T>, second: List<T>, 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<TypeParameterDescriptor>,
|
||||
bTypeParams: List<TypeParameterDescriptor>,
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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<ConstructorDescriptor>()?.constructedClass
|
||||
return DescriptorUtils.isAnnotationClass(constructedClass)
|
||||
}
|
||||
fun isParameterOfAnnotation(parameterDescriptor: ParameterDescriptor): Boolean =
|
||||
parameterDescriptor.containingDeclaration.isAnnotationConstructor()
|
||||
|
||||
fun DeclarationDescriptor.isAnnotationConstructor(): Boolean =
|
||||
this is ConstructorDescriptor && DescriptorUtils.isAnnotationClass(this.constructedClass)
|
||||
|
||||
@@ -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<DeclarationDescriptor> =
|
||||
with(ExpectedActualDeclarationChecker) {
|
||||
with(ExpectedActualResolver) {
|
||||
if (checkCompatible) {
|
||||
descriptor.findCompatibleActualForExpected(this@actualsFor)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user