org.jetbrains.kotlin.resolve.calls.mpp.AbstractExpectActualChecker.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlin-compiler-embeddable Show documentation
Show all versions of kotlin-compiler-embeddable Show documentation
the Kotlin compiler embeddable
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.resolve.calls.mpp
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.mpp.*
import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.resolve.calls.mpp.AbstractExpectActualMatcher.matchSingleExpectAgainstPotentialActuals
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCheckingCompatibility
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualMatchingCompatibility
import org.jetbrains.kotlin.types.model.TypeSubstitutorMarker
import org.jetbrains.kotlin.utils.SmartList
import org.jetbrains.kotlin.utils.addToStdlib.enumMapOf
import org.jetbrains.kotlin.utils.addToStdlib.enumSetOf
import org.jetbrains.kotlin.utils.zipIfSizesAreEqual
import java.util.*
/**
* This object is responsible for checking of expect-actual pairs
* after they have been matched by [the matcher][AbstractExpectActualMatcher]
*
* See `/docs/fir/k2_kmp.md` for details
*/
object AbstractExpectActualChecker {
fun getClassifiersCompatibility(
expectClassSymbol: RegularClassSymbolMarker,
actualClassLikeSymbol: ClassLikeSymbolMarker,
context: ExpectActualMatchingContext,
languageVersionSettings: LanguageVersionSettings,
): ExpectActualCheckingCompatibility {
val result = with(context) {
getClassifiersCompatibility(
expectClassSymbol,
actualClassLikeSymbol,
parentSubstitutor = null,
languageVersionSettings,
)
}
@Suppress("UNCHECKED_CAST")
return result as ExpectActualCheckingCompatibility
}
fun getCallablesCompatibility(
expectDeclaration: CallableSymbolMarker,
actualDeclaration: CallableSymbolMarker,
expectContainingClass: RegularClassSymbolMarker?,
actualContainingClass: RegularClassSymbolMarker?,
context: ExpectActualMatchingContext,
languageVersionSettings: LanguageVersionSettings,
): ExpectActualCheckingCompatibility = with(context) {
val expectTypeParameters = expectContainingClass?.typeParameters.orEmpty()
val actualTypeParameters = actualContainingClass?.typeParameters.orEmpty()
val parentSubstitutor = (expectTypeParameters zipIfSizesAreEqual actualTypeParameters)
?.let { createExpectActualTypeParameterSubstitutor(it, parentSubstitutor = null) }
val result = getCallablesCompatibility(
expectDeclaration,
actualDeclaration,
parentSubstitutor,
expectContainingClass,
actualContainingClass,
languageVersionSettings,
)
@Suppress("UNCHECKED_CAST")
result as ExpectActualCheckingCompatibility
}
fun checkSingleExpectTopLevelDeclarationAgainstMatchedActual(
expectDeclaration: DeclarationSymbolMarker,
actualDeclaration: DeclarationSymbolMarker,
context: ExpectActualMatchingContext,
languageVersionSettings: LanguageVersionSettings,
) {
with(context) {
checkSingleExpectAgainstMatchedActual(
expectDeclaration,
actualDeclaration,
substitutor = null,
expectClassSymbol = null,
actualClassSymbol = null,
incompatibleMembers = null,
languageVersionSettings,
)
}
}
private fun ExpectActualMatchingContext<*>.getClassifiersCompatibility(
expectClassSymbol: RegularClassSymbolMarker,
actualClassLikeSymbol: ClassLikeSymbolMarker,
parentSubstitutor: TypeSubstitutorMarker?,
languageVersionSettings: LanguageVersionSettings,
): ExpectActualCheckingCompatibility<*> {
// Can't check FQ names here because nested expected class may be implemented via actual typealias's expansion with the other FQ name
require(nameOf(expectClassSymbol) == nameOf(actualClassLikeSymbol)) {
"This function should be invoked only for declarations with the same name: $expectClassSymbol, $actualClassLikeSymbol"
}
val actualClass = when (actualClassLikeSymbol) {
is RegularClassSymbolMarker -> actualClassLikeSymbol
is TypeAliasSymbolMarker -> actualClassLikeSymbol.expandToRegularClass()
?: return ExpectActualCheckingCompatibility.Compatible // do not report extra error on erroneous typealias
else -> error("Incorrect actual classifier for $expectClassSymbol: $actualClassLikeSymbol")
}
if (!areCompatibleClassKinds(expectClassSymbol, actualClass)) return ExpectActualCheckingCompatibility.ClassKind
if (!equalBy(expectClassSymbol, actualClass) { listOf(it.isCompanion, it.isInner, it.isInline || it.isValue) }) {
return ExpectActualCheckingCompatibility.ClassModifiers
}
if (expectClassSymbol.isFun && !actualClass.isFun && actualClass.isNotSamInterface()) {
return ExpectActualCheckingCompatibility.FunInterfaceModifier
}
val expectTypeParameterSymbols = expectClassSymbol.typeParameters
val actualTypeParameterSymbols = actualClass.typeParameters
if (expectTypeParameterSymbols.size != actualTypeParameterSymbols.size) {
return ExpectActualCheckingCompatibility.ClassTypeParameterCount
}
if (!areCompatibleModalities(expectClassSymbol.modality, actualClass.modality)) {
return ExpectActualCheckingCompatibility.Modality
}
if (!areCompatibleClassVisibilities(expectClassSymbol, actualClass)) {
return ExpectActualCheckingCompatibility.Visibility
}
val substitutor = createExpectActualTypeParameterSubstitutor(
(expectTypeParameterSymbols zipIfSizesAreEqual actualTypeParameterSymbols)
?: error("expect/actual type parameters sizes are checked earlier"),
parentSubstitutor
)
if (!areCompatibleTypeParameterUpperBounds(expectTypeParameterSymbols, actualTypeParameterSymbols, substitutor)) {
return ExpectActualCheckingCompatibility.ClassTypeParameterUpperBounds
}
getTypeParametersVarianceOrReifiedIncompatibility(expectTypeParameterSymbols, actualTypeParameterSymbols)
?.let { return it }
if (!areCompatibleSupertypes(expectClassSymbol, actualClass, substitutor)) {
return ExpectActualCheckingCompatibility.Supertypes
}
getClassScopesIncompatibility(expectClassSymbol, actualClass, substitutor, languageVersionSettings)?.let { return it }
return ExpectActualCheckingCompatibility.Compatible
}
private fun ExpectActualMatchingContext<*>.areCompatibleSupertypes(
expectClassSymbol: RegularClassSymbolMarker,
actualClassSymbol: RegularClassSymbolMarker,
substitutor: TypeSubstitutorMarker,
): Boolean {
val expectSupertypes = expectClassSymbol.superTypes.filterNot { it.typeConstructor().isAnyConstructor() }
val actualType = actualClassSymbol.defaultType
return expectSupertypes.all { expectSupertype ->
val expectType = substitutor.safeSubstitute(expectSupertype)
isSubtypeOf(superType = expectType, subType = actualType) &&
!isSubtypeOf(superType = actualType, subType = expectType)
}
}
private fun ExpectActualMatchingContext<*>.getClassScopesIncompatibility(
expectClassSymbol: RegularClassSymbolMarker,
actualClassSymbol: RegularClassSymbolMarker,
substitutor: TypeSubstitutorMarker,
languageVersionSettings: LanguageVersionSettings,
): ExpectActualCheckingCompatibility.Incompatible<*>? {
val mismatchedMembers =
arrayListOf>>>()
val incompatibleMembers =
arrayListOf, List>>>()
val actualMembersByName = actualClassSymbol.collectAllMembers(isActualDeclaration = true).groupBy { nameOf(it) }
val expectMembers = expectClassSymbol.collectAllMembers(isActualDeclaration = false)
// private expect constructors are yet allowed KT-68688
.filterNot { it is CallableSymbolMarker && it !is ConstructorSymbolMarker && it.visibility == Visibilities.Private }
for (expectMember in expectMembers) {
val actualMembers = getPossibleActualsByExpectName(expectMember, actualMembersByName)
val matched = matchSingleExpectAgainstPotentialActuals(
expectMember,
actualMembers,
substitutor,
expectClassSymbol,
actualClassSymbol,
mismatchedMembers,
)
matched.forEach {
checkSingleExpectAgainstMatchedActual(
expectMember,
it,
substitutor,
expectClassSymbol,
actualClassSymbol,
incompatibleMembers,
languageVersionSettings,
)
}
}
if (expectClassSymbol.classKind == ClassKind.ENUM_CLASS) {
val aEntries = expectClassSymbol.collectEnumEntryNames()
val bEntries = actualClassSymbol.collectEnumEntryNames()
if (!bEntries.containsAll(aEntries)) return ExpectActualCheckingCompatibility.EnumEntries
}
// TODO: check static scope?
return when (mismatchedMembers.isNotEmpty() || incompatibleMembers.isNotEmpty()) {
true -> ExpectActualCheckingCompatibility.ClassScopes(mismatchedMembers, incompatibleMembers)
false -> null
}
}
private fun ExpectActualMatchingContext<*>.checkSingleExpectAgainstMatchedActual(
expectMember: DeclarationSymbolMarker,
actualMember: DeclarationSymbolMarker,
substitutor: TypeSubstitutorMarker?,
expectClassSymbol: RegularClassSymbolMarker?,
actualClassSymbol: RegularClassSymbolMarker?,
incompatibleMembers: MutableList, List>>>?,
languageVersionSettings: LanguageVersionSettings,
) {
val compatibility = when (expectMember) {
is CallableSymbolMarker -> getCallablesCompatibility(
expectMember,
actualMember as CallableSymbolMarker,
substitutor,
expectClassSymbol,
actualClassSymbol,
languageVersionSettings,
)
is RegularClassSymbolMarker -> {
val parentSubstitutor = substitutor?.takeIf { !innerClassesCapturesOuterTypeParameters }
getClassifiersCompatibility(
expectMember,
actualMember as ClassLikeSymbolMarker,
parentSubstitutor,
languageVersionSettings,
)
}
else -> error("Unsupported declaration: $expectMember ($actualMember)")
}
val incompatibilityMap = mutableMapOf, MutableList>()
when (compatibility) {
ExpectActualCheckingCompatibility.Compatible -> return
is ExpectActualCheckingCompatibility.Incompatible<*> -> incompatibilityMap.getOrPut(compatibility) { SmartList() }.add(actualMember)
}
incompatibleMembers?.add(expectMember to incompatibilityMap)
onIncompatibleMembersFromClassScope(expectMember, incompatibilityMap, expectClassSymbol, actualClassSymbol)
}
private fun ExpectActualMatchingContext<*>.getCallablesCompatibility(
expectDeclaration: CallableSymbolMarker,
actualDeclaration: CallableSymbolMarker,
parentSubstitutor: TypeSubstitutorMarker?,
expectContainingClass: RegularClassSymbolMarker?,
actualContainingClass: RegularClassSymbolMarker?,
languageVersionSettings: LanguageVersionSettings,
): ExpectActualCheckingCompatibility<*> {
checkCallablesInvariants(expectDeclaration, actualDeclaration)
if (areEnumConstructors(expectDeclaration, actualDeclaration, expectContainingClass, actualContainingClass)) {
return ExpectActualCheckingCompatibility.Compatible
}
val insideAnnotationClass = expectContainingClass?.classKind == ClassKind.ANNOTATION_CLASS
val expectedTypeParameters = expectDeclaration.typeParameters
val actualTypeParameters = actualDeclaration.typeParameters
val expectedValueParameters = expectDeclaration.valueParameters
val actualValueParameters = actualDeclaration.valueParameters
val substitutor = createExpectActualTypeParameterSubstitutor(
(expectedTypeParameters zipIfSizesAreEqual actualTypeParameters)
?: error("expect/actual type parameters sizes are checked earlier"),
parentSubstitutor
)
if (!areCompatibleExpectActualTypes(
substitutor.safeSubstitute(expectDeclaration.returnType),
actualDeclaration.returnType,
parameterOfAnnotationComparisonMode = insideAnnotationClass,
dynamicTypesEqualToAnything = false
)
) {
return ExpectActualCheckingCompatibility.ReturnType
}
if (
actualDeclaration.hasStableParameterNames &&
expectDeclaration.hasStableParameterNames &&
!equalsBy(expectedValueParameters, actualValueParameters) { nameOf(it) }
) {
return ExpectActualCheckingCompatibility.ParameterNames
}
if (!equalsBy(expectedTypeParameters, actualTypeParameters) { nameOf(it) }) {
return ExpectActualCheckingCompatibility.TypeParameterNames
}
val expectModality = expectDeclaration.modality
val actualModality = actualDeclaration.modality
if (
!areCompatibleModalities(
expectModality,
actualModality,
expectContainingClass?.modality,
actualContainingClass?.modality
)
) {
return ExpectActualCheckingCompatibility.Modality
}
if (!areCompatibleCallableVisibilities(
expectDeclaration.visibility,
expectModality,
expectContainingClass?.modality,
actualDeclaration.visibility,
languageVersionSettings
)
) {
return ExpectActualCheckingCompatibility.Visibility
}
getTypeParametersVarianceOrReifiedIncompatibility(expectedTypeParameters, actualTypeParameters)?.let { return it }
if (languageVersionSettings.supportsFeature(LanguageFeature.ProhibitDefaultArgumentsInExpectActualizedByFakeOverride) &&
// If expect declaration is a fake-override then default params came from common
// supertypes of actual class and expect class. It's a valid code.
!expectDeclaration.isFakeOverride(expectContainingClass) &&
(actualDeclaration.isFakeOverride(actualContainingClass) || actualDeclaration.isDelegatedMember) &&
expectDeclaration.valueParameters.any { it.hasDefaultValueNonRecursive }
) {
return ExpectActualCheckingCompatibility.DefaultArgumentsInExpectActualizedByFakeOverride
}
if (shouldCheckDefaultParams &&
// "parameters" checks are required only for functions, because only functions can have parameters
actualDeclaration is FunctionSymbolMarker && expectDeclaration is FunctionSymbolMarker &&
// Actual annotation constructors can have default argument values; their consistency with arguments in the expected annotation
// is checked in ExpectedActualDeclarationChecker.checkAnnotationConstructors
!actualDeclaration.isAnnotationConstructor()
) {
val expectOverriddenDeclarations =
expectDeclaration.allRecursivelyOverriddenDeclarationsIncludingSelf(expectContainingClass).toSet()
val actualOverriddenDeclarations =
actualDeclaration.allRecursivelyOverriddenDeclarationsIncludingSelf(actualContainingClass)
// If default params came from common supertypes of actual class and expect class then it's a valid code.
// Here we filter out such default params.
if ((actualOverriddenDeclarations - expectOverriddenDeclarations).flatMap { it.valueParameters }.any { it.hasDefaultValue }) {
return ExpectActualCheckingCompatibility.ActualFunctionWithDefaultParameters
}
}
if (!equalsBy(expectedValueParameters, actualValueParameters) { it.isVararg }) {
return ExpectActualCheckingCompatibility.ValueParameterVararg
}
// Adding noinline/crossinline to parameters is disallowed, except if the expected declaration was not inline at all
if (expectDeclaration is SimpleFunctionSymbolMarker && expectDeclaration.isInline) {
if (expectedValueParameters.indices.any { i -> !expectedValueParameters[i].isNoinline && actualValueParameters[i].isNoinline }) {
return ExpectActualCheckingCompatibility.ValueParameterNoinline
}
if (expectedValueParameters.indices.any { i -> !expectedValueParameters[i].isCrossinline && actualValueParameters[i].isCrossinline }) {
return ExpectActualCheckingCompatibility.ValueParameterCrossinline
}
}
when {
expectDeclaration is FunctionSymbolMarker && actualDeclaration is FunctionSymbolMarker ->
getFunctionsIncompatibility(expectDeclaration, actualDeclaration)?.let { return it }
expectDeclaration is PropertySymbolMarker && actualDeclaration is PropertySymbolMarker ->
getPropertiesIncompatibility(expectDeclaration, actualDeclaration, expectContainingClass, languageVersionSettings)?.let { return it }
expectDeclaration is EnumEntrySymbolMarker && actualDeclaration is EnumEntrySymbolMarker -> {
// do nothing, entries are matched only by name
}
actualDeclaration.isJavaField && expectDeclaration.canBeActualizedByJavaField -> {
// no specific checks, actualization by Java field is permitted in a limited well-known number of cases
}
else -> error("Unsupported declarations: $expectDeclaration, $actualDeclaration")
}
return ExpectActualCheckingCompatibility.Compatible
}
private fun ExpectActualMatchingContext<*>.areCompatibleClassKinds(
expectClass: RegularClassSymbolMarker,
actualClass: RegularClassSymbolMarker,
): Boolean {
if (expectClass.classKind == actualClass.classKind) return true
if (expectClass.classKind == ClassKind.CLASS && isFinal(expectClass) && isCtorless(expectClass)) {
if (actualClass.classKind == ClassKind.OBJECT) return true
}
return false
}
private fun areCompatibleModalities(
expectModality: Modality?,
actualModality: Modality?,
expectContainingClassModality: Modality? = null,
actualContainingClassModality: Modality? = null,
): Boolean {
val expectEffectiveModality = effectiveModality(expectModality, expectContainingClassModality)
val actualEffectiveModality = effectiveModality(actualModality, actualContainingClassModality)
return actualEffectiveModality in compatibleModalityMap.getValue(expectEffectiveModality)
}
/*
* If containing class is final then all declarations in it effectively final
*/
private fun effectiveModality(declarationModality: Modality?, containingClassModality: Modality?): Modality? {
return when (containingClassModality) {
Modality.FINAL -> Modality.FINAL
else -> declarationModality
}
}
/*
* Key is expect modality, value is a set of compatible actual modalities
*/
private val compatibleModalityMap: EnumMap> = enumMapOf(
Modality.ABSTRACT to enumSetOf(Modality.ABSTRACT),
Modality.OPEN to enumSetOf(Modality.OPEN),
Modality.FINAL to enumSetOf(Modality.OPEN, Modality.FINAL),
Modality.SEALED to enumSetOf(Modality.SEALED),
)
private fun areCompatibleCallableVisibilities(
expectVisibility: Visibility,
expectModality: Modality?,
expectContainingClassModality: Modality?,
actualVisibility: Visibility,
languageVersionSettings: LanguageVersionSettings
): Boolean {
// In the case of actualization by a Java declaration such as a field or a method normalize the Java visibility
// to the closest Kotlin visibility.Example: "protected_and_package" -> "protected".
val normalizedActualVisibility = actualVisibility.normalize()
val compare = Visibilities.compare(expectVisibility, normalizedActualVisibility)
val effectiveModality =
when (languageVersionSettings.supportsFeature(LanguageFeature.SupportEffectivelyFinalInExpectActualVisibilityCheck)) {
true -> effectiveModality(expectModality, expectContainingClassModality)
false -> expectModality
}
return if (effectiveModality != Modality.FINAL) {
// For overridable declarations visibility should match precisely, see KT-19664
compare == 0
} else {
// For non-overridable declarations actuals are allowed to have more permissive visibility
compare != null && compare <= 0
}
}
private fun ExpectActualMatchingContext<*>.areCompatibleClassVisibilities(
expectClassSymbol: RegularClassSymbolMarker,
actualClassSymbol: RegularClassSymbolMarker,
): Boolean {
val expectVisibility = expectClassSymbol.visibility
val actualVisibility = actualClassSymbol.visibility
if (expectVisibility == actualVisibility) return true
val result = Visibilities.compare(actualVisibility, expectVisibility)
return result != null && result > 0
}
private fun ExpectActualMatchingContext<*>.getTypeParametersVarianceOrReifiedIncompatibility(
expectTypeParameterSymbols: List,
actualTypeParameterSymbols: List,
): ExpectActualCheckingCompatibility.Incompatible<*>? {
if (!equalsBy(expectTypeParameterSymbols, actualTypeParameterSymbols) { it.variance }) {
return ExpectActualCheckingCompatibility.TypeParameterVariance
}
// Removing "reified" from an expected function's type parameter is fine
if (
expectTypeParameterSymbols.indices.any { i ->
!expectTypeParameterSymbols[i].isReified && actualTypeParameterSymbols[i].isReified
}
) {
return ExpectActualCheckingCompatibility.TypeParameterReified
}
return null
}
private fun ExpectActualMatchingContext<*>.getFunctionsIncompatibility(
expectFunction: CallableSymbolMarker,
actualFunction: CallableSymbolMarker,
): ExpectActualCheckingCompatibility.Incompatible<*>? {
if (!equalBy(expectFunction, actualFunction) { f -> f.isSuspend }) {
return ExpectActualCheckingCompatibility.FunctionModifiersDifferent
}
if (
expectFunction.isInfix && !actualFunction.isInfix ||
expectFunction.isInline && !actualFunction.isInline ||
expectFunction.isOperator && !actualFunction.isOperator
) {
return ExpectActualCheckingCompatibility.FunctionModifiersNotSubset
}
return null
}
private fun ExpectActualMatchingContext<*>.getPropertiesIncompatibility(
expected: PropertySymbolMarker,
actual: PropertySymbolMarker,
expectContainingClass: RegularClassSymbolMarker?,
languageVersionSettings: LanguageVersionSettings,
): ExpectActualCheckingCompatibility.Incompatible<*>? {
return when {
!equalBy(expected, actual) { p -> p.isVar } -> ExpectActualCheckingCompatibility.PropertyKind
!equalBy(expected, actual) { p -> p.isLateinit } -> ExpectActualCheckingCompatibility.PropertyLateinitModifier
expected.isConst && !actual.isConst -> ExpectActualCheckingCompatibility.PropertyConstModifier
!arePropertySettersWithCompatibleVisibilities(expected, actual, expectContainingClass, languageVersionSettings) ->
ExpectActualCheckingCompatibility.PropertySetterVisibility
else -> null
}
}
private fun ExpectActualMatchingContext<*>.arePropertySettersWithCompatibleVisibilities(
expected: PropertySymbolMarker,
actual: PropertySymbolMarker,
expectContainingClass: RegularClassSymbolMarker?,
languageVersionSettings: LanguageVersionSettings,
): Boolean {
val expectedSetter = expected.setter ?: return true
val actualSetter = actual.setter ?: return true
return areCompatibleCallableVisibilities(
expectedSetter.visibility,
expectedSetter.modality,
expectContainingClass?.modality,
actualSetter.visibility,
languageVersionSettings,
)
}
// ---------------------------------------- Utils ----------------------------------------
private inline fun equalsBy(first: List, second: List, selector: (T) -> K): Boolean {
for (i in first.indices) {
if (selector(first[i]) != selector(second[i])) return false
}
return true
}
private inline fun equalBy(first: T, second: T, selector: (T) -> K): Boolean =
selector(first) == selector(second)
private fun ExpectActualMatchingContext<*>.isCtorless(regularClass: RegularClassSymbolMarker): Boolean {
return regularClass.getMembersForExpectClass(SpecialNames.INIT).isEmpty()
}
private fun ExpectActualMatchingContext<*>.isFinal(regularClassSymbolMarker: RegularClassSymbolMarker): Boolean {
return regularClassSymbolMarker.modality == Modality.FINAL
}
}