[FE, IR] Add expect actual annotations incompatibility info into diagnostic

This is needed for more beautiful reporting and easier
implementation of quick fix in IDE.

^KT-58551
This commit is contained in:
Roman Efremov
2023-07-28 16:51:22 +02:00
committed by Space Team
parent a64bac0b8c
commit 11ccad7e40
19 changed files with 60 additions and 20 deletions
@@ -30,13 +30,14 @@ import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.FirTypeRef
import org.jetbrains.kotlin.lexer.KtKeywordToken
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.resolve.ForbiddenNamedArgumentsTarget
import org.jetbrains.kotlin.resolve.deprecation.DeprecationInfo
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualAnnotationsIncompatibilityType
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility
import org.jetbrains.kotlin.types.Variance
import kotlin.reflect.KClass
@@ -363,6 +364,7 @@ internal object FirToKtConversionCreator {
FirModuleData::class,
ExpectActualCompatibility::class,
ExpectActualCompatibility.Incompatible::class,
ExpectActualAnnotationsIncompatibilityType::class,
DeprecationInfo::class,
ApiVersion::class,
CallableId::class,
@@ -3540,6 +3540,7 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
ActualAnnotationsNotMatchExpectImpl(
firSymbolBuilder.buildSymbol(firDiagnostic.a),
firSymbolBuilder.buildSymbol(firDiagnostic.b),
firDiagnostic.c,
firDiagnostic as KtPsiDiagnostic,
token,
)
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.descriptors.EffectiveVisibility
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.diagnostics.WhenMissingCase
import org.jetbrains.kotlin.fir.FirModuleData
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.lexer.KtKeywordToken
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
@@ -80,6 +81,7 @@ import org.jetbrains.kotlin.psi.KtWhenEntry
import org.jetbrains.kotlin.psi.KtWhenExpression
import org.jetbrains.kotlin.resolve.ForbiddenNamedArgumentsTarget
import org.jetbrains.kotlin.resolve.deprecation.DeprecationInfo
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualAnnotationsIncompatibilityType
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility.Incompatible
import org.jetbrains.kotlin.types.Variance
@@ -2472,8 +2474,9 @@ sealed interface KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
interface ActualAnnotationsNotMatchExpect : KtFirDiagnostic<KtElement> {
override val diagnosticClass get() = ActualAnnotationsNotMatchExpect::class
val expect: KtSymbol
val actual: KtSymbol
val expectSymbol: KtSymbol
val actualSymbol: KtSymbol
val incompatibilityType: ExpectActualAnnotationsIncompatibilityType<FirAnnotation>
}
interface InitializerRequiredForDestructuringDeclaration : KtFirDiagnostic<KtDestructuringDeclaration> {
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.diagnostics.KtPsiDiagnostic
import org.jetbrains.kotlin.diagnostics.WhenMissingCase
import org.jetbrains.kotlin.fir.FirModuleData
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.lexer.KtKeywordToken
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
@@ -81,6 +82,7 @@ import org.jetbrains.kotlin.psi.KtWhenEntry
import org.jetbrains.kotlin.psi.KtWhenExpression
import org.jetbrains.kotlin.resolve.ForbiddenNamedArgumentsTarget
import org.jetbrains.kotlin.resolve.deprecation.DeprecationInfo
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualAnnotationsIncompatibilityType
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility.Incompatible
import org.jetbrains.kotlin.types.Variance
@@ -2980,8 +2982,9 @@ internal class ActualTypealiasToSpecialAnnotationImpl(
) : KtAbstractFirDiagnostic<KtTypeAlias>(firDiagnostic, token), KtFirDiagnostic.ActualTypealiasToSpecialAnnotation
internal class ActualAnnotationsNotMatchExpectImpl(
override val expect: KtSymbol,
override val actual: KtSymbol,
override val expectSymbol: KtSymbol,
override val actualSymbol: KtSymbol,
override val incompatibilityType: ExpectActualAnnotationsIncompatibilityType<FirAnnotation>,
firDiagnostic: KtPsiDiagnostic,
token: KtLifetimeToken,
) : KtAbstractFirDiagnostic<KtElement>(firDiagnostic, token), KtFirDiagnostic.ActualAnnotationsNotMatchExpect
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.fir.FirModuleData
import org.jetbrains.kotlin.fir.checkers.generator.diagnostics.model.*
import org.jetbrains.kotlin.fir.declarations.FirFunction
import org.jetbrains.kotlin.fir.declarations.FirTypeAlias
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.*
@@ -34,6 +35,7 @@ import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.ForbiddenNamedArgumentsTarget
import org.jetbrains.kotlin.resolve.deprecation.DeprecationInfo
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualAnnotationsIncompatibilityType
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility.Incompatible
import org.jetbrains.kotlin.types.Variance
@@ -1195,8 +1197,9 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
}
val ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT by warning<KtElement>(PositioningStrategy.DECLARATION_NAME_ONLY) {
parameter<Symbol>("expect")
parameter<Symbol>("actual")
parameter<Symbol>("expectSymbol")
parameter<Symbol>("actualSymbol")
parameter<ExpectActualAnnotationsIncompatibilityType<FirAnnotation>>("incompatibilityType")
}
}
@@ -36,6 +36,7 @@ import org.jetbrains.kotlin.diagnostics.WhenMissingCase
import org.jetbrains.kotlin.diagnostics.rendering.RootDiagnosticRendererFactory
import org.jetbrains.kotlin.fir.FirModuleData
import org.jetbrains.kotlin.fir.declarations.FirFunction
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirBackingFieldSymbol
@@ -103,6 +104,7 @@ import org.jetbrains.kotlin.psi.KtWhenEntry
import org.jetbrains.kotlin.psi.KtWhenExpression
import org.jetbrains.kotlin.resolve.ForbiddenNamedArgumentsTarget
import org.jetbrains.kotlin.resolve.deprecation.DeprecationInfo
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualAnnotationsIncompatibilityType
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility.Incompatible
import org.jetbrains.kotlin.types.Variance
@@ -635,7 +637,7 @@ object FirErrors {
val NOT_A_MULTIPLATFORM_COMPILATION by error0<PsiElement>()
val EXPECT_ACTUAL_OPT_IN_ANNOTATION by error0<KtNamedDeclaration>(SourceElementPositioningStrategies.EXPECT_ACTUAL_MODIFIER)
val ACTUAL_TYPEALIAS_TO_SPECIAL_ANNOTATION by error1<KtTypeAlias, ClassId>(SourceElementPositioningStrategies.TYPEALIAS_TYPE_REFERENCE)
val ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT by warning2<KtElement, FirBasedSymbol<*>, FirBasedSymbol<*>>(SourceElementPositioningStrategies.DECLARATION_NAME_ONLY)
val ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT by warning3<KtElement, FirBasedSymbol<*>, FirBasedSymbol<*>, ExpectActualAnnotationsIncompatibilityType<FirAnnotation>>(SourceElementPositioningStrategies.DECLARATION_NAME_ONLY)
// Destructuring declaration
val INITIALIZER_REQUIRED_FOR_DESTRUCTURING_DECLARATION by error0<KtDestructuringDeclaration>()
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.fir.declarations.utils.isExpect
import org.jetbrains.kotlin.fir.declarations.utils.isExternal
import org.jetbrains.kotlin.fir.declarations.utils.isTailRec
import org.jetbrains.kotlin.fir.expectActualMatchingContextFactory
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.languageVersionSettings
import org.jetbrains.kotlin.fir.scopes.collectAllFunctions
import org.jetbrains.kotlin.fir.scopes.getDeclaredConstructors
@@ -263,6 +264,7 @@ object FirExpectActualDeclarationChecker : FirBasicDeclarationChecker() {
actualSymbol.source, FirErrors.ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT,
incompatibility.expectSymbol as FirBasedSymbol<*>,
incompatibility.actualSymbol as FirBasedSymbol<*>,
incompatibility.type.mapAnnotationType { it.annotationSymbol as FirAnnotation },
context
)
}
@@ -1889,7 +1889,8 @@ object FirErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
"All annotations from `expect` must be presented with same arguments on `actual`, otherwise they have no effect.\n" +
"Expected: {0}\n" +
"Actual: {1}",
FirDiagnosticRenderers.SYMBOL_WITH_ANNOTATIONS, FirDiagnosticRenderers.SYMBOL_WITH_ANNOTATIONS
FirDiagnosticRenderers.SYMBOL_WITH_ANNOTATIONS, FirDiagnosticRenderers.SYMBOL_WITH_ANNOTATIONS,
TO_STRING
)
// Destructuring declaration
@@ -357,6 +357,8 @@ class FirExpectActualMatchingContextImpl private constructor(
}
private inner class AnnotationCallInfoImpl(val annotation: FirAnnotation) : AnnotationCallInfo {
override val annotationSymbol: FirAnnotation = annotation
override val classId: ClassId?
get() = annotation.toAnnotationClassId(actualSession)
@@ -13,6 +13,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.config.LanguageFeature;
import org.jetbrains.kotlin.config.LanguageVersionSettings;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
import org.jetbrains.kotlin.diagnostics.rendering.DeclarationWithDiagnosticComponents;
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages;
import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap;
@@ -33,6 +34,7 @@ import org.jetbrains.kotlin.resolve.calls.tower.CandidateApplicability;
import org.jetbrains.kotlin.resolve.calls.tower.WrongResolutionToClassifier;
import org.jetbrains.kotlin.resolve.calls.util.BuilderLambdaLabelingInfo;
import org.jetbrains.kotlin.resolve.deprecation.DescriptorBasedDeprecationInfo;
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualAnnotationsIncompatibilityType;
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility.Incompatible;
import org.jetbrains.kotlin.serialization.deserialization.IncompatibleVersionErrorData;
import org.jetbrains.kotlin.types.KotlinType;
@@ -844,8 +846,9 @@ public interface Errors {
DiagnosticFactory0<KtNamedDeclaration> EXPECT_ACTUAL_OPT_IN_ANNOTATION = DiagnosticFactory0.create(ERROR, EXPECT_ACTUAL_MODIFIER);
DiagnosticFactory1<KtTypeAlias, ClassId> ACTUAL_TYPEALIAS_TO_SPECIAL_ANNOTATION =
DiagnosticFactory1.create(ERROR, TYPEALIAS_TYPE_REFERENCE);
DiagnosticFactory2<KtNamedDeclaration, DeclarationDescriptor, DeclarationDescriptor> ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT
= DiagnosticFactory2.create(WARNING, DECLARATION_NAME_ONLY);
DiagnosticFactory3<KtNamedDeclaration, DeclarationDescriptor, DeclarationDescriptor,
ExpectActualAnnotationsIncompatibilityType<AnnotationDescriptor>> ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT =
DiagnosticFactory3.create(WARNING, DECLARATION_NAME_ONLY);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -407,7 +407,7 @@ public class DefaultErrorMessages {
"All annotations from `expect` must be presented with same arguments on `actual`, otherwise they have no effect.\n" +
" Expected: {0}\n" +
" Actual: {1}",
DESCRIPTOR_WITH_ANNOTATIONS, DESCRIPTOR_WITH_ANNOTATIONS);
DESCRIPTOR_WITH_ANNOTATIONS, DESCRIPTOR_WITH_ANNOTATIONS, TO_STRING);
MAP.put(PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT, "Projections are not allowed on type arguments of functions and properties");
MAP.put(SUPERTYPE_NOT_INITIALIZED, "This type has a constructor, and thus must be initialized here");
@@ -21,6 +21,7 @@ import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.config.AnalysisFlags
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.incremental.components.ExpectActualTracker
import org.jetbrains.kotlin.mpp.MppJavaImplicitActualizatorMarker
@@ -490,7 +491,8 @@ class ExpectedActualDeclarationChecker(
Errors.ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT.on(
reportOn,
incompatibility.expectSymbol as DeclarationDescriptor,
incompatibility.actualSymbol as DeclarationDescriptor
incompatibility.actualSymbol as DeclarationDescriptor,
incompatibility.type.mapAnnotationType { it.annotationSymbol as AnnotationDescriptor }
)
)
}
@@ -9,20 +9,17 @@ import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.backend.common.BackendDiagnosticRenderers.INCOMPATIBILITY
import org.jetbrains.kotlin.backend.common.BackendDiagnosticRenderers.SYMBOL_OWNER_DECLARATION_FQ_NAME
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactoryToRendererMap
import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies.ACTUAL_DECLARATION_NAME
import org.jetbrains.kotlin.diagnostics.error1
import org.jetbrains.kotlin.diagnostics.error2
import org.jetbrains.kotlin.diagnostics.error3
import org.jetbrains.kotlin.diagnostics.*
import org.jetbrains.kotlin.diagnostics.rendering.BaseDiagnosticRendererFactory
import org.jetbrains.kotlin.diagnostics.rendering.CommonRenderers.STRING
import org.jetbrains.kotlin.diagnostics.rendering.Renderer
import org.jetbrains.kotlin.diagnostics.rendering.Renderers.MODULE_WITH_PLATFORM
import org.jetbrains.kotlin.diagnostics.rendering.RootDiagnosticRendererFactory
import org.jetbrains.kotlin.diagnostics.warning2
import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualAnnotationsIncompatibilityType
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility
object CommonBackendErrors {
@@ -30,7 +27,7 @@ object CommonBackendErrors {
val MANY_INTERFACES_MEMBER_NOT_IMPLEMENTED by error2<PsiElement, String, String>()
val MANY_IMPL_MEMBER_NOT_IMPLEMENTED by error2<PsiElement, String, String>()
val INCOMPATIBLE_MATCHING by error3<PsiElement, String, String, ExpectActualCompatibility.Incompatible<*>>()
val ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT by warning2<PsiElement, IrSymbol, IrSymbol>()
val ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT by warning3<PsiElement, IrSymbol, IrSymbol, ExpectActualAnnotationsIncompatibilityType<IrConstructorCall>>()
val EVALUATION_ERROR by error1<PsiElement, String>()
init {
@@ -70,6 +67,7 @@ object KtDefaultCommonBackendErrorMessages : BaseDiagnosticRendererFactory() {
"All annotations from expect declaration `{0}` must be present with the same arguments on actual declaration `{1}` otherwise they have no effect",
SYMBOL_OWNER_DECLARATION_FQ_NAME,
SYMBOL_OWNER_DECLARATION_FQ_NAME,
STRING,
)
map.put(
CommonBackendErrors.EVALUATION_ERROR,
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.builders.declarations.buildProperty
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrPropertySymbol
import org.jetbrains.kotlin.ir.symbols.IrSymbol
@@ -20,6 +21,7 @@ import org.jetbrains.kotlin.ir.types.IrTypeSystemContext
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.resolve.calls.mpp.AbstractExpectActualCompatibilityChecker
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualAnnotationsIncompatibilityType
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility
import org.jetbrains.kotlin.resolve.multiplatform.OptionalAnnotationUtil
import org.jetbrains.kotlin.utils.addToStdlib.runIf
@@ -125,12 +127,14 @@ internal fun KtDiagnosticReporterWithImplicitIrBasedContext.reportIncompatibleEx
internal fun KtDiagnosticReporterWithImplicitIrBasedContext.reportActualAnnotationsNotMatchExpect(
expectSymbol: IrSymbol,
actualSymbol: IrSymbol,
incompatibilityType: ExpectActualAnnotationsIncompatibilityType<IrConstructorCall>,
reportOn: IrSymbol,
) {
at(reportOn.owner as IrDeclaration).report(
CommonBackendErrors.ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT,
expectSymbol,
actualSymbol,
incompatibilityType,
)
}
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.KtDiagnosticReporterWithImplicitIrBasedContext
import org.jetbrains.kotlin.backend.common.lower.parentsWithSelf
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeAliasSymbol
@@ -45,6 +46,7 @@ internal class IrExpectActualAnnotationMatchingChecker(
diagnosticsReporter.reportActualAnnotationsNotMatchExpect(
incompatibility.expectSymbol as IrSymbol,
incompatibility.actualSymbol as IrSymbol,
incompatibility.type.mapAnnotationType { it.annotationSymbol as IrConstructorCall },
reportOn,
)
}
@@ -472,6 +472,8 @@ internal abstract class IrExpectActualMatchingContext(
}
private inner class AnnotationCallInfoImpl(val irElement: IrConstructorCall) : AnnotationCallInfo {
override val annotationSymbol: IrConstructorCall = irElement
override val classId: ClassId?
get() = getAnnotationClass()?.classId
@@ -169,6 +169,7 @@ interface ExpectActualMatchingContext<T : DeclarationSymbolMarker> : TypeSystemC
val DeclarationSymbolMarker.hasSourceAnnotationsErased: Boolean
interface AnnotationCallInfo {
val annotationSymbol: Any
val classId: ClassId?
val isRetentionSource: Boolean
val isOptIn: Boolean
@@ -355,6 +355,8 @@ class ClassicExpectActualMatchingContext(val platformModule: ModuleDescriptor) :
private class AnnotationCallInfoImpl(
val annotationDescriptor: AnnotationDescriptor,
) : AnnotationCallInfo {
override val annotationSymbol: AnnotationDescriptor = annotationDescriptor
override val classId: ClassId?
get() = annotationDescriptor.annotationClass?.classId
@@ -16,4 +16,11 @@ sealed class ExpectActualAnnotationsIncompatibilityType<out A> {
override val expectAnnotation: A,
val actualAnnotation: A
) : ExpectActualAnnotationsIncompatibilityType<A>()
fun <A2> mapAnnotationType(mapper: (A) -> A2): ExpectActualAnnotationsIncompatibilityType<A2> {
return when (this) {
is MissingOnActual -> MissingOnActual(mapper(expectAnnotation))
is DifferentOnActual<A> -> DifferentOnActual(mapper(expectAnnotation), mapper(actualAnnotation))
}
}
}