diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendMPPDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendMPPDiagnosticsWithLightTreeTestGenerated.java index 236bf9384ad..bf7a1dbdd97 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendMPPDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendMPPDiagnosticsWithLightTreeTestGenerated.java @@ -932,6 +932,12 @@ public class FirOldFrontendMPPDiagnosticsWithLightTreeTestGenerated extends Abst runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeUsage.kt"); } + @Test + @TestMetadata("typeUsageTypealiasInSuper.kt") + public void testTypeUsageTypealiasInSuper() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeUsageTypealiasInSuper.kt"); + } + @Test @TestMetadata("typeUsageWithImplicitType.kt") public void testTypeUsageWithImplicitType() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendMPPDiagnosticsWithPsiTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendMPPDiagnosticsWithPsiTestGenerated.java index 1d26dffe897..9f8eb73d032 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendMPPDiagnosticsWithPsiTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendMPPDiagnosticsWithPsiTestGenerated.java @@ -932,6 +932,12 @@ public class FirOldFrontendMPPDiagnosticsWithPsiTestGenerated extends AbstractFi runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeUsage.kt"); } + @Test + @TestMetadata("typeUsageTypealiasInSuper.kt") + public void testTypeUsageTypealiasInSuper() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeUsageTypealiasInSuper.kt"); + } + @Test @TestMetadata("typeUsageWithImplicitType.kt") public void testTypeUsageWithImplicitType() throws Exception { 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 98bce662b8d..6f92dbcd9c9 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 @@ -9,7 +9,6 @@ import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.Visibility import org.jetbrains.kotlin.fir.* -import org.jetbrains.kotlin.fir.declarations.getRetention import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.utils.isActual import org.jetbrains.kotlin.fir.expressions.FirAnnotation @@ -26,6 +25,7 @@ import org.jetbrains.kotlin.name.CallableId import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.calls.mpp.ExpectActualCollectionArgumentsCompatibilityCheckStrategy +import org.jetbrains.kotlin.resolve.calls.mpp.ExpectActualMatchingContext import org.jetbrains.kotlin.resolve.calls.mpp.ExpectActualMatchingContext.AnnotationCallInfo import org.jetbrains.kotlin.resolve.checkers.OptInNames import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility @@ -36,6 +36,7 @@ import org.jetbrains.kotlin.types.model.KotlinTypeMarker import org.jetbrains.kotlin.types.model.SimpleTypeMarker import org.jetbrains.kotlin.types.model.TypeSubstitutorMarker import org.jetbrains.kotlin.types.model.TypeSystemContext +import org.jetbrains.kotlin.utils.zipIfSizesAreEqual class FirExpectActualMatchingContextImpl private constructor( private val actualSession: FirSession, @@ -160,6 +161,9 @@ class FirExpectActualMatchingContextImpl private constructor( override val RegularClassSymbolMarker.superTypes: List get() = asSymbol().resolvedSuperTypes + override val RegularClassSymbolMarker.superTypesRefs: List + get() = asSymbol().resolvedSuperTypeRefs + override val RegularClassSymbolMarker.defaultType: KotlinTypeMarker get() = asSymbol().defaultType() @@ -245,8 +249,12 @@ class FirExpectActualMatchingContextImpl private constructor( get() = asSymbol().dispatchReceiverType override val CallableSymbolMarker.extensionReceiverType: KotlinTypeMarker? get() = asSymbol().resolvedReceiverTypeRef?.coneType + override val CallableSymbolMarker.extensionReceiverTypeRef: TypeRefMarker? + get() = asSymbol().resolvedReceiverTypeRef override val CallableSymbolMarker.returnType: KotlinTypeMarker get() = asSymbol().resolvedReturnType.type + override val CallableSymbolMarker.returnTypeRef: TypeRefMarker + get() = asSymbol().resolvedReturnTypeRef override val CallableSymbolMarker.typeParameters: List get() = asSymbol().typeParameterSymbols @@ -286,6 +294,9 @@ class FirExpectActualMatchingContextImpl private constructor( override val TypeParameterSymbolMarker.bounds: List get() = asSymbol().resolvedBounds.map { it.coneType } + override val TypeParameterSymbolMarker.boundsTypeRefs: List + get() = asSymbol().resolvedBounds + override val TypeParameterSymbolMarker.variance: Variance get() = asSymbol().variance @@ -489,6 +500,65 @@ class FirExpectActualMatchingContextImpl private constructor( override fun DeclarationSymbolMarker.getSourceElement(): SourceElementMarker = FirSourceElement(asSymbol().source) + override fun TypeRefMarker.getClassId(): ClassId? = (this as FirResolvedTypeRef).type.fullyExpandedType(actualSession).classId + + override fun checkAnnotationsOnTypeRefAndArguments( + expectTypeRef: TypeRefMarker, + actualTypeRef: TypeRefMarker, + checker: ExpectActualMatchingContext.AnnotationsCheckerCallback, + ) { + check(expectTypeRef is FirResolvedTypeRef && actualTypeRef is FirResolvedTypeRef) + checkAnnotationsOnTypeRefAndArgumentsImpl(expectTypeRef, actualTypeRef, checker) + } + + private fun checkAnnotationsOnTypeRefAndArgumentsImpl( + expectTypeRef: FirTypeRef?, + actualTypeRef: FirTypeRef?, + checker: ExpectActualMatchingContext.AnnotationsCheckerCallback, + ) { + fun FirAnnotationContainer.getAnnotations() = annotations.map(::AnnotationCallInfoImpl) + + if (expectTypeRef == null || actualTypeRef == null) return + if (expectTypeRef is FirErrorTypeRef || actualTypeRef is FirErrorTypeRef) return + + checker.check(expectTypeRef.getAnnotations(), actualTypeRef.getAnnotations(), FirSourceElement(actualTypeRef.source)) + + val expectDelegatedTypeRef = (expectTypeRef as? FirResolvedTypeRef)?.delegatedTypeRef ?: return + val actualDelegatedTypeRef = (actualTypeRef as? FirResolvedTypeRef?)?.delegatedTypeRef ?: return + + when { + expectDelegatedTypeRef is FirUserTypeRef && actualDelegatedTypeRef is FirUserTypeRef -> { + val expectQualifier = expectDelegatedTypeRef.qualifier + val actualQualifier = actualDelegatedTypeRef.qualifier + for ((expectPart, actualPart) in expectQualifier.zipIfSizesAreEqual(actualQualifier).orEmpty()) { + val expectPartTypeArguments = expectPart.typeArgumentList.typeArguments + val actualPartTypeArguments = actualPart.typeArgumentList.typeArguments + val zippedArgs = expectPartTypeArguments.zipIfSizesAreEqual(actualPartTypeArguments).orEmpty() + for ((expectTypeArgument, actualTypeArgument) in zippedArgs) { + if (expectTypeArgument !is FirTypeProjectionWithVariance || actualTypeArgument !is FirTypeProjectionWithVariance) { + continue + } + checkAnnotationsOnTypeRefAndArgumentsImpl(expectTypeArgument.typeRef, actualTypeArgument.typeRef, checker) + } + } + } + expectDelegatedTypeRef is FirFunctionTypeRef && actualDelegatedTypeRef is FirFunctionTypeRef -> { + checkAnnotationsOnTypeRefAndArgumentsImpl( + expectDelegatedTypeRef.receiverTypeRef, actualDelegatedTypeRef.receiverTypeRef, checker, + ) + checkAnnotationsOnTypeRefAndArgumentsImpl( + expectDelegatedTypeRef.returnTypeRef, actualDelegatedTypeRef.returnTypeRef, checker, + ) + + val expectParams = expectDelegatedTypeRef.parameters + val actualParams = actualDelegatedTypeRef.parameters + for ((expectParam, actualParam) in expectParams.zipIfSizesAreEqual(actualParams).orEmpty()) { + checkAnnotationsOnTypeRefAndArgumentsImpl(expectParam.returnTypeRef, actualParam.returnTypeRef, checker) + } + } + } + } + object Factory : FirExpectActualMatchingContextFactory { override fun create( session: FirSession, scopeSession: ScopeSession, diff --git a/compiler/fir/tree/gen/org/jetbrains/kotlin/fir/types/FirResolvedTypeRef.kt b/compiler/fir/tree/gen/org/jetbrains/kotlin/fir/types/FirResolvedTypeRef.kt index abe391e2e66..1813753c963 100644 --- a/compiler/fir/tree/gen/org/jetbrains/kotlin/fir/types/FirResolvedTypeRef.kt +++ b/compiler/fir/tree/gen/org/jetbrains/kotlin/fir/types/FirResolvedTypeRef.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.fir.types import org.jetbrains.kotlin.KtSourceElement import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.expressions.FirAnnotation +import org.jetbrains.kotlin.mpp.TypeRefMarker import org.jetbrains.kotlin.fir.visitors.* /* @@ -15,7 +16,7 @@ import org.jetbrains.kotlin.fir.visitors.* * DO NOT MODIFY IT MANUALLY */ -abstract class FirResolvedTypeRef : FirTypeRef() { +abstract class FirResolvedTypeRef : FirTypeRef(), TypeRefMarker { abstract override val source: KtSourceElement? abstract override val annotations: List abstract val type: ConeKotlinType diff --git a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/NodeConfigurator.kt b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/NodeConfigurator.kt index 1536a76ed50..a8225540c99 100644 --- a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/NodeConfigurator.kt +++ b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/NodeConfigurator.kt @@ -707,7 +707,7 @@ object NodeConfigurator : AbstractFieldConfigurator(FirTreeBuild resolvedTypeRef.configure { +field("type", coneKotlinTypeType) +field("delegatedTypeRef", typeRef, nullable = true) - // TODO: add supertype in subsequent commits + element.additionalSupertypeInterfaces.add(typeRefMarkerType) } typeRefWithNullability.configure { diff --git a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/Types.kt b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/Types.kt index 1e6c616d87b..919eb5ec228 100644 --- a/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/Types.kt +++ b/compiler/fir/tree/tree-generator/src/org/jetbrains/kotlin/fir/tree/generator/Types.kt @@ -108,3 +108,5 @@ val firPropertySymbolType = type("fir.symbols.impl", "FirPropertySymbol") val errorTypeRefImplType = type("fir.types.impl", "FirErrorTypeRefImpl") val annotationResolvePhaseType = generatedType("expressions", "FirAnnotationResolvePhase") + +val typeRefMarkerType = type("mpp", "TypeRefMarker") 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 24c6c06d033..0ff8188713c 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 @@ -28,7 +28,6 @@ import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.calls.mpp.ExpectActualCollectionArgumentsCompatibilityCheckStrategy import org.jetbrains.kotlin.resolve.calls.mpp.ExpectActualMatchingContext import org.jetbrains.kotlin.resolve.calls.mpp.ExpectActualMatchingContext.AnnotationCallInfo -import org.jetbrains.kotlin.mpp.SourceElementMarker import org.jetbrains.kotlin.resolve.checkers.OptInNames import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility import org.jetbrains.kotlin.types.AbstractTypeChecker @@ -177,9 +176,12 @@ internal abstract class IrExpectActualMatchingContext( else -> shouldNotBeCalled() } - override val RegularClassSymbolMarker.superTypes: List + override val RegularClassSymbolMarker.superTypes: List get() = asIr().superTypes + override val RegularClassSymbolMarker.superTypesRefs: List + get() = superTypes + override val RegularClassSymbolMarker.defaultType: KotlinTypeMarker get() = asIr().defaultType @@ -275,10 +277,13 @@ internal abstract class IrExpectActualMatchingContext( override val CallableSymbolMarker.dispatchReceiverType: KotlinTypeMarker? get() = (asIr().parent as? IrClass)?.defaultType - override val CallableSymbolMarker.extensionReceiverType: KotlinTypeMarker? + override val CallableSymbolMarker.extensionReceiverType: IrType? get() = safeAsIr()?.extensionReceiverParameter?.type - override val CallableSymbolMarker.returnType: KotlinTypeMarker + override val CallableSymbolMarker.extensionReceiverTypeRef: TypeRefMarker? + get() = extensionReceiverType + + override val CallableSymbolMarker.returnType: IrType get() = processIr( onFunction = { it.returnType }, onProperty = { it.getter?.returnType ?: it.backingField?.type ?: error("No type for property: $it") }, @@ -287,6 +292,10 @@ internal abstract class IrExpectActualMatchingContext( onEnumEntry = { it.parentAsClass.defaultType } ) + override val CallableSymbolMarker.returnTypeRef: TypeRefMarker + get() = returnType + + override val CallableSymbolMarker.typeParameters: List get() = processIr( onFunction = { it.typeParameters.map { parameter -> parameter.symbol } }, @@ -316,8 +325,10 @@ internal abstract class IrExpectActualMatchingContext( return irConstructor.constructedClass.isAnnotationClass } - override val TypeParameterSymbolMarker.bounds: List + override val TypeParameterSymbolMarker.bounds: List get() = asIr().superTypes + override val TypeParameterSymbolMarker.boundsTypeRefs: List + get() = bounds override val TypeParameterSymbolMarker.variance: Variance get() = asIr().variance override val TypeParameterSymbolMarker.isReified: Boolean @@ -529,4 +540,27 @@ internal abstract class IrExpectActualMatchingContext( private object IrSourceElementStub : SourceElementMarker override fun DeclarationSymbolMarker.getSourceElement(): SourceElementMarker = IrSourceElementStub + + override fun TypeRefMarker.getClassId(): ClassId? = (this as IrType).getClass()?.classId + + override fun checkAnnotationsOnTypeRefAndArguments( + expectTypeRef: TypeRefMarker, + actualTypeRef: TypeRefMarker, + checker: ExpectActualMatchingContext.AnnotationsCheckerCallback + ) { + check(expectTypeRef is IrType && actualTypeRef is IrType) + + fun IrType.getAnnotations() = annotations.map(::AnnotationCallInfoImpl) + + checker.check(expectTypeRef.getAnnotations(), actualTypeRef.getAnnotations(), IrSourceElementStub) + + if (expectTypeRef !is IrSimpleType || actualTypeRef !is IrSimpleType) return + if (expectTypeRef.arguments.size != actualTypeRef.arguments.size) return + + for ((expectArg, actualArg) in expectTypeRef.arguments.zip(actualTypeRef.arguments)) { + val expectArgType = expectArg.typeOrNull ?: continue + val actualArgType = actualArg.typeOrNull ?: continue + checkAnnotationsOnTypeRefAndArguments(expectArgType, actualArgType, checker) + } + } } diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/types/IrType.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/types/IrType.kt index 89e1443f508..a5d9fc335de 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/types/IrType.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/types/IrType.kt @@ -10,11 +10,12 @@ import org.jetbrains.kotlin.ir.symbols.IrClassSymbol import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol import org.jetbrains.kotlin.ir.symbols.IrTypeAliasSymbol import org.jetbrains.kotlin.ir.types.impl.IrTypeBase +import org.jetbrains.kotlin.mpp.TypeRefMarker import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.Variance import org.jetbrains.kotlin.types.model.* -abstract class IrType : KotlinTypeMarker, IrAnnotationContainer { +abstract class IrType : KotlinTypeMarker, TypeRefMarker, IrAnnotationContainer { /** * @return true if this type is equal to [other] symbolically. Note that this is NOT EQUIVALENT to the full type checking algorithm diff --git a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualAnnotationMatchChecker.kt b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualAnnotationMatchChecker.kt index 30606674d87..18d64bf6f9f 100644 --- a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualAnnotationMatchChecker.kt +++ b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualAnnotationMatchChecker.kt @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.StandardClassIds import org.jetbrains.kotlin.resolve.checkers.OptInNames import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility +import org.jetbrains.kotlin.utils.zipIfSizesAreEqual import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualAnnotationsIncompatibilityType as IncompatibilityType object AbstractExpectActualAnnotationMatchChecker { @@ -84,6 +85,13 @@ object AbstractExpectActualAnnotationMatchChecker { arePropertyGetterAndSetterAnnotationsCompatible(expectSymbol, actualSymbol)?.let { return it } } + areAnnotationsSetOnTypesCompatible( + expectSymbol, actualSymbol, expectSymbol.extensionReceiverTypeRef, actualSymbol.extensionReceiverTypeRef + )?.let { return it } + + areAnnotationsSetOnTypesCompatible(expectSymbol, actualSymbol, expectSymbol.returnTypeRef, actualSymbol.returnTypeRef) + ?.let { return it } + return null } @@ -126,6 +134,16 @@ object AbstractExpectActualAnnotationMatchChecker { checkAnnotationsOnEnumEntries(expectSymbol, actualSymbol)?.let { return it } } + // actual may have more super types, that's why we can't just zip them. + // Identifying super type with ClassId (without type parameters) is enough because same type can't be extended twice. + val actualSuperTypes = actualSymbol.superTypesRefs.groupBy { it.getClassId() } + for (expectSuperType in expectSymbol.superTypesRefs) { + val expectClassId = expectSuperType.getClassId() ?: continue + val actualSuperType = actualSuperTypes[expectClassId]?.singleOrNull() ?: continue + areAnnotationsSetOnTypesCompatible(expectSymbol, actualSymbol, expectSuperType, actualSuperType) + ?.let { return it } + } + return null } @@ -148,13 +166,12 @@ object AbstractExpectActualAnnotationMatchChecker { val expectParams = expectSymbol.valueParameters val actualParams = actualSymbol.valueParameters - if (expectParams.size != actualParams.size) return null - - return expectParams.zip(actualParams).firstNotNullOfOrNull { (expectParam, actualParam) -> + return expectParams.zipIfSizesAreEqual(actualParams)?.firstNotNullOfOrNull { (expectParam, actualParam) -> areAnnotationsSetOnDeclarationsCompatible(expectParam, actualParam)?.let { // Write containing declarations into diagnostic Incompatibility(expectSymbol, actualSymbol, actualParam.getSourceElement(), it.type) } + ?: areAnnotationsSetOnTypesCompatible(expectSymbol, actualSymbol, expectParam.returnTypeRef, actualParam.returnTypeRef) } } @@ -173,16 +190,57 @@ object AbstractExpectActualAnnotationMatchChecker { val expectParams = expectSymbol.getTypeParameters() ?: return null val actualParams = actualSymbol.getTypeParameters() ?: return null - if (expectParams.size != actualParams.size) return null - return expectParams.zip(actualParams).firstNotNullOfOrNull { (expectParam, actualParam) -> + return expectParams.zipIfSizesAreEqual(actualParams)?.firstNotNullOfOrNull { (expectParam, actualParam) -> areAnnotationsSetOnDeclarationsCompatible(expectParam, actualParam)?.let { // Write containing declarations into diagnostic Incompatibility(expectSymbol, actualSymbol, actualParam.getSourceElement(), it.type) } + ?: areAnnotationsOnTypeParameterBoundsCompatible(expectSymbol, actualSymbol, expectParam, actualParam) } } + context (ExpectActualMatchingContext<*>) + private fun areAnnotationsOnTypeParameterBoundsCompatible( + expectDeclarationSymbol: DeclarationSymbolMarker, + actualDeclarationSymbol: DeclarationSymbolMarker, + expectParam: TypeParameterSymbolMarker, + actualParam: TypeParameterSymbolMarker, + ): Incompatibility? { + val expectBounds = expectParam.boundsTypeRefs + val actualBounds = actualParam.boundsTypeRefs + + return expectBounds.zipIfSizesAreEqual(actualBounds)?.firstNotNullOfOrNull { (expectType, actualType) -> + // Identifying bounds by ClassId (without type parameters) is enough because type can't have two bounds of same type. + if (expectType.getClassId() != actualType.getClassId()) { + // Expect and actual type bounds must be same and have same order, otherwise expect-actual compatibility checker + // reports error. So, skip in case of incompatibility to not report extra warning. + return@firstNotNullOfOrNull null + } + areAnnotationsSetOnTypesCompatible(expectDeclarationSymbol, actualDeclarationSymbol, expectType, actualType) + } + } + + context (ExpectActualMatchingContext<*>) + private fun areAnnotationsSetOnTypesCompatible( + expectDeclarationSymbol: DeclarationSymbolMarker, + actualDeclarationSymbol: DeclarationSymbolMarker, + expectTypeRef: TypeRefMarker?, + actualTypeRef: TypeRefMarker?, + ): Incompatibility? { + if (expectTypeRef == null || actualTypeRef == null) return null + + var firstIncompatibility: Incompatibility? = null + + checkAnnotationsOnTypeRefAndArguments(expectTypeRef, actualTypeRef) { expectAnnotations, actualAnnotations, actualTypeRefSource -> + if (firstIncompatibility == null) { + firstIncompatibility = areAnnotationListsCompatible(expectAnnotations, actualAnnotations, actualDeclarationSymbol) + ?.let { Incompatibility(expectDeclarationSymbol, actualDeclarationSymbol, actualTypeRefSource, type = it) } + } + } + return firstIncompatibility + } + context (ExpectActualMatchingContext<*>) private fun areAnnotationsSetOnDeclarationsCompatible( expectSymbol: DeclarationSymbolMarker, @@ -198,7 +256,6 @@ object AbstractExpectActualAnnotationMatchChecker { actualAnnotations: List, actualContainerSymbol: DeclarationSymbolMarker, ): IncompatibilityType? { - // TODO(Roman.Efremov, KT-60671): check annotations set on types val skipSourceAnnotations = actualContainerSymbol.hasSourceAnnotationsErased val actualAnnotationsByName = actualAnnotations.groupBy { it.classId } 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 1b940350504..99859b8369e 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 @@ -85,6 +85,7 @@ interface ExpectActualMatchingContext : TypeSystemC val CallableSymbolMarker.visibility: Visibility val RegularClassSymbolMarker.superTypes: List + val RegularClassSymbolMarker.superTypesRefs: List val RegularClassSymbolMarker.defaultType: KotlinTypeMarker val CallableSymbolMarker.isExpect: Boolean @@ -115,7 +116,9 @@ interface ExpectActualMatchingContext : TypeSystemC val CallableSymbolMarker.dispatchReceiverType: KotlinTypeMarker? val CallableSymbolMarker.extensionReceiverType: KotlinTypeMarker? + val CallableSymbolMarker.extensionReceiverTypeRef: TypeRefMarker? val CallableSymbolMarker.returnType: KotlinTypeMarker + val CallableSymbolMarker.returnTypeRef: TypeRefMarker val CallableSymbolMarker.typeParameters: List val FunctionSymbolMarker.valueParameters: List @@ -135,6 +138,7 @@ interface ExpectActualMatchingContext : TypeSystemC fun CallableSymbolMarker.isAnnotationConstructor(): Boolean val TypeParameterSymbolMarker.bounds: List + val TypeParameterSymbolMarker.boundsTypeRefs: List val TypeParameterSymbolMarker.variance: Variance val TypeParameterSymbolMarker.isReified: Boolean @@ -215,4 +219,35 @@ interface ExpectActualMatchingContext : TypeSystemC ): Map> fun DeclarationSymbolMarker.getSourceElement(): SourceElementMarker + + fun TypeRefMarker.getClassId(): ClassId? + + /** + * Callback interface to be implemented by caller of [checkAnnotationsOnTypeRefAndArguments]. + */ + fun interface AnnotationsCheckerCallback { + /** + * Implementation must check `expect` and `actual` annotations and report diagnostic in case of incompatibility. + * [actualTypeRefSource] is needed in order to know where on the `actual` declaration to insert the missing annotation + * from the `expect` declaration (see [AbstractExpectActualAnnotationMatchChecker.Incompatibility.actualAnnotationTargetElement]). + */ + fun check( + expectAnnotations: List, actualAnnotations: List, + actualTypeRefSource: SourceElementMarker, + ) + } + + /** + * Finds pairs of matching expect and actual types, on which annotations must be checked by [AbstractExpectActualAnnotationMatchChecker]. + * + * This is done by recursively traversing [expectTypeRef] and [actualTypeRef] and their arguments, which is needed in case of + * complex types like `T1>`. Founded expect and actual annotations are passed to [checker] callback. + * For functional types (e.g. `ReceiverType.(Arg1Type) -> ReturnType`) receiver, argument and return types and their arguments + * are checked. + * + * **Example**: for type `@Ann1 List<@Ann2 Map<@Ann3 Int, @Ann4 String>>`, there are 4 types to check in [checker]. + */ + fun checkAnnotationsOnTypeRefAndArguments( + expectTypeRef: TypeRefMarker, actualTypeRef: TypeRefMarker, checker: AnnotationsCheckerCallback, + ) } diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1AbstractExpectActualAnnotationMatchChecker.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1AbstractExpectActualAnnotationMatchChecker.kt index bf359e0172f..d1c4c7685fa 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1AbstractExpectActualAnnotationMatchChecker.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/K1AbstractExpectActualAnnotationMatchChecker.kt @@ -179,8 +179,6 @@ object K1AbstractExpectActualAnnotationMatchChecker { expectSymbol: DeclarationSymbolMarker, actualSymbol: DeclarationSymbolMarker, ): Incompatibility? { - // TODO(Roman.Efremov, KT-60671): check annotations set on types - val skipSourceAnnotations = actualSymbol.hasSourceAnnotationsErased val actualAnnotationsByName = actualSymbol.annotations.groupBy { it.classId } diff --git a/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/checkDiagnosticFullText.fir.diag.txt b/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/checkDiagnosticFullText.fir.diag.txt index 9680cbcd606..10f20fb9ac0 100644 --- a/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/checkDiagnosticFullText.fir.diag.txt +++ b/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/checkDiagnosticFullText.fir.diag.txt @@ -21,3 +21,6 @@ All annotations from expect 'fun inTypeParam(): Unit' must be present with t /jvm.kt:(513,521): warning: Annotation `@PROPERTY_GETTER:Ann()` is missing on actual declaration. All annotations from expect 'val onGetter: String get(): String' must be present with the same arguments on actual 'val onGetter: String get(): String', otherwise they might behave incorrectly. + +/jvm.kt:(547,553): warning: Annotation `@Ann()` is missing on actual declaration. +All annotations from expect 'fun onType(param: @R|Ann|() Any): Unit' must be present with the same arguments on actual 'fun onType(param: Any): Unit', otherwise they might behave incorrectly. diff --git a/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/checkDiagnosticFullText.fir.kt b/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/checkDiagnosticFullText.fir.kt index ec82512a911..8afbc683967 100644 --- a/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/checkDiagnosticFullText.fir.kt +++ b/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/checkDiagnosticFullText.fir.kt @@ -68,4 +68,4 @@ actual fun withDifferentArg() {} actual val onGetter: String = "" -actual fun onType(param: Any) {} +actual fun onType(param: Any) {} diff --git a/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeUsage.fir.kt b/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeUsage.fir.kt index 33c907ec194..8dfcc6f3e9d 100644 --- a/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeUsage.fir.kt +++ b/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeUsage.fir.kt @@ -61,46 +61,46 @@ expect fun funcTypeArgType(arg: (arg: @Ann Any) -> Unit) // MODULE: m1-jvm()()(m1-common) // FILE: jvm.kt -actual fun valueParameterType(arg: String) {} +actual fun valueParameterType(arg: String) {} -actual fun returnType(): String = "" +actual fun returnType(): String = "" -actual fun methodTypeParamBound() {} +actual fun methodTypeParamBound() {} -actual class OnClassTypeParamBound +actual class OnClassTypeParamBound -actual fun typeParamBoundInWhere() where T : Any {} +actual fun typeParamBoundInWhere() where T : Any {} -actual fun severalBounds() where T : I1, T : I2 {} +actual fun severalBounds() where T : I1, T : I2 {} actual fun severalBoundsDifferentOrder() where T : @Ann I1, T : I2 {} actual fun lessTypeParamBoundsOnActual() where T : @Ann I2 {} -actual fun Any.onReceiver() {} +actual fun Any.onReceiver() {} -actual class OnClassSuper : I1 +actual class OnClassSuper : I1 actual class OnClassSuperDifferentOrder : @Ann I2, I1 -actual class OnClassSuperMoreOnActual : I1, I2 +actual class OnClassSuperMoreOnActual : I1, I2 -actual class OnClassSuperTypeParams : I3 +actual class OnClassSuperTypeParams : I3 -actual fun deepInParamsTypes(arg: I3>) {} +actual fun deepInParamsTypes(arg: I3>) {} -actual fun starProjection(arg: I4<*, Any>) {} +actual fun starProjection(arg: I4<*, Any>) {} -actual fun typeArgWithVariance(t: I3) {} +actual fun typeArgWithVariance(t: I3) {} actual fun qualifierPartsMatching(arg: WithNested.Nested<@Ann String>) {} -actual fun qualifierPartsNonMatching(arg: WithNested<@Ann String>.Nested) {} +actual fun qualifierPartsNonMatching(arg: WithNested<@Ann String>.Nested) {} actual fun funTypeVsUserType(arg: kotlin.jvm.functions.Function0) {} -actual fun funcTypeReturnType(arg: () -> Any) {} +actual fun funcTypeReturnType(arg: () -> Any) {} -actual fun funcTypeReceiverType(arg: Any.() -> Unit) {} +actual fun funcTypeReceiverType(arg: Any.() -> Unit) {} -actual fun funcTypeArgType(arg: (arg: Any) -> Unit) {} +actual fun funcTypeArgType(arg: (arg: Any) -> Unit) {} diff --git a/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeUsageTypealiasInSuper.fir.kt b/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeUsageTypealiasInSuper.fir.kt new file mode 100644 index 00000000000..283e777561c --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeUsageTypealiasInSuper.fir.kt @@ -0,0 +1,14 @@ +// MODULE: m1-common +// FILE: common.kt +@Target(AnnotationTarget.TYPE) +annotation class Ann + +interface I + +expect class Foo: @Ann I + +// MODULE: m1-jvm()()(m1-common) +// FILE: jvm.kt +typealias ITypealias = I + +actual class Foo : ITypealias diff --git a/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeUsageTypealiasInSuper.kt b/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeUsageTypealiasInSuper.kt new file mode 100644 index 00000000000..f92d8b8d60c --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeUsageTypealiasInSuper.kt @@ -0,0 +1,14 @@ +// MODULE: m1-common +// FILE: common.kt +@Target(AnnotationTarget.TYPE) +annotation class Ann + +interface I + +expect class Foo: @Ann I + +// MODULE: m1-jvm()()(m1-common) +// FILE: jvm.kt +typealias ITypealias = I + +actual class Foo : ITypealias diff --git a/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeUsageWithImplicitType.fir.kt b/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeUsageWithImplicitType.fir.kt new file mode 100644 index 00000000000..d244739065e --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeUsageWithImplicitType.fir.kt @@ -0,0 +1,10 @@ +// MODULE: m1-common +// FILE: common.kt +@Target(AnnotationTarget.TYPE) +annotation class Ann + +expect fun foo(): @Ann Int + +// MODULE: m1-jvm()()(m1-common) +// FILE: jvm.kt +actual fun foo() = 1 diff --git a/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeUsageWithImplicitType.kt b/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeUsageWithImplicitType.kt index 6db09169331..dcd97056d7a 100644 --- a/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeUsageWithImplicitType.kt +++ b/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeUsageWithImplicitType.kt @@ -1,4 +1,3 @@ -// FIR_IDENTICAL // MODULE: m1-common // FILE: common.kt @Target(AnnotationTarget.TYPE) diff --git a/compiler/testData/diagnostics/tests/multiplatform/defaultParams_inheritanceByDelegation_positive.kt b/compiler/testData/diagnostics/tests/multiplatform/defaultParams_inheritanceByDelegation_positive.kt index 065dc8ea938..36cf9a2db46 100644 --- a/compiler/testData/diagnostics/tests/multiplatform/defaultParams_inheritanceByDelegation_positive.kt +++ b/compiler/testData/diagnostics/tests/multiplatform/defaultParams_inheritanceByDelegation_positive.kt @@ -12,7 +12,7 @@ expect class E2 { // MODULE: m2-jvm()()(m1-common) // FILE: jvm.kt interface I { - fun f(x: Int = 5) = x + fun f(x: Int = 5): Int = x } actual class E(i: I) : I by i diff --git a/compiler/testData/diagnostics/tests/multiplatform/java/inheritedJavaMembers.kt b/compiler/testData/diagnostics/tests/multiplatform/java/inheritedJavaMembers.kt index f98f608d425..c06cca89d53 100644 --- a/compiler/testData/diagnostics/tests/multiplatform/java/inheritedJavaMembers.kt +++ b/compiler/testData/diagnostics/tests/multiplatform/java/inheritedJavaMembers.kt @@ -23,11 +23,11 @@ expect class FastArrayList : MutableListEx, RandomAccess { fun ensureCapacity(minCapacity: Int) override val size: Int override fun isEmpty(): Boolean - override fun contains(element: @UnsafeVariance E): Boolean - override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean + override fun contains(element: E): Boolean + override fun containsAll(elements: Collection): Boolean override operator fun get(index: Int): E - override fun indexOf(element: @UnsafeVariance E): Int - override fun lastIndexOf(element: @UnsafeVariance E): Int + override fun indexOf(element: E): Int + override fun lastIndexOf(element: E): Int override fun iterator(): MutableIterator override fun add(element: E): Boolean override fun remove(element: E): Boolean diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java index 551a657b4b6..a66726ad746 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java @@ -24107,6 +24107,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeUsage.kt"); } + @Test + @TestMetadata("typeUsageTypealiasInSuper.kt") + public void testTypeUsageTypealiasInSuper() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typeUsageTypealiasInSuper.kt"); + } + @Test @TestMetadata("typeUsageWithImplicitType.kt") public void testTypeUsageWithImplicitType() throws Exception { diff --git a/core/compiler.common/src/org/jetbrains/kotlin/mpp/TypeRefMarker.kt b/core/compiler.common/src/org/jetbrains/kotlin/mpp/TypeRefMarker.kt new file mode 100644 index 00000000000..3b4535ced62 --- /dev/null +++ b/core/compiler.common/src/org/jetbrains/kotlin/mpp/TypeRefMarker.kt @@ -0,0 +1,17 @@ +/* + * 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.mpp + +import org.jetbrains.kotlin.name.ClassId + +/** + * Common interface for type references to be used in abstract checker. + * The idea is similar to [org.jetbrains.kotlin.types.model.KotlinTypeMarker], + * but type reference, unlike a type, has source element. + * + * Used in [org.jetbrains.kotlin.resolve.calls.mpp.AbstractExpectActualAnnotationMatchChecker]. + */ +interface TypeRefMarker