[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
@@ -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")