K2/MPP: treat Array<Some> & Array<out Some> as similar types in annotations

#KT-59561 Fixed
Related to KT-61100
This commit is contained in:
Mikhail Glukhikh
2023-10-12 08:44:57 +02:00
committed by Space Team
parent f085d0f660
commit 9f6abfc166
5 changed files with 74 additions and 13 deletions
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.symbols.resolvedAnnotationsWithArguments
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
import org.jetbrains.kotlin.mpp.*
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId
@@ -305,10 +306,21 @@ class FirExpectActualMatchingContextImpl private constructor(
override fun areCompatibleExpectActualTypes(
expectType: KotlinTypeMarker?,
actualType: KotlinTypeMarker?,
parameterOfAnnotationComparisonMode: Boolean,
): Boolean {
if (expectType == null) return actualType == null
if (actualType == null) return false
if (parameterOfAnnotationComparisonMode && expectType is ConeClassLikeType && expectType.isArrayType &&
actualType is ConeClassLikeType && actualType.isArrayType
) {
return AbstractTypeChecker.equalTypes(
createTypeCheckerState(),
expectType.convertToArrayWithOutProjections(),
actualType.convertToArrayWithOutProjections()
)
}
return AbstractTypeChecker.equalTypes(
createTypeCheckerState(),
expectType,
@@ -316,6 +328,15 @@ class FirExpectActualMatchingContextImpl private constructor(
)
}
private fun ConeClassLikeType.convertToArrayWithOutProjections(): ConeClassLikeType {
val argumentsWithOutProjection = Array(typeArguments.size) { i ->
val typeArgument = typeArguments[i]
if (typeArgument !is ConeKotlinType) typeArgument
else ConeKotlinTypeProjectionOut(typeArgument)
}
return ConeClassLikeTypeImpl(lookupTag, argumentsWithOutProjection, isNullable)
}
override fun actualTypeIsSubtypeOfExpectType(expectType: KotlinTypeMarker, actualType: KotlinTypeMarker): Boolean {
return AbstractTypeChecker.isSubtypeOf(
createTypeCheckerState(),
@@ -370,7 +391,10 @@ class FirExpectActualMatchingContextImpl private constructor(
check(annotation1.hasResolvedArguments() && annotation2.hasResolvedArguments()) {
"By this time compared annotations are expected to have resolved arguments"
}
if (!areCompatibleExpectActualTypes(annotation1.resolvedType, annotation2.resolvedType)) {
if (!areCompatibleExpectActualTypes(
annotation1.resolvedType, annotation2.resolvedType, parameterOfAnnotationComparisonMode = false
)
) {
return false
}
val args1 = annotation1.argumentMapping.mapping
@@ -334,7 +334,11 @@ internal abstract class IrExpectActualMatchingContext(
override val TypeParameterSymbolMarker.isReified: Boolean
get() = asIr().isReified
override fun areCompatibleExpectActualTypes(expectType: KotlinTypeMarker?, actualType: KotlinTypeMarker?): Boolean {
override fun areCompatibleExpectActualTypes(
expectType: KotlinTypeMarker?,
actualType: KotlinTypeMarker?,
parameterOfAnnotationComparisonMode: Boolean,
): Boolean {
if (expectType == null) return actualType == null
if (actualType == null) return false
/*
@@ -355,6 +359,17 @@ internal abstract class IrExpectActualMatchingContext(
*/
val actualizedExpectType = expectType.actualize()
val actualizedActualType = actualType.actualize()
if (parameterOfAnnotationComparisonMode && actualizedExpectType is IrSimpleType && actualizedExpectType.isArray() &&
actualizedActualType is IrSimpleType && actualizedActualType.isArray()
) {
return AbstractTypeChecker.equalTypes(
createTypeCheckerState(),
actualizedExpectType.convertToArrayWithOutProjections(),
actualizedActualType.convertToArrayWithOutProjections()
)
}
return AbstractTypeChecker.equalTypes(
createTypeCheckerState(),
actualizedExpectType,
@@ -362,6 +377,15 @@ internal abstract class IrExpectActualMatchingContext(
)
}
private fun IrSimpleType.convertToArrayWithOutProjections(): IrSimpleType {
val argumentsWithOutProjection = List(arguments.size) { i ->
val typeArgument = arguments[i]
if (typeArgument !is IrSimpleType) typeArgument
else makeTypeProjection(typeArgument, Variance.OUT_VARIANCE)
}
return IrSimpleTypeImpl(classifier, isNullable(), argumentsWithOutProjection, annotations)
}
private fun createTypeCheckerState(): TypeCheckerState {
return typeContext.newTypeCheckerState(errorTypesEqualToAnything = true, stubTypesEqualToAnything = false)
}
@@ -179,7 +179,7 @@ object AbstractExpectActualCompatibilityChecker {
return expectSupertypes.all { expectSupertype ->
val substitutedExpectType = substitutor.safeSubstitute(expectSupertype)
actualSupertypes.any { actualSupertype ->
areCompatibleExpectActualTypes(substitutedExpectType, actualSupertype)
areCompatibleExpectActualTypes(substitutedExpectType, actualSupertype, parameterOfAnnotationComparisonMode = false)
}
}
}
@@ -321,7 +321,8 @@ object AbstractExpectActualCompatibilityChecker {
}
// We must prioritize to return STRONG incompatible over WEAK incompatible (because STRONG incompatibility allows to search for overloads)
return getCallablesStrongIncompatibility(expectDeclaration, actualDeclaration, parentSubstitutor)
val annotationMode = expectContainingClass?.classKind == ClassKind.ANNOTATION_CLASS
return getCallablesStrongIncompatibility(expectDeclaration, actualDeclaration, annotationMode, parentSubstitutor)
?: getCallablesWeakIncompatibility(expectDeclaration, actualDeclaration, expectContainingClass, actualContainingClass)
?: ExpectActualCompatibility.Compatible
}
@@ -330,6 +331,7 @@ object AbstractExpectActualCompatibilityChecker {
private fun getCallablesStrongIncompatibility(
expectDeclaration: CallableSymbolMarker,
actualDeclaration: CallableSymbolMarker,
insideAnnotationClass: Boolean,
parentSubstitutor: TypeSubstitutorMarker?,
): Incompatible.StrongIncompatible<*>? {
if (expectDeclaration is FunctionSymbolMarker != actualDeclaration is FunctionSymbolMarker) {
@@ -363,18 +365,24 @@ object AbstractExpectActualCompatibilityChecker {
if (
!areCompatibleTypeLists(
expectedValueParameters.toTypeList(substitutor),
actualValueParameters.toTypeList(createEmptySubstitutor())
) ||
!areCompatibleExpectActualTypes(
actualValueParameters.toTypeList(createEmptySubstitutor()),
insideAnnotationClass
) || !areCompatibleExpectActualTypes(
expectedReceiverType?.let { substitutor.safeSubstitute(it) },
actualReceiverType
actualReceiverType,
parameterOfAnnotationComparisonMode = false
)
) {
return Incompatible.ParameterTypes
}
if (shouldCheckReturnTypesOfCallables) {
if (!areCompatibleExpectActualTypes(substitutor.safeSubstitute(expectDeclaration.returnType), actualDeclaration.returnType)) {
if (!areCompatibleExpectActualTypes(
substitutor.safeSubstitute(expectDeclaration.returnType),
actualDeclaration.returnType,
parameterOfAnnotationComparisonMode = insideAnnotationClass
)
) {
return Incompatible.ReturnType
}
}
@@ -492,9 +500,13 @@ object AbstractExpectActualCompatibilityChecker {
private fun areCompatibleTypeLists(
expectedTypes: List<KotlinTypeMarker?>,
actualTypes: List<KotlinTypeMarker?>,
insideAnnotationClass: Boolean,
): Boolean {
for (i in expectedTypes.indices) {
if (!areCompatibleExpectActualTypes(expectedTypes[i], actualTypes[i])) {
if (!areCompatibleExpectActualTypes(
expectedTypes[i], actualTypes[i], parameterOfAnnotationComparisonMode = insideAnnotationClass
)
) {
return false
}
}
@@ -586,7 +598,7 @@ object AbstractExpectActualCompatibilityChecker {
val actualBounds = actualTypeParameterSymbols[i].bounds
if (
expectBounds.size != actualBounds.size ||
!areCompatibleTypeLists(expectBounds.map { substitutor.safeSubstitute(it) }, actualBounds)
!areCompatibleTypeLists(expectBounds.map { substitutor.safeSubstitute(it) }, actualBounds, insideAnnotationClass = false)
) {
return false
}
@@ -145,6 +145,7 @@ interface ExpectActualMatchingContext<T : DeclarationSymbolMarker> : TypeSystemC
fun areCompatibleExpectActualTypes(
expectType: KotlinTypeMarker?,
actualType: KotlinTypeMarker?,
parameterOfAnnotationComparisonMode: Boolean = false,
): Boolean
fun actualTypeIsSubtypeOfExpectType(
@@ -7,7 +7,7 @@
Throughput, AverageTime
}<!>
<!INCOMPATIBLE_MATCHING{JVM}!>expect annotation class BenchmarkMode<!INCOMPATIBLE_MATCHING{JVM}!>(<!INCOMPATIBLE_MATCHING{JVM}!>vararg val value: Mode<!>)<!><!>
expect annotation class BenchmarkMode(vararg val value: Mode)
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
@@ -15,7 +15,7 @@
actual typealias <!NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS("actual typealias Mode = Mode; expect constructor(): Mode")!>Mode<!> = mypackage.Mode
actual typealias <!NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS("actual typealias BenchmarkMode = BenchmarkMode; expect constructor(vararg value: Array<out Mode>): BenchmarkMode The following declaration is incompatible because parameter types are different: constructor(vararg value: Array<Mode>): BenchmarkMode")!>BenchmarkMode<!> = mypackage.BenchmarkMode
actual typealias BenchmarkMode = mypackage.BenchmarkMode
// FILE: mypackage/Mode.java