[FE] Implement FE logic of expect actual annotations matching

This implementation only checks annotations set on expect/actual
declarations and requires further refinement (e.g. checking of other
annotation targets, class scopes within typealiases).

^KT-58551
This commit is contained in:
Roman Efremov
2023-06-22 15:22:17 +02:00
committed by Space Team
parent b1bdb619d8
commit 1a4ab9bb4b
49 changed files with 1481 additions and 25 deletions
@@ -3504,6 +3504,14 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirErrors.ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT) { firDiagnostic ->
ActualAnnotationsNotMatchExpectImpl(
firSymbolBuilder.buildSymbol(firDiagnostic.a),
firSymbolBuilder.buildSymbol(firDiagnostic.b),
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirErrors.INITIALIZER_REQUIRED_FOR_DESTRUCTURING_DECLARATION) { firDiagnostic ->
InitializerRequiredForDestructuringDeclarationImpl(
firDiagnostic as KtPsiDiagnostic,
@@ -2448,6 +2448,12 @@ sealed interface KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
val typealiasedClassId: ClassId
}
interface ActualAnnotationsNotMatchExpect : KtFirDiagnostic<KtElement> {
override val diagnosticClass get() = ActualAnnotationsNotMatchExpect::class
val expect: KtSymbol
val actual: KtSymbol
}
interface InitializerRequiredForDestructuringDeclaration : KtFirDiagnostic<KtDestructuringDeclaration> {
override val diagnosticClass get() = InitializerRequiredForDestructuringDeclaration::class
}
@@ -2952,6 +2952,13 @@ internal class ActualTypealiasToSpecialAnnotationImpl(
token: KtLifetimeToken,
) : KtAbstractFirDiagnostic<KtTypeAlias>(firDiagnostic, token), KtFirDiagnostic.ActualTypealiasToSpecialAnnotation
internal class ActualAnnotationsNotMatchExpectImpl(
override val expect: KtSymbol,
override val actual: KtSymbol,
firDiagnostic: KtPsiDiagnostic,
token: KtLifetimeToken,
) : KtAbstractFirDiagnostic<KtElement>(firDiagnostic, token), KtFirDiagnostic.ActualAnnotationsNotMatchExpect
internal class InitializerRequiredForDestructuringDeclarationImpl(
firDiagnostic: KtPsiDiagnostic,
token: KtLifetimeToken,
@@ -205,6 +205,100 @@ public class FirOldFrontendMPPDiagnosticsWithLightTreeTestGenerated extends Abst
runTest("compiler/testData/diagnostics/tests/multiplatform/widerVisibilityInActualClassifier.kt");
}
@Nested
@TestMetadata("compiler/testData/diagnostics/tests/multiplatform/annotationMatching")
@TestDataPath("$PROJECT_ROOT")
public class AnnotationMatching {
@Test
public void testAllFilesPresentInAnnotationMatching() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/multiplatform/annotationMatching"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.(reversed|fir|ll)\\.kts?$"), TargetBackend.JVM_IR, true);
}
@Test
@TestMetadata("annotationArgumentsConstExpressions.kt")
public void testAnnotationArgumentsConstExpressions() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/annotationArgumentsConstExpressions.kt");
}
@Test
@TestMetadata("annotationArgumentsDefaults.kt")
public void testAnnotationArgumentsDefaults() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/annotationArgumentsDefaults.kt");
}
@Test
@TestMetadata("annotationTypeParameters.kt")
public void testAnnotationTypeParameters() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/annotationTypeParameters.kt");
}
@Test
@TestMetadata("basicOnDeclaration.kt")
public void testBasicOnDeclaration() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/basicOnDeclaration.kt");
}
@Test
@TestMetadata("checkDiagnosticFullText.kt")
public void testCheckDiagnosticFullText() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/checkDiagnosticFullText.kt");
}
@Test
@TestMetadata("compatibleOverrides.kt")
public void testCompatibleOverrides() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/compatibleOverrides.kt");
}
@Test
@TestMetadata("differentOrder.kt")
public void testDifferentOrder() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/differentOrder.kt");
}
@Test
@TestMetadata("floatNumbersComparison.kt")
public void testFloatNumbersComparison() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/floatNumbersComparison.kt");
}
@Test
@TestMetadata("kclassArgWithExpectClass.kt")
public void testKclassArgWithExpectClass() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/kclassArgWithExpectClass.kt");
}
@Test
@TestMetadata("kotlinAnaloguesForJavaAnnotations.kt")
public void testKotlinAnaloguesForJavaAnnotations() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/kotlinAnaloguesForJavaAnnotations.kt");
}
@Test
@TestMetadata("typealias.kt")
public void testTypealias() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typealias.kt");
}
@Test
@TestMetadata("typealiasToJavaLibrary.kt")
public void testTypealiasToJavaLibrary() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typealiasToJavaLibrary.kt");
}
@Test
@TestMetadata("typealiasToKtLibrary.kt")
public void testTypealiasToKtLibrary() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typealiasToKtLibrary.kt");
}
@Test
@TestMetadata("withOtherIncomatibilities.kt")
public void testWithOtherIncomatibilities() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/withOtherIncomatibilities.kt");
}
}
@Nested
@TestMetadata("compiler/testData/diagnostics/tests/multiplatform/defaultArguments")
@TestDataPath("$PROJECT_ROOT")
@@ -205,6 +205,100 @@ public class FirOldFrontendMPPDiagnosticsWithPsiTestGenerated extends AbstractFi
runTest("compiler/testData/diagnostics/tests/multiplatform/widerVisibilityInActualClassifier.kt");
}
@Nested
@TestMetadata("compiler/testData/diagnostics/tests/multiplatform/annotationMatching")
@TestDataPath("$PROJECT_ROOT")
public class AnnotationMatching {
@Test
public void testAllFilesPresentInAnnotationMatching() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/multiplatform/annotationMatching"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.(reversed|fir|ll)\\.kts?$"), TargetBackend.JVM_IR, true);
}
@Test
@TestMetadata("annotationArgumentsConstExpressions.kt")
public void testAnnotationArgumentsConstExpressions() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/annotationArgumentsConstExpressions.kt");
}
@Test
@TestMetadata("annotationArgumentsDefaults.kt")
public void testAnnotationArgumentsDefaults() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/annotationArgumentsDefaults.kt");
}
@Test
@TestMetadata("annotationTypeParameters.kt")
public void testAnnotationTypeParameters() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/annotationTypeParameters.kt");
}
@Test
@TestMetadata("basicOnDeclaration.kt")
public void testBasicOnDeclaration() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/basicOnDeclaration.kt");
}
@Test
@TestMetadata("checkDiagnosticFullText.kt")
public void testCheckDiagnosticFullText() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/checkDiagnosticFullText.kt");
}
@Test
@TestMetadata("compatibleOverrides.kt")
public void testCompatibleOverrides() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/compatibleOverrides.kt");
}
@Test
@TestMetadata("differentOrder.kt")
public void testDifferentOrder() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/differentOrder.kt");
}
@Test
@TestMetadata("floatNumbersComparison.kt")
public void testFloatNumbersComparison() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/floatNumbersComparison.kt");
}
@Test
@TestMetadata("kclassArgWithExpectClass.kt")
public void testKclassArgWithExpectClass() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/kclassArgWithExpectClass.kt");
}
@Test
@TestMetadata("kotlinAnaloguesForJavaAnnotations.kt")
public void testKotlinAnaloguesForJavaAnnotations() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/kotlinAnaloguesForJavaAnnotations.kt");
}
@Test
@TestMetadata("typealias.kt")
public void testTypealias() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typealias.kt");
}
@Test
@TestMetadata("typealiasToJavaLibrary.kt")
public void testTypealiasToJavaLibrary() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typealiasToJavaLibrary.kt");
}
@Test
@TestMetadata("typealiasToKtLibrary.kt")
public void testTypealiasToKtLibrary() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typealiasToKtLibrary.kt");
}
@Test
@TestMetadata("withOtherIncomatibilities.kt")
public void testWithOtherIncomatibilities() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/withOtherIncomatibilities.kt");
}
}
@Nested
@TestMetadata("compiler/testData/diagnostics/tests/multiplatform/defaultArguments")
@TestDataPath("$PROJECT_ROOT")
@@ -1180,6 +1180,11 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
val ACTUAL_TYPEALIAS_TO_SPECIAL_ANNOTATION by error<KtTypeAlias>(PositioningStrategy.TYPEALIAS_TYPE_REFERENCE) {
parameter<ClassId>("typealiasedClassId")
}
val ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT by warning<KtElement>(PositioningStrategy.DECLARATION_NAME_ONLY) {
parameter<Symbol>("expect")
parameter<Symbol>("actual")
}
}
val DESTRUCTING_DECLARATION by object : DiagnosticGroup("Destructuring declaration") {
@@ -628,6 +628,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)
// Destructuring declaration
val INITIALIZER_REQUIRED_FOR_DESTRUCTURING_DECLARATION by error0<KtDestructuringDeclaration>()
@@ -20,17 +20,20 @@ import org.jetbrains.kotlin.fir.declarations.utils.isActual
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.languageVersionSettings
import org.jetbrains.kotlin.fir.scopes.collectAllFunctions
import org.jetbrains.kotlin.fir.scopes.getDeclaredConstructors
import org.jetbrains.kotlin.fir.scopes.impl.declaredMemberScope
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.fir.types.toSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeAliasSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirValueParameterSymbol
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.resolve.calls.mpp.AbstractExpectActualAnnotationMatchChecker
import org.jetbrains.kotlin.resolve.checkers.OptInNames
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility.*
@@ -176,7 +179,9 @@ object FirExpectActualDeclarationChecker : FirBasicDeclarationChecker() {
else -> {}
}
val expectedSingleCandidate = symbol.getSingleExpectForActualOrNull()
// We want to report errors even if a candidate is incompatible, but it's single
val expectedSingleCandidate = compatibilityToMembersMap[Compatible]?.singleOrNull()
?: symbol.getSingleExpectForActualOrNull()
if (expectedSingleCandidate != null) {
checkIfExpectHasDefaultArgumentsAndActualizedWithTypealias(
expectedSingleCandidate,
@@ -185,6 +190,7 @@ object FirExpectActualDeclarationChecker : FirBasicDeclarationChecker() {
reporter,
)
checkOptInAnnotation(declaration, expectedSingleCandidate, context, reporter)
checkAnnotationsMatch(expectedSingleCandidate, symbol, context, reporter)
}
}
@@ -243,6 +249,23 @@ object FirExpectActualDeclarationChecker : FirBasicDeclarationChecker() {
)
}
private fun checkAnnotationsMatch(
expectSymbol: FirBasedSymbol<*>,
actualSymbol: FirBasedSymbol<*>,
context: CheckerContext,
reporter: DiagnosticReporter
) {
val matchingContext = context.session.expectActualMatchingContextFactory.create(context.session, context.scopeSession)
val incompatibility =
AbstractExpectActualAnnotationMatchChecker.areAnnotationsCompatible(expectSymbol, actualSymbol, matchingContext) ?: return
reporter.reportOn(
actualSymbol.source, FirErrors.ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT,
incompatibility.expectSymbol as FirBasedSymbol<*>,
incompatibility.actualSymbol as FirBasedSymbol<*>,
context
)
}
fun Map<out ExpectActualCompatibility<*>, *>.allStrongIncompatibilities(): Boolean {
return keys.all { it is Incompatible && it.kind == IncompatibilityKind.STRONG }
}
@@ -178,4 +178,14 @@ object FirDiagnosticRenderers {
}
val FUNCTIONAL_TYPE_KINDS = KtDiagnosticRenderers.COLLECTION(FUNCTIONAL_TYPE_KIND)
@OptIn(SymbolInternals::class)
val SYMBOL_WITH_ANNOTATIONS = Renderer { s: FirBasedSymbol<*> ->
FirRenderer(
typeRenderer = ConeTypeRenderer(),
idRenderer = ConeIdShortRenderer(),
classMemberRenderer = null,
bodyRenderer = null,
).renderElementAsString(s.fir, trim = true)
}
}
@@ -51,6 +51,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ABSTRACT_PROPERTY
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ABSTRACT_SUPER_CALL
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ABSTRACT_SUPER_CALL_WARNING
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ACCESSOR_FOR_DELEGATED_PROPERTY
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ACTUAL_ANNOTATION_CONFLICTING_DEFAULT_ARGUMENT_VALUE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ACTUAL_MISSING
@@ -1854,6 +1855,13 @@ object FirErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
"`actual typealias` to annotation which affects code compilation can lead to incorrect behavior. Instead, use ''{0}'' annotation directly.",
TO_STRING
)
map.put(
ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT,
"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
)
// Destructuring declaration
map.put(INITIALIZER_REQUIRED_FOR_DESTRUCTURING_DECLARATION, "Initializer required for destructuring declaration")
@@ -11,12 +11,13 @@ import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.fir.FirExpectActualMatchingContext
import org.jetbrains.kotlin.fir.FirExpectActualMatchingContextFactory
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.declarations.collectEnumEntries
import org.jetbrains.kotlin.fir.declarations.isAnnotationConstructor
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.expressions.FirConstExpression
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.isSubstitutionOrIntersectionOverride
import org.jetbrains.kotlin.fir.resolve.*
import org.jetbrains.kotlin.fir.resolve.dfa.coneType
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
import org.jetbrains.kotlin.fir.scopes.*
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
@@ -26,6 +27,7 @@ import org.jetbrains.kotlin.mpp.*
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.ExpectActualMatchingContext.AnnotationCallInfo
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.TypeCheckerState
import org.jetbrains.kotlin.types.Variance
@@ -311,6 +313,48 @@ class FirExpectActualMatchingContextImpl private constructor(
override val CallableSymbolMarker.hasStableParameterNames: Boolean
get() = asSymbol().rawStatus.hasStableParameterNames
override val DeclarationSymbolMarker.annotations: List<AnnotationCallInfo>
get() = asSymbol().resolvedAnnotationsWithArguments.map(::AnnotationCallInfoImpl)
override fun areAnnotationArgumentsEqual(annotation1: AnnotationCallInfo, annotation2: AnnotationCallInfo): Boolean {
fun AnnotationCallInfo.getFirAnnotation(): FirAnnotation {
return (this as AnnotationCallInfoImpl).annotation
}
return areFirAnnotationsEqual(annotation1.getFirAnnotation(), annotation2.getFirAnnotation())
}
private fun areFirAnnotationsEqual(annotation1: FirAnnotation, annotation2: FirAnnotation): Boolean {
if (!areCompatibleExpectActualTypes(annotation1.typeRef.coneType, annotation2.typeRef.coneType)) {
return false
}
val args1 = annotation1.argumentMapping.mapping
val args2 = annotation2.argumentMapping.mapping
if (args1.size != args2.size) {
return false
}
return args1.all { (key, value1) ->
val value2 = args2[key]
value2 != null && areAnnotationArgumentsEqual(value1, value2)
}
}
private fun areAnnotationArgumentsEqual(expression1: FirExpression, expression2: FirExpression): Boolean {
// In K2 const expression calculated in backend.
// Because of that, we have "honest" checker at backend IR stage
// and "only simplest case" checker in frontend, so that we have at least some reporting in the IDE.
return when {
expression1 is FirConstExpression<*> && expression2 is FirConstExpression<*> -> {
expression1.value == expression2.value
}
else -> true
}
}
private inner class AnnotationCallInfoImpl(val annotation: FirAnnotation) : AnnotationCallInfo {
override val classId: ClassId?
get() = annotation.toAnnotationClassId(actualSession)
}
object Factory : FirExpectActualMatchingContextFactory {
override fun create(session: FirSession, scopeSession: ScopeSession): FirExpectActualMatchingContextImpl =
FirExpectActualMatchingContextImpl(session, scopeSession)
@@ -840,6 +840,8 @@ 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);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -396,6 +396,11 @@ public class DefaultErrorMessages {
MAP.put(ACTUAL_TYPEALIAS_TO_SPECIAL_ANNOTATION,
"`actual typealias` to annotation which affects code compilation can lead to incorrect behavior. Instead, use ''{0}'' annotation directly.",
TO_STRING);
MAP.put(ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT,
"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);
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");
@@ -32,8 +32,10 @@ import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.platform.isCommon
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.renderer.AnnotationArgumentsRenderingPolicy
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.renderer.DescriptorRenderer.Companion.DEBUG_TEXT
import org.jetbrains.kotlin.renderer.DescriptorRendererModifier
import org.jetbrains.kotlin.renderer.PropertyAccessorRenderingPolicy
import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.calls.inference.*
@@ -708,6 +710,14 @@ object Renderers {
val SHORT_NAMES_IN_TYPES = DescriptorRenderer.SHORT_NAMES_IN_TYPES.asRenderer()
@JvmField
val COMPACT_WITH_MODIFIERS = DescriptorRenderer.COMPACT_WITH_MODIFIERS.asRenderer()
@JvmField
val DESCRIPTOR_WITH_ANNOTATIONS = DescriptorRenderer.withOptions {
annotationArgumentsRenderingPolicy = AnnotationArgumentsRenderingPolicy.UNLESS_EMPTY
modifiers = modifiers.plus(DescriptorRendererModifier.ANNOTATIONS)
withDefinedIn = true
}.asRenderer()
@JvmField
val DEPRECATION_RENDERER = DescriptorRenderer.ONLY_NAMES_WITH_SHORT_TYPES.withOptions {
withoutTypeParameters = false
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.incremental.components.ExpectActualTracker
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier
import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.calls.mpp.AbstractExpectActualAnnotationMatchChecker
import org.jetbrains.kotlin.resolve.constants.ConstantValue
import org.jetbrains.kotlin.resolve.descriptorUtil.isAnnotationConstructor
import org.jetbrains.kotlin.resolve.descriptorUtil.isPrimaryConstructorOfInlineClass
@@ -338,9 +339,11 @@ class ExpectedActualDeclarationChecker(
checkOptInAnnotation(reportOn, descriptor, expected, trace)
}
}
val expectSingleCandidate = compatibility.values.singleOrNull()?.singleOrNull()
// We want to report errors even if a candidate is incompatible, but it's single
val expectSingleCandidate = (compatibility[Compatible] ?: compatibility.values.singleOrNull())?.singleOrNull()
if (expectSingleCandidate != null) {
checkIfExpectHasDefaultArgumentsAndActualizedWithTypealias(expectSingleCandidate, reportOn, trace)
checkAnnotationsMatch(expectSingleCandidate, descriptor, reportOn, trace)
}
}
@@ -443,6 +446,24 @@ class ExpectedActualDeclarationChecker(
}
}
private fun checkAnnotationsMatch(
expectDescriptor: MemberDescriptor,
actualDescriptor: MemberDescriptor,
reportOn: KtNamedDeclaration,
trace: BindingTrace
) {
val context = ClassicExpectActualMatchingContext(actualDescriptor.module)
val incompatibility =
AbstractExpectActualAnnotationMatchChecker.areAnnotationsCompatible(expectDescriptor, actualDescriptor, context) ?: return
trace.report(
Errors.ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT.on(
reportOn,
incompatibility.expectSymbol as DeclarationDescriptor,
incompatibility.actualSymbol as DeclarationDescriptor
)
)
}
companion object {
fun Map<out ExpectActualCompatibility<MemberDescriptor>, Collection<MemberDescriptor>>.allStrongIncompatibilities(): Boolean =
this.keys.all { it is Incompatible && it.kind == IncompatibilityKind.STRONG }
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
@@ -25,7 +26,10 @@ import org.jetbrains.kotlin.resolve.calls.mpp.ExpectActualMatchingContext
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.TypeCheckerState
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.types.model.*
import org.jetbrains.kotlin.types.model.AnnotationMarker
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
import org.jetbrains.kotlin.types.model.TypeSubstitutorMarker
import org.jetbrains.kotlin.types.model.TypeSystemContext
import org.jetbrains.kotlin.utils.addToStdlib.UnsafeCastFunction
import org.jetbrains.kotlin.utils.addToStdlib.castAll
import org.jetbrains.kotlin.utils.addToStdlib.shouldNotBeCalled
@@ -75,6 +79,7 @@ internal abstract class IrExpectActualMatchingContext(
private fun TypeParameterSymbolMarker.asIr(): IrTypeParameter = (this as IrTypeParameterSymbol).owner
private fun RegularClassSymbolMarker.asIr(): IrClass = (this as IrClassSymbol).owner
private fun TypeAliasSymbolMarker.asIr(): IrTypeAlias = (this as IrTypeAliasSymbol).owner
private fun AnnotationMarker.asIr(): IrConstructorCall = this as IrConstructorCall
private inline fun <reified T : IrDeclaration> DeclarationSymbolMarker.safeAsIr(): T? = (this as IrSymbol).owner as? T
@@ -440,4 +445,13 @@ internal abstract class IrExpectActualMatchingContext(
abstract fun onMatchedClasses(expectClassSymbol: IrClassSymbol, actualClassSymbol: IrClassSymbol)
abstract fun onMatchedCallables(expectSymbol: IrSymbol, actualSymbol: IrSymbol)
override val DeclarationSymbolMarker.annotations: List<ExpectActualMatchingContext.AnnotationCallInfo>
// TODO(Roman.Efremov): implement in subsequent commits
get() = emptyList()
override fun areAnnotationArgumentsEqual(annotation1: ExpectActualMatchingContext.AnnotationCallInfo, annotation2: ExpectActualMatchingContext.AnnotationCallInfo): Boolean {
// TODO(Roman.Efremov): implement in subsequent commits
return true
}
}
@@ -0,0 +1,40 @@
/*
* 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.resolve.calls.mpp
import org.jetbrains.kotlin.mpp.DeclarationSymbolMarker
import org.jetbrains.kotlin.mpp.TypeAliasSymbolMarker
object AbstractExpectActualAnnotationMatchChecker {
class Incompatibility(val expectSymbol: DeclarationSymbolMarker, val actualSymbol: DeclarationSymbolMarker)
fun areAnnotationsCompatible(
expectSymbol: DeclarationSymbolMarker,
actualSymbol: DeclarationSymbolMarker,
context: ExpectActualMatchingContext<*>,
): Incompatibility? = with(context) { areAnnotationsCompatible(expectSymbol, actualSymbol) }
context (ExpectActualMatchingContext<*>)
private fun areAnnotationsCompatible(
expectSymbol: DeclarationSymbolMarker,
actualSymbol: DeclarationSymbolMarker,
): Incompatibility? {
if (actualSymbol is TypeAliasSymbolMarker) {
val expanded = actualSymbol.expandToRegularClass() ?: return null
return areAnnotationsCompatible(expectSymbol, expanded)
}
val actualAnnotationsByName = actualSymbol.annotations.groupBy { it.classId }
for (expectAnnotation in expectSymbol.annotations) {
val actualAnnotationsWithSameClassId = actualAnnotationsByName[expectAnnotation.classId] ?: emptyList()
if (actualAnnotationsWithSameClassId.none { areAnnotationArgumentsEqual(expectAnnotation, it) }) {
return Incompatibility(expectSymbol, actualSymbol)
}
}
return null
}
}
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.mpp.*
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.resolve.multiplatform.ExpectActualCompatibility
import org.jetbrains.kotlin.types.Variance
@@ -157,4 +158,12 @@ interface ExpectActualMatchingContext<T : DeclarationSymbolMarker> : TypeSystemC
expectSymbol: DeclarationSymbolMarker,
actualSymbolsByIncompatibility: Map<ExpectActualCompatibility.Incompatible<*>, List<DeclarationSymbolMarker>>
) {}
val DeclarationSymbolMarker.annotations: List<AnnotationCallInfo>
fun areAnnotationArgumentsEqual(annotation1: AnnotationCallInfo, annotation2: AnnotationCallInfo): Boolean
interface AnnotationCallInfo {
val classId: ClassId?
}
}
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.resolve.multiplatform
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.mpp.*
import org.jetbrains.kotlin.name.CallableId
@@ -14,9 +15,8 @@ import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.components.ClassicTypeSystemContextForCS
import org.jetbrains.kotlin.resolve.calls.mpp.ExpectActualMatchingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.resolve.descriptorUtil.getKotlinTypeRefiner
import org.jetbrains.kotlin.resolve.descriptorUtil.isTypeRefinementEnabled
import org.jetbrains.kotlin.resolve.calls.mpp.ExpectActualMatchingContext.AnnotationCallInfo
import org.jetbrains.kotlin.resolve.descriptorUtil.*
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
@@ -35,6 +35,7 @@ class ClassicExpectActualMatchingContext(val platformModule: ModuleDescriptor) :
override val shouldCheckReturnTypesOfCallables: Boolean
get() = true
private fun DeclarationSymbolMarker.asDescriptor(): DeclarationDescriptor = this as DeclarationDescriptor
private fun CallableSymbolMarker.asDescriptor(): CallableDescriptor = this as CallableDescriptor
private fun FunctionSymbolMarker.asDescriptor(): FunctionDescriptor = this as FunctionDescriptor
private fun PropertySymbolMarker.asDescriptor(): PropertyDescriptor = this as PropertyDescriptor
@@ -328,4 +329,21 @@ class ClassicExpectActualMatchingContext(val platformModule: ModuleDescriptor) :
override val CallableSymbolMarker.hasStableParameterNames: Boolean
get() = asDescriptor().hasStableParameterNames()
override val DeclarationSymbolMarker.annotations: List<AnnotationCallInfo>
get() = asDescriptor().annotations.map(::AnnotationCallInfoImpl)
override fun areAnnotationArgumentsEqual(annotation1: AnnotationCallInfo, annotation2: AnnotationCallInfo): Boolean {
fun AnnotationCallInfo.getDescriptor(): AnnotationDescriptor = (this as AnnotationCallInfoImpl).annotationDescriptor
return areExpressionConstValuesEqual(annotation1.getDescriptor(), annotation2.getDescriptor())
}
private class AnnotationCallInfoImpl(
val annotationDescriptor: AnnotationDescriptor,
) : AnnotationCallInfo {
override val classId: ClassId?
get() = annotationDescriptor.annotationClass?.classId
}
}
@@ -0,0 +1,33 @@
/*
* 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.resolve.multiplatform
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.resolve.calls.mpp.ExpectActualMatchingContext
import org.jetbrains.kotlin.resolve.constants.ConstantValue
internal fun ExpectActualMatchingContext<*>.areExpressionConstValuesEqual(a: Any?, b: Any?): Boolean {
return when {
a is AnnotationDescriptor && b is AnnotationDescriptor -> {
val aArgs = a.allValueArguments
val bArgs = b.allValueArguments
a.fqName == b.fqName &&
aArgs.size == bArgs.size &&
areCompatibleExpectActualTypes(a.type, b.type) &&
aArgs.keys.all { k -> areExpressionConstValuesEqual(aArgs[k], bArgs[k]) }
}
a is ConstantValue<*> && b is ConstantValue<*> -> {
areExpressionConstValuesEqual(a.value, b.value)
}
a is Collection<*> && b is Collection<*> -> {
a.size == b.size && a.zip(b).all { (f, s) -> areExpressionConstValuesEqual(f, s) }
}
a is Array<*> && b is Array<*> -> {
a.size == b.size && a.zip(b).all { (f, s) -> areExpressionConstValuesEqual(f, s) }
}
else -> a == b
}
}
@@ -0,0 +1,138 @@
// WITH_STDLIB
// MODULE: m1-common
// FILE: common.kt
package test
import kotlin.reflect.KClass
annotation class ClassArgAnn(val clazz: KClass<*>)
class ClassForReference {
class ClassForReference
}
@ClassArgAnn(ClassForReference::class)
expect fun getClassExpression()
@ClassArgAnn(ClassForReference.ClassForReference::class)
expect fun differentClassesWithSameName()
annotation class StringArgAnn(val s: String)
@StringArgAnn("1.9")
expect fun stringConstant()
@StringArgAnn("1" + ".9")
expect fun stringConcatentation()
object Constants {
const val STR = "1"
}
@StringArgAnn(Constants.STR)
expect fun constantFromInsideObject()
@StringArgAnn(Constants.STR + ".9")
expect fun stringConcatentationWithProperty()
enum class MyEnum { FOO, BAR }
annotation class EnumArgAnn(val e: MyEnum)
@EnumArgAnn(MyEnum.FOO)
expect fun enumArg()
annotation class VarargAnn(vararg val strings: String)
@VarargAnn("foo", "bar")
expect fun varargInAnnotation()
@VarargAnn(*["foo", "bar"])
expect fun varargInAnnotationWithArraySpread()
annotation class ArrayArgAnn(val strings: Array<String>)
@ArrayArgAnn(["foo", "bar"])
expect fun arrayInAnnotation()
@ArrayArgAnn(["foo", "bar"])
expect fun arrayInAnnotationNotMatch()
annotation class NestedAnnArg(val text: String, vararg val children: NestedAnnArg)
@NestedAnnArg(
text = "root",
NestedAnnArg("1"),
NestedAnnArg("2",
NestedAnnArg("2.1"),
NestedAnnArg("2.2")
)
)
expect fun complexNestedAnnotations()
@NestedAnnArg(
text = "root",
NestedAnnArg("1"),
NestedAnnArg("2",
NestedAnnArg("2.1"),
NestedAnnArg("2.2")
)
)
expect fun complexNestedAnnotationsNotMatch()
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
package test
@ClassArgAnn(ClassForReference::class)
actual fun getClassExpression() {}
@ClassArgAnn(ClassForReference::class)
actual fun differentClassesWithSameName() {}
@StringArgAnn("1.9")
actual fun stringConstant() {}
@StringArgAnn("1.9")
actual fun stringConcatentation() {}
@StringArgAnn("1")
actual fun constantFromInsideObject() {}
@StringArgAnn("1.9")
actual fun stringConcatentationWithProperty() {}
@EnumArgAnn(MyEnum.FOO)
actual fun enumArg() {}
@VarargAnn("foo", "bar")
actual fun varargInAnnotation() {}
@VarargAnn("foo", "bar")
actual fun varargInAnnotationWithArraySpread() {}
@ArrayArgAnn(arrayOf("foo", "bar"))
actual fun arrayInAnnotation() {}
@ArrayArgAnn(["foo"])
actual fun arrayInAnnotationNotMatch() {}
@NestedAnnArg(
text = "root",
NestedAnnArg("1"),
NestedAnnArg("2",
NestedAnnArg("2.1"),
NestedAnnArg("2.2")
)
)
actual fun complexNestedAnnotations() {}
@NestedAnnArg(
text = "root",
NestedAnnArg("1"),
NestedAnnArg("2",
NestedAnnArg("2.1"),
NestedAnnArg("DIFFERENT")
)
)
actual fun complexNestedAnnotationsNotMatch() {}
@@ -0,0 +1,138 @@
// WITH_STDLIB
// MODULE: m1-common
// FILE: common.kt
package test
import kotlin.reflect.KClass
annotation class ClassArgAnn(val clazz: KClass<*>)
class ClassForReference {
class ClassForReference
}
@ClassArgAnn(ClassForReference::class)
expect fun getClassExpression()
@ClassArgAnn(ClassForReference.ClassForReference::class)
expect fun differentClassesWithSameName()
annotation class StringArgAnn(val s: String)
@StringArgAnn("1.9")
expect fun stringConstant()
@StringArgAnn("1" + ".9")
expect fun stringConcatentation()
object Constants {
const val STR = "1"
}
@StringArgAnn(Constants.STR)
expect fun constantFromInsideObject()
@StringArgAnn(Constants.STR + ".9")
expect fun stringConcatentationWithProperty()
enum class MyEnum { FOO, BAR }
annotation class EnumArgAnn(val e: MyEnum)
@EnumArgAnn(MyEnum.FOO)
expect fun enumArg()
annotation class VarargAnn(vararg val strings: String)
@VarargAnn("foo", "bar")
expect fun varargInAnnotation()
@VarargAnn(*["foo", "bar"])
expect fun varargInAnnotationWithArraySpread()
annotation class ArrayArgAnn(val strings: Array<String>)
@ArrayArgAnn(["foo", "bar"])
expect fun arrayInAnnotation()
@ArrayArgAnn(["foo", "bar"])
expect fun arrayInAnnotationNotMatch()
annotation class NestedAnnArg(val text: String, vararg val children: NestedAnnArg)
@NestedAnnArg(
text = "root",
NestedAnnArg("1"),
NestedAnnArg("2",
NestedAnnArg("2.1"),
NestedAnnArg("2.2")
)
)
expect fun complexNestedAnnotations()
@NestedAnnArg(
text = "root",
NestedAnnArg("1"),
NestedAnnArg("2",
NestedAnnArg("2.1"),
NestedAnnArg("2.2")
)
)
expect fun complexNestedAnnotationsNotMatch()
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
package test
@ClassArgAnn(ClassForReference::class)
actual fun getClassExpression() {}
@ClassArgAnn(ClassForReference::class)
actual fun <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>differentClassesWithSameName<!>() {}
@StringArgAnn("1.9")
actual fun stringConstant() {}
@StringArgAnn("1.9")
actual fun stringConcatentation() {}
@StringArgAnn("1")
actual fun constantFromInsideObject() {}
@StringArgAnn("1.9")
actual fun stringConcatentationWithProperty() {}
@EnumArgAnn(MyEnum.FOO)
actual fun enumArg() {}
@VarargAnn("foo", "bar")
actual fun varargInAnnotation() {}
@VarargAnn("foo", "bar")
actual fun varargInAnnotationWithArraySpread() {}
@ArrayArgAnn(arrayOf("foo", "bar"))
actual fun arrayInAnnotation() {}
@ArrayArgAnn(["foo"])
actual fun <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>arrayInAnnotationNotMatch<!>() {}
@NestedAnnArg(
text = "root",
NestedAnnArg("1"),
NestedAnnArg("2",
NestedAnnArg("2.1"),
NestedAnnArg("2.2")
)
)
actual fun complexNestedAnnotations() {}
@NestedAnnArg(
text = "root",
NestedAnnArg("1"),
NestedAnnArg("2",
NestedAnnArg("2.1"),
NestedAnnArg("DIFFERENT")
)
)
actual fun <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>complexNestedAnnotationsNotMatch<!>() {}
@@ -0,0 +1,12 @@
// FIR_IDENTICAL
// MODULE: m1-common
// FILE: common.kt
annotation class Ann(val p: String = "")
@Ann("")
expect fun explicitDefaultArgument()
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
// No special handling for this case
@Ann
actual fun <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>explicitDefaultArgument<!>() {}
@@ -0,0 +1,101 @@
// MODULE: m1-common
// FILE: common.kt
annotation class Ann<T>
class A
class B
private object FakeA {
class A
}
@Ann<A>
expect fun sameTypeParam()
@Ann<B>
expect fun differentTypeParam()
@Ann<FakeA.A>
expect fun differentWithSameName()
@Ann<A?>
expect fun nonNullvsNull()
@Ann<Ann<in A>>
expect fun differentVariance()
@Ann<Ann<in A>>
expect fun varianceVsNoVariance()
@Ann<Ann<in A>>
expect fun sameVariance()
@Ann<Ann<*>>
expect fun startProjection()
annotation class ComplexNested<T>(
vararg val anns: ComplexNested<*>,
)
@ComplexNested<A>(
ComplexNested<A>(),
ComplexNested<A>(),
)
expect fun complexSame()
@ComplexNested<A>(
ComplexNested<A>(),
ComplexNested<B>(),
)
expect fun complexDiffer()
annotation class NestedWithSameTypeArgument<T>(
vararg val anns: NestedWithSameTypeArgument<T>
)
@NestedWithSameTypeArgument<A>(
NestedWithSameTypeArgument()
)
expect fun explicitVsInfered()
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
@Ann<A>
actual fun sameTypeParam() {}
@Ann<A>
actual fun <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT, ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>differentTypeParam<!>() {}
@Ann<A>
actual fun <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT, ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>differentWithSameName<!>() {}
@Ann<A>
actual fun <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT, ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>nonNullvsNull<!>() {}
@Ann<Ann<out A>>
actual fun <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT, ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>differentVariance<!>() {}
@Ann<Ann<A>>
actual fun <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT, ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>varianceVsNoVariance<!>() {}
@Ann<Ann<in A>>
actual fun sameVariance() {}
@Ann<Ann<Any>>
actual fun <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT, ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>startProjection<!>() {}
@ComplexNested<A>(
ComplexNested<A>(),
ComplexNested<A>(),
)
actual fun complexSame() {}
@ComplexNested<A>(
ComplexNested<A>(),
ComplexNested<A>(),
)
actual fun <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>complexDiffer<!>() {}
@NestedWithSameTypeArgument<A>(
NestedWithSameTypeArgument<A>()
)
actual fun explicitVsInfered() {}
@@ -0,0 +1,101 @@
// MODULE: m1-common
// FILE: common.kt
annotation class Ann<T>
class A
class B
private object FakeA {
class A
}
@Ann<A>
expect fun sameTypeParam()
@Ann<B>
expect fun differentTypeParam()
@Ann<FakeA.A>
expect fun differentWithSameName()
@Ann<A?>
expect fun nonNullvsNull()
@Ann<Ann<in A>>
expect fun differentVariance()
@Ann<Ann<in A>>
expect fun varianceVsNoVariance()
@Ann<Ann<in A>>
expect fun sameVariance()
@Ann<Ann<*>>
expect fun startProjection()
annotation class ComplexNested<T>(
vararg val anns: ComplexNested<*>,
)
@ComplexNested<A>(
ComplexNested<A>(),
ComplexNested<A>(),
)
expect fun complexSame()
@ComplexNested<A>(
ComplexNested<A>(),
ComplexNested<B>(),
)
expect fun complexDiffer()
annotation class NestedWithSameTypeArgument<T>(
vararg val anns: NestedWithSameTypeArgument<T>
)
@NestedWithSameTypeArgument<A>(
NestedWithSameTypeArgument()
)
expect fun explicitVsInfered()
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
@Ann<A>
actual fun sameTypeParam() {}
@Ann<A>
actual fun <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>differentTypeParam<!>() {}
@Ann<A>
actual fun <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>differentWithSameName<!>() {}
@Ann<A>
actual fun <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>nonNullvsNull<!>() {}
@Ann<Ann<out A>>
actual fun <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>differentVariance<!>() {}
@Ann<Ann<A>>
actual fun <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>varianceVsNoVariance<!>() {}
@Ann<Ann<in A>>
actual fun sameVariance() {}
@Ann<Ann<Any>>
actual fun <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>startProjection<!>() {}
@ComplexNested<A>(
ComplexNested<A>(),
ComplexNested<A>(),
)
actual fun complexSame() {}
@ComplexNested<A>(
ComplexNested<A>(),
ComplexNested<A>(),
)
actual fun complexDiffer() {}
@NestedWithSameTypeArgument<A>(
NestedWithSameTypeArgument<A>()
)
actual fun explicitVsInfered() {}
@@ -0,0 +1,42 @@
// FIR_IDENTICAL
// MODULE: m1-common
// FILE: common.kt
annotation class Ann
@Ann
expect class AnnotationMatching
@Ann
expect class AnnotationOnExpectOnly
expect class AnnotationOnActualOnly
expect class AnnotationInside {
@Ann
fun matches()
@Ann
fun onlyOnExpect()
fun onlyOnActual()
}
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
@Ann
actual class AnnotationMatching
actual class <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>AnnotationOnExpectOnly<!>
@Ann
actual class AnnotationOnActualOnly
actual class AnnotationInside {
@Ann
actual fun matches() {}
actual fun <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>onlyOnExpect<!>() {}
@Ann
actual fun onlyOnActual() {}
}
@@ -0,0 +1,19 @@
// -- Module: <m1-common> --
// -- Module: <m1-jvm> --
/jvm.kt:24:14: warning: all annotations from `expect` must be presented with same arguments on `actual`, otherwise they have no effect.
Expected: @Ann public final expect class OnClass defined in root package in file common.kt
Actual: public final actual class OnClass defined in root package in file jvm.kt
actual class OnClass
^
/jvm.kt:27:16: warning: all annotations from `expect` must be presented with same arguments on `actual`, otherwise they have no effect.
Expected: @Ann public final expect fun onMember(): Unit defined in OnMember
Actual: public final actual fun onMember(): Unit defined in OnMember
actual fun onMember() {}
^
/jvm.kt:34:18: warning: all annotations from `expect` must be presented with same arguments on `actual`, otherwise they have no effect.
Expected: @Ann public final expect class ViaTypealias defined in root package in file common.kt
Actual: public final class ViaTypealiasImpl defined in root package in file jvm.kt
actual typealias ViaTypealias = ViaTypealiasImpl
^
@@ -0,0 +1,11 @@
/jvm.kt:(83,90): warning: All annotations from `expect` must be presented with same arguments on `actual`, otherwise they have no effect.
Expected: @Ann() public final expect class OnClass : Any
Actual: public final actual class OnClass : Any
/jvm.kt:(131,139): warning: All annotations from `expect` must be presented with same arguments on `actual`, otherwise they have no effect.
Expected: @Ann() public final expect fun onMember(): Unit
Actual: public final actual fun onMember(): Unit
/jvm.kt:(210,222): warning: All annotations from `expect` must be presented with same arguments on `actual`, otherwise they have no effect.
Expected: @Ann() public final expect class ViaTypealias : Any
Actual: public final class ViaTypealiasImpl : Any
@@ -0,0 +1,34 @@
// FIR_IDENTICAL
// RENDER_DIAGNOSTICS_FULL_TEXT
// MODULE: m1-common
// FILE: common.kt
annotation class Ann
@Ann
expect class OnClass
expect class OnMember {
@Ann
fun onMember()
}
@Ann
expect class ViaTypealias {
@Ann
fun foo()
}
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
actual class <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>OnClass<!>
actual class OnMember {
actual fun <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>onMember<!>() {}
}
class ViaTypealiasImpl {
fun foo() {}
}
actual typealias <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>ViaTypealias<!> = ViaTypealiasImpl
@@ -0,0 +1,19 @@
// FIR_IDENTICAL
// MODULE: m1-common
// FILE: common.kt
annotation class Ann
expect class CompatibleOverrides {
fun foo()
@Ann
fun foo(withArg: Any)
}
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
actual class CompatibleOverrides {
actual fun foo() {}
actual fun <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>foo<!>(withArg: Any) {}
}
@@ -0,0 +1,31 @@
// MODULE: m1-common
// FILE: common.kt
annotation class Ann1
annotation class Ann2
@Ann1
@Ann2
expect class AnnotationOrder
annotation class Ann3(vararg val numbers: Int)
@Ann3(1, 2)
expect class ValuesOrderInsideAnnotationArgument
annotation class Ann4(val arg1: String, val arg2: String)
@Ann4(arg1 = "1", arg2 = "2")
expect fun differentArgumentsOrder()
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
@Ann2
@Ann1
actual class AnnotationOrder
@Ann3(2, 1)
actual class ValuesOrderInsideAnnotationArgument
@Ann4(arg2 = "2", arg1 = "1")
actual fun differentArgumentsOrder() {}
@@ -0,0 +1,31 @@
// MODULE: m1-common
// FILE: common.kt
annotation class Ann1
annotation class Ann2
@Ann1
@Ann2
expect class AnnotationOrder
annotation class Ann3(vararg val numbers: Int)
@Ann3(1, 2)
expect class ValuesOrderInsideAnnotationArgument
annotation class Ann4(val arg1: String, val arg2: String)
@Ann4(arg1 = "1", arg2 = "2")
expect fun differentArgumentsOrder()
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
@Ann2
@Ann1
actual class AnnotationOrder
@Ann3(2, 1)
actual class <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>ValuesOrderInsideAnnotationArgument<!>
@Ann4(arg2 = "2", arg1 = "1")
actual fun differentArgumentsOrder() {}
@@ -0,0 +1,11 @@
// MODULE: m1-common
// FILE: common.kt
annotation class Ann(val p: Double)
@Ann(0.3)
expect fun floatNumbersComparison()
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
@Ann(0.1 + 0.1 + 0.1)
actual fun floatNumbersComparison() {}
@@ -0,0 +1,11 @@
// MODULE: m1-common
// FILE: common.kt
annotation class Ann(val p: Double)
@Ann(0.3)
expect fun floatNumbersComparison()
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
@Ann(0.1 + 0.1 + 0.1)
actual fun <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>floatNumbersComparison<!>() {}
@@ -0,0 +1,35 @@
// FIR_IDENTICAL
// WITH_STDLIB
// MODULE: m1-common
// FILE: common.kt
import kotlin.reflect.KClass
annotation class Ann(val clazz: KClass<*>)
@Ann(LinkToExpectInnerClass.Inner::class)
expect class LinkToExpectInnerClass {
object Inner
}
expect class WillBeTypealiased
@Ann(WillBeTypealiased::class)
expect fun linkToExpectClassWhichWillBeTypealiased()
@Ann(WillBeTypealiased::class)
expect fun linkToExpectClassWhichWillBeTypealiased2()
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
@Ann(LinkToExpectInnerClass.Inner::class)
actual class LinkToExpectInnerClass {
actual object Inner
}
actual typealias WillBeTypealiased = Any
@Ann(WillBeTypealiased::class)
actual fun linkToExpectClassWhichWillBeTypealiased() {}
@Ann(Any::class)
actual fun linkToExpectClassWhichWillBeTypealiased2() {}
@@ -0,0 +1,29 @@
// FIR_IDENTICAL
// WITH_STDLIB
// MODULE: m1-common
// FILE: common.kt
@Retention(AnnotationRetention.RUNTIME)
expect annotation class JavaTypealiasAnnotationAnalogue
@Retention(AnnotationRetention.RUNTIME)
expect annotation class JavaTypealiasKotlinAnnotation
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
actual typealias JavaTypealiasAnnotationAnalogue = JavaTypealiasAnnotationAnalogueImpl
actual typealias JavaTypealiasKotlinAnnotation = JavaTypealiasKotlinAnnotationImpl
// FILE: jvmJavaImpls.java
import kotlin.annotation.AnnotationRetention;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface JavaTypealiasAnnotationAnalogueImpl {
}
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
public @interface JavaTypealiasKotlinAnnotationImpl {
}
@@ -0,0 +1,23 @@
// FIR_IDENTICAL
// WITH_STDLIB
// MODULE: m1-common
// FILE: common.kt
@Target(AnnotationTarget.TYPEALIAS, AnnotationTarget.CLASS)
annotation class Ann
@Ann
expect class KtTypealiasNotMatch
@Ann
expect class AnnotationsNotConsideredOnTypealias
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
class KtTypealiasNotMatchImpl
actual typealias <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>KtTypealiasNotMatch<!> = KtTypealiasNotMatchImpl
class AnnotationsNotConsideredOnTypealiasImpl
@Ann
actual typealias <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>AnnotationsNotConsideredOnTypealias<!> = AnnotationsNotConsideredOnTypealiasImpl
@@ -0,0 +1,14 @@
// WITH_STDLIB
// MODULE: m1-common
// FILE: common.kt
@Retention(AnnotationRetention.SOURCE)
expect annotation class MyDeprecatedNotMatch
@Retention(AnnotationRetention.RUNTIME)
expect annotation class MyDeprecatedMatch
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
actual typealias MyDeprecatedNotMatch = java.lang.Deprecated
actual typealias MyDeprecatedMatch = java.lang.Deprecated
@@ -0,0 +1,14 @@
// WITH_STDLIB
// MODULE: m1-common
// FILE: common.kt
@Retention(AnnotationRetention.SOURCE)
expect annotation class MyDeprecatedNotMatch
@Retention(AnnotationRetention.RUNTIME)
expect annotation class MyDeprecatedMatch
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
actual typealias <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>MyDeprecatedNotMatch<!> = java.lang.Deprecated
actual typealias MyDeprecatedMatch = java.lang.Deprecated
@@ -0,0 +1,32 @@
// WITH_STDLIB
// MODULE: m1-common
// FILE: common.kt
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY,
AnnotationTarget.ANNOTATION_CLASS,
AnnotationTarget.CONSTRUCTOR,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.PROPERTY_GETTER,
// removed target TYPEALIAS
)
expect annotation class MyDeprecatedNotMatch
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY,
AnnotationTarget.ANNOTATION_CLASS,
AnnotationTarget.CONSTRUCTOR,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.TYPEALIAS
)
expect annotation class MyDeprecatedMatch
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
actual typealias MyDeprecatedNotMatch = kotlin.Deprecated
actual typealias MyDeprecatedMatch = kotlin.Deprecated
@@ -0,0 +1,32 @@
// WITH_STDLIB
// MODULE: m1-common
// FILE: common.kt
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY,
AnnotationTarget.ANNOTATION_CLASS,
AnnotationTarget.CONSTRUCTOR,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.PROPERTY_GETTER,
// removed target TYPEALIAS
)
expect annotation class MyDeprecatedNotMatch
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY,
AnnotationTarget.ANNOTATION_CLASS,
AnnotationTarget.CONSTRUCTOR,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.TYPEALIAS
)
expect annotation class MyDeprecatedMatch
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
actual typealias <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>MyDeprecatedNotMatch<!> = kotlin.Deprecated
actual typealias MyDeprecatedMatch = kotlin.Deprecated
@@ -0,0 +1,19 @@
// MODULE: m1-common
// FILE: common.kt
annotation class Ann
<!INCOMPATIBLE_MATCHING{JVM}!>@Ann
expect inline fun hasWeakIncompatibility()<!>
<!INCOMPATIBLE_MATCHING{JVM}, INCOMPATIBLE_MATCHING{JVM}!>@Ann
expect fun hasStrongIncompatibility(arg: Int)<!>
expect fun hasStrongIncompatibility(arg: Double)
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
actual fun <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT, ACTUAL_WITHOUT_EXPECT!>hasWeakIncompatibility<!>() {}
actual fun <!ACTUAL_WITHOUT_EXPECT!>hasStrongIncompatibility<!>(arg: Any?) {}
actual fun hasStrongIncompatibility(arg: Double) {}
@@ -0,0 +1,19 @@
// MODULE: m1-common
// FILE: common.kt
annotation class Ann
@Ann
expect inline fun hasWeakIncompatibility()
@Ann
expect fun hasStrongIncompatibility<!NO_ACTUAL_FOR_EXPECT{JVM}!>(arg: Int)<!>
expect fun hasStrongIncompatibility(arg: Double)
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
<!ACTUAL_WITHOUT_EXPECT!>actual<!> fun <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>hasWeakIncompatibility<!>() {}
actual fun hasStrongIncompatibility<!ACTUAL_WITHOUT_EXPECT!>(arg: Any?)<!> {}
actual fun hasStrongIncompatibility(arg: Double) {}
@@ -1,12 +1,13 @@
// WITH_STDLIB
// MODULE: m1-common
// FILE: common.kt
@file:OptIn(ExperimentalMultiplatform::class)
expect annotation class ActualOnly
@RequiresOptIn
<!EXPECT_ACTUAL_OPT_IN_ANNOTATION!>expect<!> annotation class Both
@OptIn(ExperimentalMultiplatform::class)
@RequiresOptIn
@OptionalExpectation
expect annotation class MyOptIn
@@ -1,12 +1,13 @@
// WITH_STDLIB
// MODULE: m1-common
// FILE: common.kt
@file:OptIn(ExperimentalMultiplatform::class)
expect annotation class ActualOnly
@RequiresOptIn
<!EXPECT_ACTUAL_OPT_IN_ANNOTATION{JVM}!>expect<!> annotation class Both
@OptIn(ExperimentalMultiplatform::class)
@RequiresOptIn
@OptionalExpectation
expect annotation class MyOptIn
@@ -4,7 +4,7 @@
@OptionalExpectation
expect annotation class A()
expect class C {
class C {
@A
fun f()
fun f() {}
}
@@ -1,3 +0,0 @@
actual class C {
actual fun f() {}
}
@@ -1,6 +1 @@
actual annotation class A
actual class C {
@A
actual fun f() {}
}
@@ -22750,6 +22750,100 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
runTest("compiler/testData/diagnostics/tests/multiplatform/widerVisibilityInActualClassifier.kt");
}
@Nested
@TestMetadata("compiler/testData/diagnostics/tests/multiplatform/annotationMatching")
@TestDataPath("$PROJECT_ROOT")
public class AnnotationMatching {
@Test
public void testAllFilesPresentInAnnotationMatching() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/multiplatform/annotationMatching"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.(reversed|fir|ll)\\.kts?$"), true);
}
@Test
@TestMetadata("annotationArgumentsConstExpressions.kt")
public void testAnnotationArgumentsConstExpressions() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/annotationArgumentsConstExpressions.kt");
}
@Test
@TestMetadata("annotationArgumentsDefaults.kt")
public void testAnnotationArgumentsDefaults() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/annotationArgumentsDefaults.kt");
}
@Test
@TestMetadata("annotationTypeParameters.kt")
public void testAnnotationTypeParameters() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/annotationTypeParameters.kt");
}
@Test
@TestMetadata("basicOnDeclaration.kt")
public void testBasicOnDeclaration() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/basicOnDeclaration.kt");
}
@Test
@TestMetadata("checkDiagnosticFullText.kt")
public void testCheckDiagnosticFullText() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/checkDiagnosticFullText.kt");
}
@Test
@TestMetadata("compatibleOverrides.kt")
public void testCompatibleOverrides() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/compatibleOverrides.kt");
}
@Test
@TestMetadata("differentOrder.kt")
public void testDifferentOrder() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/differentOrder.kt");
}
@Test
@TestMetadata("floatNumbersComparison.kt")
public void testFloatNumbersComparison() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/floatNumbersComparison.kt");
}
@Test
@TestMetadata("kclassArgWithExpectClass.kt")
public void testKclassArgWithExpectClass() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/kclassArgWithExpectClass.kt");
}
@Test
@TestMetadata("kotlinAnaloguesForJavaAnnotations.kt")
public void testKotlinAnaloguesForJavaAnnotations() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/kotlinAnaloguesForJavaAnnotations.kt");
}
@Test
@TestMetadata("typealias.kt")
public void testTypealias() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typealias.kt");
}
@Test
@TestMetadata("typealiasToJavaLibrary.kt")
public void testTypealiasToJavaLibrary() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typealiasToJavaLibrary.kt");
}
@Test
@TestMetadata("typealiasToKtLibrary.kt")
public void testTypealiasToKtLibrary() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/typealiasToKtLibrary.kt");
}
@Test
@TestMetadata("withOtherIncomatibilities.kt")
public void testWithOtherIncomatibilities() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/withOtherIncomatibilities.kt");
}
}
@Nested
@TestMetadata("compiler/testData/diagnostics/tests/multiplatform/defaultArguments")
@TestDataPath("$PROJECT_ROOT")