From 9f6abfc16684b58248c7f992003f7172f6813607 Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Thu, 12 Oct 2023 08:44:57 +0200 Subject: [PATCH] K2/MPP: treat Array & Array as similar types in annotations #KT-59561 Fixed Related to KT-61100 --- .../mpp/FirExpectActualMatchingContextImpl.kt | 26 +++++++++++++++- .../IrExpectActualMatchingContext.kt | 26 +++++++++++++++- ...bstractExpectActualCompatibilityChecker.kt | 30 +++++++++++++------ .../calls/mpp/ExpectActualMatchingContext.kt | 1 + .../typeAliasWithArray.fir.kt | 4 +-- 5 files changed, 74 insertions(+), 13 deletions(-) diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/mpp/FirExpectActualMatchingContextImpl.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/mpp/FirExpectActualMatchingContextImpl.kt index 56c4c5e6eff..f3c99a5bb1c 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/mpp/FirExpectActualMatchingContextImpl.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/mpp/FirExpectActualMatchingContextImpl.kt @@ -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 diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/actualizer/IrExpectActualMatchingContext.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/actualizer/IrExpectActualMatchingContext.kt index bd7103b43e9..0c5e4da9efc 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/actualizer/IrExpectActualMatchingContext.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/actualizer/IrExpectActualMatchingContext.kt @@ -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) } diff --git a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualCompatibilityChecker.kt b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualCompatibilityChecker.kt index 7b53b3a4d9e..18e65686a5e 100644 --- a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualCompatibilityChecker.kt +++ b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualCompatibilityChecker.kt @@ -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, actualTypes: List, + 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 } diff --git a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/ExpectActualMatchingContext.kt b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/ExpectActualMatchingContext.kt index 24da91a390c..5c3d7e5d61d 100644 --- a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/ExpectActualMatchingContext.kt +++ b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/ExpectActualMatchingContext.kt @@ -145,6 +145,7 @@ interface ExpectActualMatchingContext : TypeSystemC fun areCompatibleExpectActualTypes( expectType: KotlinTypeMarker?, actualType: KotlinTypeMarker?, + parameterOfAnnotationComparisonMode: Boolean = false, ): Boolean fun actualTypeIsSubtypeOfExpectType( diff --git a/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeAliasWithArray.fir.kt b/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeAliasWithArray.fir.kt index 677ba840c39..e01b0456fbb 100644 --- a/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeAliasWithArray.fir.kt +++ b/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeAliasWithArray.fir.kt @@ -7,7 +7,7 @@ Throughput, AverageTime } -expect annotation class BenchmarkMode(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 Mode = mypackage.Mode -actual typealias ): BenchmarkMode The following declaration is incompatible because parameter types are different: constructor(vararg value: Array): BenchmarkMode")!>BenchmarkMode = mypackage.BenchmarkMode +actual typealias BenchmarkMode = mypackage.BenchmarkMode // FILE: mypackage/Mode.java