K1: Implement a checker that disallows to have different member scopes for expect open and its actual
^KT-22841 Fixed Review: https://jetbrains.team/p/kt/reviews/11603/timeline The commit also introduces `@AllowDifferentMembersInActual` annotation in stdlib which allows to suppress the diagnostic
This commit is contained in:
@@ -36,6 +36,7 @@ import org.jetbrains.kotlin.resolve.calls.util.BuilderLambdaLabelingInfo;
|
||||
import org.jetbrains.kotlin.resolve.deprecation.DescriptorBasedDeprecationInfo;
|
||||
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualAnnotationsIncompatibilityType;
|
||||
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility.Incompatible;
|
||||
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualMemberDiff;
|
||||
import org.jetbrains.kotlin.serialization.deserialization.IncompatibleVersionErrorData;
|
||||
import org.jetbrains.kotlin.types.KotlinType;
|
||||
|
||||
@@ -839,6 +840,32 @@ public interface Errors {
|
||||
|
||||
DiagnosticFactory1<KtNamedDeclaration, MemberDescriptor> EXPECT_AND_ACTUAL_IN_THE_SAME_MODULE = DiagnosticFactory1.create(WARNING, DECLARATION_NAME);
|
||||
|
||||
DiagnosticFactory3<KtClassLikeDeclaration, ClassifierDescriptorWithTypeParameters, Set<ExpectActualMemberDiff<CallableMemberDescriptor, ClassDescriptor>>, ClassDescriptor>
|
||||
ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER =
|
||||
DiagnosticFactory3.create(ERROR, DECLARATION_NAME);
|
||||
DiagnosticFactory1<KtCallableDeclaration, ExpectActualMemberDiff<CallableMemberDescriptor, ClassDescriptor>>
|
||||
NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION = DiagnosticFactory1.create(ERROR, DECLARATION_NAME);
|
||||
DiagnosticFactory1<KtCallableDeclaration, ExpectActualMemberDiff<CallableMemberDescriptor, ClassDescriptor>>
|
||||
RETURN_TYPE_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION = DiagnosticFactory1.create(ERROR, DECLARATION_RETURN_TYPE);
|
||||
DiagnosticFactory1<KtCallableDeclaration, ExpectActualMemberDiff<CallableMemberDescriptor, ClassDescriptor>>
|
||||
MODALITY_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION = DiagnosticFactory1.create(ERROR, MODALITY_MODIFIER);
|
||||
DiagnosticFactory1<KtCallableDeclaration, ExpectActualMemberDiff<CallableMemberDescriptor, ClassDescriptor>>
|
||||
VISIBILITY_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION = DiagnosticFactory1.create(ERROR, VISIBILITY_MODIFIER);
|
||||
DiagnosticFactory1<KtPropertyAccessor, ExpectActualMemberDiff<CallableMemberDescriptor, ClassDescriptor>>
|
||||
SETTER_VISIBILITY_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION = DiagnosticFactory1.create(ERROR, VISIBILITY_MODIFIER);
|
||||
DiagnosticFactory1<KtCallableDeclaration, ExpectActualMemberDiff<CallableMemberDescriptor, ClassDescriptor>>
|
||||
PARAMETER_NAME_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION = DiagnosticFactory1.create(ERROR, DECLARATION_NAME);
|
||||
DiagnosticFactory1<KtCallableDeclaration, ExpectActualMemberDiff<CallableMemberDescriptor, ClassDescriptor>>
|
||||
PROPERTY_KIND_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION = DiagnosticFactory1.create(ERROR, VAL_OR_VAR_NODE);
|
||||
DiagnosticFactory1<KtCallableDeclaration, ExpectActualMemberDiff<CallableMemberDescriptor, ClassDescriptor>>
|
||||
LATEINIT_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION = DiagnosticFactory1.create(ERROR, LATEINIT_MODIFIER);
|
||||
DiagnosticFactory1<KtCallableDeclaration, ExpectActualMemberDiff<CallableMemberDescriptor, ClassDescriptor>>
|
||||
TYPE_PARAMETER_NAMES_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION = DiagnosticFactory1.create(ERROR, TYPE_PARAMETERS_OR_DECLARATION_SIGNATURE);
|
||||
|
||||
DiagnosticFactory3<KtClassLikeDeclaration, ClassifierDescriptorWithTypeParameters, List<Name>, ClassDescriptor>
|
||||
ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_SUPERTYPES_AS_NON_FINAL_EXPECT_CLASSIFIER =
|
||||
DiagnosticFactory3.create(ERROR, DECLARATION_NAME);
|
||||
|
||||
DiagnosticFactory0<PsiElement> OPTIONAL_EXPECTATION_NOT_ON_EXPECTED = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory0<PsiElement> OPTIONAL_DECLARATION_OUTSIDE_OF_ANNOTATION_ENTRY = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory0<PsiElement> OPTIONAL_DECLARATION_USAGE_IN_NON_COMMON_SOURCE = DiagnosticFactory0.create(ERROR);
|
||||
|
||||
+37
-1
@@ -395,6 +395,42 @@ public class DefaultErrorMessages {
|
||||
"{0}: expect and corresponding actual are declared in the same module, which will be prohibited in Kotlin 2.0. See https://youtrack.jetbrains.com/issue/KT-55177",
|
||||
CAPITALIZED_DECLARATION_NAME_WITH_KIND_AND_PLATFORM);
|
||||
|
||||
MAP.put(ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER,
|
||||
"{0}: actual class and its non-final expect class must declare exactly the same non-private members. " +
|
||||
"The following non-private members in actual class are mismatched:{1}\n" +
|
||||
"This error happens because the expect class ''{2}'' is non-final. " +
|
||||
"Also see https://youtrack.jetbrains.com/issue/KT-22841 for more details",
|
||||
CAPITALIZED_DECLARATION_NAME_WITH_KIND_AND_PLATFORM,
|
||||
ExpectActualScopeDiffsRenderer.TEXT,
|
||||
NAME);
|
||||
MAP.put(NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION,
|
||||
"{0}. Also see https://youtrack.jetbrains.com/issue/KT-22841 for more details", ExpectActualScopeDiffRenderer.INSTANCE);
|
||||
MAP.put(RETURN_TYPE_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION,
|
||||
"{0}. Also see https://youtrack.jetbrains.com/issue/KT-22841 for more details", ExpectActualScopeDiffRenderer.INSTANCE);
|
||||
MAP.put(MODALITY_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION,
|
||||
"{0}. Also see https://youtrack.jetbrains.com/issue/KT-22841 for more details", ExpectActualScopeDiffRenderer.INSTANCE);
|
||||
MAP.put(VISIBILITY_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION,
|
||||
"{0}. Also see https://youtrack.jetbrains.com/issue/KT-22841 for more details", ExpectActualScopeDiffRenderer.INSTANCE);
|
||||
MAP.put(PARAMETER_NAME_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION,
|
||||
"{0}. Also see https://youtrack.jetbrains.com/issue/KT-22841 for more details", ExpectActualScopeDiffRenderer.INSTANCE);
|
||||
MAP.put(PROPERTY_KIND_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION,
|
||||
"{0}. Also see https://youtrack.jetbrains.com/issue/KT-22841 for more details", ExpectActualScopeDiffRenderer.INSTANCE);
|
||||
MAP.put(LATEINIT_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION,
|
||||
"{0}. Also see https://youtrack.jetbrains.com/issue/KT-22841 for more details", ExpectActualScopeDiffRenderer.INSTANCE);
|
||||
MAP.put(SETTER_VISIBILITY_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION,
|
||||
"{0}. Also see https://youtrack.jetbrains.com/issue/KT-22841 for more details", ExpectActualScopeDiffRenderer.INSTANCE);
|
||||
MAP.put(TYPE_PARAMETER_NAMES_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION,
|
||||
"{0}. Also see https://youtrack.jetbrains.com/issue/KT-22841 for more details", ExpectActualScopeDiffRenderer.INSTANCE);
|
||||
|
||||
MAP.put(ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_SUPERTYPES_AS_NON_FINAL_EXPECT_CLASSIFIER,
|
||||
"{0}: actual class and its non-final expect class must declare exactly the same supertypes. " +
|
||||
"Actual class declares the following supertypes that are not presented in expect class: {1}. " +
|
||||
"This error happens because the expect class ''{2}'' is non-final. " +
|
||||
"Also see https://youtrack.jetbrains.com/issue/KT-22841 for more details",
|
||||
CAPITALIZED_DECLARATION_NAME_WITH_KIND_AND_PLATFORM,
|
||||
new ListRenderer<>(TO_STRING, (elem) -> "'" + elem + "'"),
|
||||
NAME);
|
||||
|
||||
MAP.put(OPTIONAL_EXPECTATION_NOT_ON_EXPECTED, "'@OptionalExpectation' can only be used on an expected annotation class");
|
||||
MAP.put(OPTIONAL_DECLARATION_OUTSIDE_OF_ANNOTATION_ENTRY, "Declaration annotated with '@OptionalExpectation' can only be used inside an annotation entry");
|
||||
MAP.put(OPTIONAL_DECLARATION_USAGE_IN_NON_COMMON_SOURCE, "Declaration annotated with '@OptionalExpectation' can only be used in common module sources");
|
||||
@@ -1287,7 +1323,7 @@ public class DefaultErrorMessages {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static DiagnosticParameterRenderer<List<Pair<MemberDescriptor, Map<Incompatible<MemberDescriptor>, Collection<MemberDescriptor>>>>> adaptGenerics2(DiagnosticParameterRenderer<List<? extends Pair<? extends MemberDescriptor, ? extends Map<ExpectActualCompatibility.Incompatible<? extends MemberDescriptor>, ? extends Collection<? extends MemberDescriptor>>>>> renderer) {
|
||||
public static DiagnosticParameterRenderer<List<Pair<MemberDescriptor, Map<Incompatible<MemberDescriptor>, Collection<MemberDescriptor>>>>> adaptGenerics2(DiagnosticParameterRenderer<List<? extends Pair<? extends MemberDescriptor, ? extends Map<Incompatible<? extends MemberDescriptor>, ? extends Collection<? extends MemberDescriptor>>>>> renderer) {
|
||||
return (obj, renderingContext) -> renderer.render((List)obj, renderingContext);
|
||||
}
|
||||
|
||||
|
||||
+47
@@ -16,9 +16,13 @@
|
||||
|
||||
package org.jetbrains.kotlin.diagnostics.rendering
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.MemberDescriptor
|
||||
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility.Incompatible
|
||||
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualMemberDiff
|
||||
import java.text.MessageFormat
|
||||
|
||||
class PlatformIncompatibilityDiagnosticRenderer(
|
||||
private val mode: MultiplatformDiagnosticRenderingMode
|
||||
@@ -62,6 +66,49 @@ class IncompatibleExpectedActualClassScopesRenderer(
|
||||
}
|
||||
}
|
||||
|
||||
class ExpectActualScopeDiffsRenderer(
|
||||
private val mode: MultiplatformDiagnosticRenderingMode,
|
||||
) : DiagnosticParameterRenderer<Set<ExpectActualMemberDiff<CallableMemberDescriptor, ClassDescriptor>>> {
|
||||
override fun render(
|
||||
obj: Set<ExpectActualMemberDiff<CallableMemberDescriptor, ClassDescriptor>>,
|
||||
renderingContext: RenderingContext,
|
||||
): String {
|
||||
check(obj.isNotEmpty())
|
||||
return buildString {
|
||||
mode.renderList(this, obj.toList().map { diff ->
|
||||
{
|
||||
appendLine()
|
||||
appendLine(ExpectActualScopeDiffRenderer.render(diff, renderingContext))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val TEXT = ExpectActualScopeDiffsRenderer(MultiplatformDiagnosticRenderingMode())
|
||||
}
|
||||
}
|
||||
|
||||
object ExpectActualScopeDiffRenderer : DiagnosticParameterRenderer<ExpectActualMemberDiff<CallableMemberDescriptor, ClassDescriptor>> {
|
||||
override fun render(
|
||||
obj: ExpectActualMemberDiff<CallableMemberDescriptor, ClassDescriptor>,
|
||||
renderingContext: RenderingContext,
|
||||
): String = MessageFormat.format(
|
||||
obj.kind.rawMessage,
|
||||
Renderers.DECLARATION_NAME_WITH_KIND.render(obj.actualMember, renderingContext),
|
||||
Renderers.NAME.render(obj.expectClass)
|
||||
)
|
||||
}
|
||||
|
||||
class ListRenderer<T>(
|
||||
private val elementRenderer: DiagnosticParameterRenderer<T>,
|
||||
private val elemProcessor: (String) -> String = { it },
|
||||
) : DiagnosticParameterRenderer<List<T>> {
|
||||
override fun render(obj: List<T>, renderingContext: RenderingContext): String =
|
||||
obj.joinToString { elemProcessor(elementRenderer.render(it, renderingContext)) }
|
||||
}
|
||||
|
||||
open class MultiplatformDiagnosticRenderingMode {
|
||||
open fun newLine(sb: StringBuilder) {
|
||||
sb.appendLine()
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.jetbrains.kotlin.types.DynamicTypesSettings
|
||||
|
||||
private val DEFAULT_DECLARATION_CHECKERS = listOf(
|
||||
ExpectActualInTheSameModuleChecker,
|
||||
ActualClassifierMustHasTheSameMembersAsNonFinalExpectClassifierChecker,
|
||||
DataClassDeclarationChecker(),
|
||||
ConstModifierChecker,
|
||||
UnderscoreChecker,
|
||||
|
||||
+360
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.resolve.checkers
|
||||
|
||||
import org.jetbrains.kotlin.config.LanguageFeature
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingTrace
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.*
|
||||
import org.jetbrains.kotlin.resolve.multiplatform.*
|
||||
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.model.TypeSubstitutorMarker
|
||||
import java.util.*
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
|
||||
object ActualClassifierMustHasTheSameMembersAsNonFinalExpectClassifierChecker : DeclarationChecker {
|
||||
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
|
||||
val (actual, expect) = matchActualWithNonFinalExpect(declaration, descriptor, context) ?: return
|
||||
|
||||
// The explicit casts won't be necessary when we start compiling kotlin with K2. K1 doesn't build CFG properly
|
||||
declaration as KtClassLikeDeclaration
|
||||
descriptor as ClassifierDescriptorWithTypeParameters
|
||||
|
||||
checkSupertypes(actual, expect, context, declaration, descriptor)
|
||||
checkExpectActualScopeDiff(expect, actual, context, declaration, descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkSupertypes(
|
||||
actual: ClassDescriptor,
|
||||
expect: ClassDescriptor,
|
||||
context: DeclarationCheckerContext,
|
||||
declaration: KtClassLikeDeclaration,
|
||||
descriptor: ClassifierDescriptorWithTypeParameters,
|
||||
) {
|
||||
val addedSupertypes = (actual.getSuperInterfaces() + listOfNotNull(actual.getSuperClassNotAny())).map(ClassDescriptor::fqNameSafe) -
|
||||
(expect.getSuperInterfaces() + listOfNotNull(expect.getSuperClassNotAny())).map(ClassDescriptor::fqNameSafe).toSet()
|
||||
|
||||
if (addedSupertypes.isNotEmpty()) {
|
||||
context.trace.report(
|
||||
Errors.ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_SUPERTYPES_AS_NON_FINAL_EXPECT_CLASSIFIER.on(
|
||||
declaration,
|
||||
descriptor,
|
||||
addedSupertypes.map(FqName::shortName),
|
||||
expect,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkExpectActualScopeDiff(
|
||||
expect: ClassDescriptor,
|
||||
actual: ClassDescriptor,
|
||||
context: DeclarationCheckerContext,
|
||||
declaration: KtClassLikeDeclaration,
|
||||
descriptor: ClassifierDescriptorWithTypeParameters,
|
||||
) {
|
||||
val scopeDiff = calculateExpectActualScopeDiff(expect, actual)
|
||||
if (scopeDiff.isNotEmpty()) {
|
||||
context.trace.report(
|
||||
Errors.ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER.on(
|
||||
declaration,
|
||||
descriptor,
|
||||
scopeDiff,
|
||||
scopeDiff.first().expectClass, // All expect classes in scopeDiff are the same
|
||||
)
|
||||
)
|
||||
}
|
||||
if (descriptor !is TypeAliasDescriptor) {
|
||||
for (diff in scopeDiff) {
|
||||
// If it can't be reported, it's not a big deal, because this error is already
|
||||
// reported on the 'actual class' itself (see code above)
|
||||
context.trace.reportIfPossible(diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val allowDifferentMembersInActualFqn = FqName("kotlin.AllowDifferentMembersInActual")
|
||||
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
internal fun matchActualWithNonFinalExpect(
|
||||
declaration: KtDeclaration,
|
||||
descriptor: DeclarationDescriptor,
|
||||
context: DeclarationCheckerContext,
|
||||
): Pair<ClassDescriptor, ClassDescriptor>? {
|
||||
contract {
|
||||
returnsNotNull() implies (declaration is KtClassLikeDeclaration)
|
||||
returnsNotNull() implies (descriptor is ClassifierDescriptorWithTypeParameters)
|
||||
}
|
||||
if (!context.languageVersionSettings.supportsFeature(LanguageFeature.MultiPlatformProjects)) return null
|
||||
if (declaration !is KtTypeAlias && declaration !is KtClassOrObject) return null
|
||||
if (descriptor !is TypeAliasDescriptor && descriptor !is ClassDescriptor) return null
|
||||
|
||||
// Common supertype of KtTypeAlias and KtClassOrObject is KtClassLikeDeclaration.
|
||||
// Common supertype of TypeAliasDescriptor and ClassDescriptor is ClassifierDescriptorWithTypeParameters.
|
||||
// The explicit casts won't be necessary when we start compiling kotlin with K2.
|
||||
declaration as KtClassLikeDeclaration
|
||||
descriptor as ClassifierDescriptorWithTypeParameters
|
||||
|
||||
if (!descriptor.isActual) return null
|
||||
|
||||
with(OptInUsageChecker) {
|
||||
if (declaration.isDeclarationAnnotatedWith(allowDifferentMembersInActualFqn, context.trace.bindingContext)) return null
|
||||
}
|
||||
|
||||
val actual = when (descriptor) {
|
||||
is ClassDescriptor -> descriptor
|
||||
is TypeAliasDescriptor -> descriptor.classDescriptor
|
||||
else -> error("ClassifierDescriptorWithTypeParameters has only two inheritors")
|
||||
} ?: return null
|
||||
// If actual is final then expect is final as well (otherwise another checker will report a diagnostic).
|
||||
// There is no need to waste time searching for the appropriate expect and checking its modality. This `if` is an optimization
|
||||
if (actual.modality == Modality.FINAL) return null
|
||||
|
||||
val expect = ExpectedActualResolver.findExpectedForActual(descriptor)
|
||||
?.get(ExpectActualCompatibility.Compatible)
|
||||
?.singleOrNull() as? ClassDescriptor // if actual has more than one expects then it will be reported by another checker
|
||||
?: return null
|
||||
|
||||
if (expect.modality == Modality.FINAL) return null
|
||||
return actual to expect
|
||||
}
|
||||
|
||||
private fun calculateExpectActualScopeDiff(
|
||||
expect: ClassDescriptor,
|
||||
actual: ClassDescriptor,
|
||||
): Set<ExpectActualMemberDiff<CallableMemberDescriptor, ClassDescriptor>> {
|
||||
val matchingContext = ClassicExpectActualMatchingContext(actual.module)
|
||||
val classTypeSubstitutor = matchingContext.createExpectActualTypeParameterSubstitutor(
|
||||
expect.declaredTypeParameters,
|
||||
actual.declaredTypeParameters,
|
||||
parentSubstitutor = null
|
||||
)
|
||||
|
||||
val expectClassCallables = expect.unsubstitutedMemberScope
|
||||
.extractNonPrivateCallables(classTypeSubstitutor, ExpectActual.EXPECT, matchingContext)
|
||||
val actualClassCallables = actual.unsubstitutedMemberScope
|
||||
.extractNonPrivateCallables(classTypeSubstitutor, ExpectActual.ACTUAL, matchingContext)
|
||||
.filter { it.descriptor.kind.isReal } // Filter out fake-overrides from actual because we compare list of supertypes separately anyway
|
||||
|
||||
val nameAndKindToExpectCallable = expectClassCallables.groupBy { it.name to it.kind }
|
||||
|
||||
return (actualClassCallables - expectClassCallables).asSequence()
|
||||
.flatMap { unmatchedActualCallable ->
|
||||
when (val expectCallablesWithTheSameNameAndKind =
|
||||
nameAndKindToExpectCallable[unmatchedActualCallable.name to unmatchedActualCallable.kind]) {
|
||||
null -> listOf(ExpectActualMemberDiff.Kind.NonPrivateCallableAdded)
|
||||
else -> expectCallablesWithTheSameNameAndKind.map {
|
||||
calculateExpectActualMemberDiffKind(
|
||||
expect = it,
|
||||
actual = unmatchedActualCallable,
|
||||
checkParameterNames = unmatchedActualCallable.descriptor.hasStableParameterNames()
|
||||
)
|
||||
}
|
||||
}.filterNotNull().map { kind -> ExpectActualMemberDiff(kind, unmatchedActualCallable.descriptor, expect) }
|
||||
}
|
||||
.toSet()
|
||||
}
|
||||
|
||||
private fun MemberScope.extractNonPrivateCallables(
|
||||
classTypeSubstitutor: TypeSubstitutorMarker,
|
||||
expectActual: ExpectActual,
|
||||
matchingContext: ClassicExpectActualMatchingContext,
|
||||
): Set<Callable> {
|
||||
val functions =
|
||||
getFunctionNames().asSequence().flatMap { getContributedFunctions(it, NoLookupLocation.WHEN_GET_ALL_DESCRIPTORS) }
|
||||
val properties =
|
||||
getVariableNames().asSequence().flatMap { getContributedVariables(it, NoLookupLocation.WHEN_GET_ALL_DESCRIPTORS) }
|
||||
return (functions + properties).filter { !Visibilities.isPrivate(it.visibility.delegate) }
|
||||
.map { descriptor -> Callable(descriptor, expectActual, classTypeSubstitutor, matchingContext) }
|
||||
.toSet()
|
||||
}
|
||||
|
||||
private data class Parameter(val name: Name, val type: KotlinType)
|
||||
private enum class Kind { FUNCTION, PROPERTY }
|
||||
private enum class ExpectActual { EXPECT, ACTUAL }
|
||||
private data class TypeParameter(val name: Name, val upperBounds: List<KotlinType>)
|
||||
private class Callable(
|
||||
val descriptor: CallableMemberDescriptor,
|
||||
val expectActual: ExpectActual,
|
||||
val classTypeSubstitutor: TypeSubstitutorMarker,
|
||||
val matchingContext: ClassicExpectActualMatchingContext,
|
||||
) {
|
||||
val name: Name = descriptor.name
|
||||
val kind: Kind = when (descriptor) {
|
||||
is PropertyDescriptor -> Kind.PROPERTY
|
||||
is FunctionDescriptor -> Kind.FUNCTION
|
||||
else -> error("Unknown kind $descriptor")
|
||||
}
|
||||
val isVarProperty: Boolean = descriptor is PropertyDescriptor && descriptor.isVar
|
||||
val isLateinitProperty: Boolean = descriptor is PropertyDescriptor && descriptor.isLateInit
|
||||
val modality: Modality = descriptor.modality
|
||||
val visibility: Visibility = descriptor.visibility.delegate
|
||||
val setterVisibility: Visibility? = (descriptor as? PropertyDescriptor)?.setter?.visibility?.delegate
|
||||
val parameters: List<Parameter> = descriptor.valueParameters.map { Parameter(it.name, it.type) }
|
||||
val returnType: KotlinType = descriptor.returnType ?: error("Can't get return type")
|
||||
val extensionReceiverType: KotlinType? = descriptor.extensionReceiverParameter?.type
|
||||
val typeParameters: List<TypeParameter> = descriptor.typeParameters.map { TypeParameter(it.name, it.upperBounds) }
|
||||
val contextReceiverTypes: List<KotlinType> = descriptor.contextReceiverParameters.map(ValueDescriptor::getType)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other !is Callable) return false
|
||||
check(classTypeSubstitutor === other.classTypeSubstitutor)
|
||||
check(matchingContext === other.matchingContext)
|
||||
return if (expectActual == other.expectActual) {
|
||||
name == other.name &&
|
||||
kind == other.kind &&
|
||||
isVarProperty == other.isVarProperty &&
|
||||
isLateinitProperty == other.isLateinitProperty &&
|
||||
modality == other.modality &&
|
||||
visibility == other.visibility &&
|
||||
setterVisibility == other.setterVisibility &&
|
||||
parameters == other.parameters &&
|
||||
returnType == other.returnType &&
|
||||
extensionReceiverType == other.extensionReceiverType &&
|
||||
typeParameters == other.typeParameters &&
|
||||
contextReceiverTypes == other.contextReceiverTypes
|
||||
} else {
|
||||
val (expect, actual) = if (expectActual == ExpectActual.EXPECT) this to other else other to this
|
||||
calculateExpectActualMemberDiffKind(expect, actual) == null
|
||||
}
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = // Don't hash the types because type comparison is complicated
|
||||
Objects.hash(
|
||||
name,
|
||||
kind,
|
||||
isVarProperty,
|
||||
isLateinitProperty,
|
||||
modality,
|
||||
visibility,
|
||||
setterVisibility,
|
||||
parameters.map(Parameter::name),
|
||||
extensionReceiverType != null,
|
||||
typeParameters.map(TypeParameter::name),
|
||||
contextReceiverTypes.size,
|
||||
)
|
||||
}
|
||||
|
||||
private val CallableMemberDescriptor.psiIfReal: KtCallableDeclaration?
|
||||
get() = takeIf { it.kind.isReal }?.source?.let { it as? KotlinSourceElement }?.psi as? KtCallableDeclaration
|
||||
|
||||
private fun ClassicExpectActualMatchingContext.areCompatibleWithSubstitution(
|
||||
expect: KotlinType?,
|
||||
actual: KotlinType?,
|
||||
substitutor: TypeSubstitutorMarker,
|
||||
): Boolean = areCompatibleExpectActualTypes(expect?.let { substitutor.safeSubstitute(it) }, actual)
|
||||
|
||||
private fun ClassicExpectActualMatchingContext.areCompatibleListWithSubstitution(
|
||||
expect: List<KotlinType>,
|
||||
actual: List<KotlinType>,
|
||||
substitutor: TypeSubstitutorMarker,
|
||||
): Boolean = expect.size == actual.size && expect.asSequence().zip(actual.asSequence())
|
||||
.all { (a, b) -> areCompatibleWithSubstitution(a, b, substitutor) }
|
||||
|
||||
private fun ClassicExpectActualMatchingContext.areCompatibleUpperBoundsWithSubstitution(
|
||||
expect: List<List<KotlinType>>,
|
||||
actual: List<List<KotlinType>>,
|
||||
substitutor: TypeSubstitutorMarker,
|
||||
): Boolean = expect.size == actual.size && expect.asSequence().zip(actual.asSequence())
|
||||
.all { (a, b) -> areCompatibleListWithSubstitution(a, b, substitutor) }
|
||||
|
||||
private fun calculateExpectActualMemberDiffKind(
|
||||
expect: Callable,
|
||||
actual: Callable,
|
||||
checkParameterNames: Boolean = true,
|
||||
): ExpectActualMemberDiff.Kind? {
|
||||
check(expect.expectActual == ExpectActual.EXPECT)
|
||||
check(actual.expectActual == ExpectActual.ACTUAL)
|
||||
check(expect.classTypeSubstitutor === actual.classTypeSubstitutor)
|
||||
check(expect.matchingContext === actual.matchingContext)
|
||||
val substitutor = actual.matchingContext.createExpectActualTypeParameterSubstitutor(
|
||||
expect.descriptor.typeParameters,
|
||||
actual.descriptor.typeParameters,
|
||||
actual.classTypeSubstitutor
|
||||
)
|
||||
with(actual.matchingContext) {
|
||||
return when {
|
||||
expect.name != actual.name ||
|
||||
expect.kind != actual.kind ||
|
||||
!areCompatibleListWithSubstitution(
|
||||
expect.parameters.map(Parameter::type),
|
||||
actual.parameters.map(Parameter::type),
|
||||
substitutor
|
||||
) ||
|
||||
!areCompatibleUpperBoundsWithSubstitution(
|
||||
expect.typeParameters.map(TypeParameter::upperBounds),
|
||||
actual.typeParameters.map(TypeParameter::upperBounds),
|
||||
substitutor
|
||||
) ||
|
||||
!areCompatibleWithSubstitution(
|
||||
expect.extensionReceiverType,
|
||||
actual.extensionReceiverType,
|
||||
substitutor
|
||||
) ||
|
||||
!areCompatibleListWithSubstitution(
|
||||
expect.contextReceiverTypes,
|
||||
actual.contextReceiverTypes,
|
||||
substitutor
|
||||
) ->
|
||||
ExpectActualMemberDiff.Kind.NonPrivateCallableAdded
|
||||
|
||||
expect.isVarProperty != actual.isVarProperty -> ExpectActualMemberDiff.Kind.PropertyKindChangedInOverride
|
||||
|
||||
expect.isLateinitProperty != actual.isLateinitProperty -> ExpectActualMemberDiff.Kind.LateinitChangedInOverride
|
||||
|
||||
expect.modality != actual.modality -> ExpectActualMemberDiff.Kind.ModalityChangedInOverride
|
||||
|
||||
expect.visibility != actual.visibility -> ExpectActualMemberDiff.Kind.VisibilityChangedInOverride
|
||||
|
||||
expect.setterVisibility != actual.setterVisibility -> ExpectActualMemberDiff.Kind.SetterVisibilityChangedInOverride
|
||||
|
||||
checkParameterNames && expect.parameters.map(Parameter::name) != actual.parameters.map(Parameter::name) ->
|
||||
ExpectActualMemberDiff.Kind.ParameterNameChangedInOverride
|
||||
|
||||
expect.typeParameters.map(TypeParameter::name) != actual.typeParameters.map(TypeParameter::name) ->
|
||||
ExpectActualMemberDiff.Kind.TypeParameterNamesChangedInOverride
|
||||
|
||||
!areCompatibleWithSubstitution(expect.returnType, actual.returnType, substitutor) ->
|
||||
ExpectActualMemberDiff.Kind.ReturnTypeChangedInOverride
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun BindingTrace.reportIfPossible(diff: ExpectActualMemberDiff<CallableMemberDescriptor, ClassDescriptor>) {
|
||||
val psi = diff.actualMember.psiIfReal ?: return
|
||||
val diagnostic = when (diff.kind) {
|
||||
ExpectActualMemberDiff.Kind.NonPrivateCallableAdded ->
|
||||
Errors.NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION.on(psi, diff)
|
||||
ExpectActualMemberDiff.Kind.ReturnTypeChangedInOverride ->
|
||||
Errors.RETURN_TYPE_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION.on(psi, diff)
|
||||
ExpectActualMemberDiff.Kind.ModalityChangedInOverride ->
|
||||
Errors.MODALITY_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION.on(psi, diff)
|
||||
ExpectActualMemberDiff.Kind.VisibilityChangedInOverride ->
|
||||
Errors.VISIBILITY_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION.on(psi, diff)
|
||||
ExpectActualMemberDiff.Kind.SetterVisibilityChangedInOverride ->
|
||||
Errors.SETTER_VISIBILITY_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION.on((psi as? KtProperty)?.setter ?: return, diff)
|
||||
ExpectActualMemberDiff.Kind.ParameterNameChangedInOverride ->
|
||||
Errors.PARAMETER_NAME_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION.on(psi, diff)
|
||||
ExpectActualMemberDiff.Kind.PropertyKindChangedInOverride ->
|
||||
Errors.PROPERTY_KIND_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION.on(psi, diff)
|
||||
ExpectActualMemberDiff.Kind.LateinitChangedInOverride ->
|
||||
Errors.LATEINIT_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION.on(psi, diff)
|
||||
ExpectActualMemberDiff.Kind.TypeParameterNamesChangedInOverride ->
|
||||
Errors.TYPE_PARAMETER_NAMES_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION.on(psi, diff)
|
||||
}
|
||||
report(diagnostic)
|
||||
}
|
||||
Reference in New Issue
Block a user