[FE, IR] Check compatibility of annotations set on type usages in expect and actual declarations
This includes checking of annotatins set on: - value parameter types - type parameter bound types - extension functions receiver types - function return types - class super types Fix in `defaultParams_inheritanceByDelegation_positive.kt` is needed because of problem in resolution of implicit return types (KT-62064), which leads to crash in annotation checker, because it expects resolved return type. MR: KT-MR-12245 ^KT-60671 Fixed
This commit is contained in:
committed by
Space Team
parent
cb8529d65b
commit
423f4ca5f0
+63
-6
@@ -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<ExpectActualMatchingContext.AnnotationCallInfo>,
|
||||
actualContainerSymbol: DeclarationSymbolMarker,
|
||||
): IncompatibilityType<ExpectActualMatchingContext.AnnotationCallInfo>? {
|
||||
// TODO(Roman.Efremov, KT-60671): check annotations set on types
|
||||
val skipSourceAnnotations = actualContainerSymbol.hasSourceAnnotationsErased
|
||||
val actualAnnotationsByName = actualAnnotations.groupBy { it.classId }
|
||||
|
||||
|
||||
+35
@@ -85,6 +85,7 @@ interface ExpectActualMatchingContext<T : DeclarationSymbolMarker> : TypeSystemC
|
||||
val CallableSymbolMarker.visibility: Visibility
|
||||
|
||||
val RegularClassSymbolMarker.superTypes: List<KotlinTypeMarker>
|
||||
val RegularClassSymbolMarker.superTypesRefs: List<TypeRefMarker>
|
||||
val RegularClassSymbolMarker.defaultType: KotlinTypeMarker
|
||||
|
||||
val CallableSymbolMarker.isExpect: Boolean
|
||||
@@ -115,7 +116,9 @@ interface ExpectActualMatchingContext<T : DeclarationSymbolMarker> : 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<TypeParameterSymbolMarker>
|
||||
val FunctionSymbolMarker.valueParameters: List<ValueParameterSymbolMarker>
|
||||
|
||||
@@ -135,6 +138,7 @@ interface ExpectActualMatchingContext<T : DeclarationSymbolMarker> : TypeSystemC
|
||||
fun CallableSymbolMarker.isAnnotationConstructor(): Boolean
|
||||
|
||||
val TypeParameterSymbolMarker.bounds: List<KotlinTypeMarker>
|
||||
val TypeParameterSymbolMarker.boundsTypeRefs: List<TypeRefMarker>
|
||||
val TypeParameterSymbolMarker.variance: Variance
|
||||
val TypeParameterSymbolMarker.isReified: Boolean
|
||||
|
||||
@@ -215,4 +219,35 @@ interface ExpectActualMatchingContext<T : DeclarationSymbolMarker> : TypeSystemC
|
||||
): Map<out DeclarationSymbolMarker, ExpectActualCompatibility<*>>
|
||||
|
||||
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<AnnotationCallInfo>, actualAnnotations: List<AnnotationCallInfo>,
|
||||
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<T2<@Ann T3>>`. 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,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user