[FIR] Add NativeObjCRefinement checkers

This commit is contained in:
Rick Clephas
2022-08-19 16:46:36 +02:00
committed by SvyatoslavScherbina
parent 203af3afdd
commit 0a8cefc8a5
10 changed files with 315 additions and 4 deletions
@@ -36,6 +36,12 @@ public class FirOldFrontendNativeDiagnosticsTestGenerated extends AbstractFirNat
runTest("compiler/testData/diagnostics/nativeTests/objCName.kt");
}
@Test
@TestMetadata("objCRefinement.kt")
public void testObjCRefinement() throws Exception {
runTest("compiler/testData/diagnostics/nativeTests/objCRefinement.kt");
}
@Test
@TestMetadata("sharedImmutable.kt")
public void testSharedImmutable() throws Exception {
@@ -36,6 +36,12 @@ public class FirOldFrontendNativeDiagnosticsWithLightTreeTestGenerated extends A
runTest("compiler/testData/diagnostics/nativeTests/objCName.kt");
}
@Test
@TestMetadata("objCRefinement.kt")
public void testObjCRefinement() throws Exception {
runTest("compiler/testData/diagnostics/nativeTests/objCRefinement.kt");
}
@Test
@TestMetadata("sharedImmutable.kt")
public void testSharedImmutable() throws Exception {
@@ -10,6 +10,8 @@ import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.fir.PrivateForInline
import org.jetbrains.kotlin.fir.checkers.generator.diagnostics.model.DiagnosticList
import org.jetbrains.kotlin.fir.checkers.generator.diagnostics.model.PositioningStrategy
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtDeclaration
@@ -39,5 +41,11 @@ object NATIVE_DIAGNOSTICS_LIST : DiagnosticList("FirNativeErrors") {
) {
parameter<String>("message")
}
val REDUNDANT_SWIFT_REFINEMENT by error<KtElement>()
val INCOMPATIBLE_OBJC_REFINEMENT_OVERRIDE by error<KtElement> {
parameter<FirBasedSymbol<*>>("declaration")
parameter<Collection<FirRegularClassSymbol>>("containingClasses")
}
val INVALID_OBJC_REFINEMENT_TARGETS by error<KtElement>()
}
}
}
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.diagnostics.*
import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies
import org.jetbrains.kotlin.diagnostics.rendering.RootDiagnosticRendererFactory
import org.jetbrains.kotlin.fir.analysis.diagnostics.*
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtDeclaration
@@ -32,6 +33,9 @@ object FirNativeErrors {
val INAPPLICABLE_THREAD_LOCAL by error0<KtElement>()
val INAPPLICABLE_THREAD_LOCAL_TOP_LEVEL by error0<KtElement>()
val INVALID_CHARACTERS_NATIVE by deprecationError1<PsiElement, String>(ProhibitInvalidCharsInNativeIdentifiers, SourceElementPositioningStrategies.NAME_IDENTIFIER)
val REDUNDANT_SWIFT_REFINEMENT by error0<KtElement>()
val INCOMPATIBLE_OBJC_REFINEMENT_OVERRIDE by error2<KtElement, FirBasedSymbol<*>, Collection<FirRegularClassSymbol>>()
val INVALID_OBJC_REFINEMENT_TARGETS by error0<KtElement>()
init {
RootDiagnosticRendererFactory.registerFactory(FirNativeErrorsDefaultMessages)
@@ -15,10 +15,13 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INAP
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INAPPLICABLE_SHARED_IMMUTABLE_TOP_LEVEL
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INAPPLICABLE_THREAD_LOCAL
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INAPPLICABLE_THREAD_LOCAL_TOP_LEVEL
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INCOMPATIBLE_OBJC_REFINEMENT_OVERRIDE
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INCOMPATIBLE_THROWS_INHERITED
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INCOMPATIBLE_THROWS_OVERRIDE
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INVALID_CHARACTERS_NATIVE
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INVALID_OBJC_REFINEMENT_TARGETS
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.MISSING_EXCEPTION_IN_THROWS_ON_SUSPEND
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.REDUNDANT_SWIFT_REFINEMENT
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.THROWS_LIST_EMPTY
object FirNativeErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
@@ -41,6 +44,17 @@ object FirNativeErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
)
map.put(INAPPLICABLE_THREAD_LOCAL_TOP_LEVEL, "@ThreadLocal is applicable only to top level declarations")
map.put(INVALID_CHARACTERS_NATIVE, "Name {0}", TO_STRING)
map.put(REDUNDANT_SWIFT_REFINEMENT, "An ObjC refined declaration can't also be refined in Swift")
map.put(
INCOMPATIBLE_OBJC_REFINEMENT_OVERRIDE,
"Refined declaration \"{0}\" overrides declarations with different or no refinement from {1}",
SYMBOL,
SYMBOLS
)
map.put(
INVALID_OBJC_REFINEMENT_TARGETS,
"Refines annotations are only applicable to annotations with targets FUNCTION and/or PROPERTY"
)
map.checkMissingMessages(FirNativeErrors)
}
@@ -0,0 +1,56 @@
/*
* Copyright 2010-2022 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.fir.analysis.native.checkers
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirRegularClassChecker
import org.jetbrains.kotlin.fir.analysis.checkers.getAllowedAnnotationTargets
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors
import org.jetbrains.kotlin.fir.analysis.native.checkers.FirNativeObjCRefinementChecker.hidesFromObjCClassId
import org.jetbrains.kotlin.fir.analysis.native.checkers.FirNativeObjCRefinementChecker.refinesInSwiftClassId
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
object FirNativeObjCRefinementAnnotationChecker : FirRegularClassChecker() {
private val supportedTargets = arrayOf(KotlinTarget.FUNCTION, KotlinTarget.PROPERTY)
override fun check(declaration: FirRegularClass, context: CheckerContext, reporter: DiagnosticReporter) {
if (declaration.classKind != ClassKind.ANNOTATION_CLASS) return
val (objCAnnotation, swiftAnnotation) = declaration.findMetaAnnotations()
if (objCAnnotation == null && swiftAnnotation == null) return
if (objCAnnotation != null && swiftAnnotation != null) {
reporter.reportOn(
swiftAnnotation.source,
FirNativeErrors.REDUNDANT_SWIFT_REFINEMENT,
context
)
}
val targets = declaration.getAllowedAnnotationTargets()
val unsupportedTargets = targets - supportedTargets
if (unsupportedTargets.isNotEmpty()) {
objCAnnotation?.let { reporter.reportOn(it.source, FirNativeErrors.INVALID_OBJC_REFINEMENT_TARGETS, context) }
swiftAnnotation?.let { reporter.reportOn(it.source, FirNativeErrors.INVALID_OBJC_REFINEMENT_TARGETS, context) }
}
}
private fun FirRegularClass.findMetaAnnotations(): Pair<FirAnnotation?, FirAnnotation?> {
var objCAnnotation: FirAnnotation? = null
var swiftAnnotation: FirAnnotation? = null
for (annotation in annotations) {
when (annotation.toAnnotationClassId()) {
hidesFromObjCClassId -> objCAnnotation = annotation
refinesInSwiftClassId -> swiftAnnotation = annotation
}
if (objCAnnotation != null && swiftAnnotation != null) break
}
return objCAnnotation to swiftAnnotation
}
}
@@ -0,0 +1,64 @@
/*
* Copyright 2010-2022 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.fir.analysis.native.checkers
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.native.checkers.FirNativeObjCRefinementOverridesChecker.check
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirCallableDeclarationChecker
import org.jetbrains.kotlin.fir.analysis.checkers.unsubstitutedScope
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.REDUNDANT_SWIFT_REFINEMENT
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.expressions.coneClassLikeType
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
object FirNativeObjCRefinementChecker : FirCallableDeclarationChecker() {
val hidesFromObjCClassId = ClassId.topLevel(FqName("kotlin.native.HidesFromObjC"))
val refinesInSwiftClassId = ClassId.topLevel(FqName("kotlin.native.RefinesInSwift"))
override fun check(declaration: FirCallableDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
if (declaration !is FirSimpleFunction && declaration !is FirProperty) return
val (objCAnnotations, swiftAnnotations) = declaration.findRefinedAnnotations(context.session)
if (objCAnnotations.isNotEmpty() && swiftAnnotations.isNotEmpty()) {
for (swiftAnnotation in swiftAnnotations) {
reporter.reportOn(swiftAnnotation.source, REDUNDANT_SWIFT_REFINEMENT, context)
}
}
val containingClass = context.containingDeclarations.lastOrNull() as? FirClass
if (containingClass != null) {
val firTypeScope = containingClass.unsubstitutedScope(context)
check(firTypeScope, declaration.symbol, declaration, context, reporter, objCAnnotations, swiftAnnotations)
}
}
private fun FirCallableDeclaration.findRefinedAnnotations(session: FirSession): Pair<List<FirAnnotation>, List<FirAnnotation>> {
val objCAnnotations = mutableListOf<FirAnnotation>()
val swiftAnnotations = mutableListOf<FirAnnotation>()
for (annotation in annotations) {
val metaAnnotations = annotation.coneClassLikeType?.lookupTag?.toSymbol(session)?.resolvedAnnotationsWithClassIds.orEmpty()
for (metaAnnotation in metaAnnotations) {
when (metaAnnotation.toAnnotationClassId()) {
hidesFromObjCClassId -> {
objCAnnotations.add(annotation)
break
}
refinesInSwiftClassId -> {
swiftAnnotations.add(annotation)
break
}
}
}
}
return objCAnnotations to swiftAnnotations
}
}
@@ -0,0 +1,138 @@
/*
* Copyright 2010-2022 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.fir.analysis.native.checkers
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirClassChecker
import org.jetbrains.kotlin.fir.analysis.checkers.unsubstitutedScope
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INCOMPATIBLE_OBJC_REFINEMENT_OVERRIDE
import org.jetbrains.kotlin.fir.analysis.native.checkers.FirNativeObjCRefinementChecker.hidesFromObjCClassId
import org.jetbrains.kotlin.fir.analysis.native.checkers.FirNativeObjCRefinementChecker.refinesInSwiftClassId
import org.jetbrains.kotlin.fir.containingClass
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.toAnnotationClassId
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.expressions.coneClassLikeType
import org.jetbrains.kotlin.fir.isIntersectionOverride
import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.scopes.*
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
object FirNativeObjCRefinementOverridesChecker : FirClassChecker() {
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
// We just need to check intersection overrides, all other declarations are checked by FirNativeObjCRefinementChecker
val firTypeScope = declaration.unsubstitutedScope(context)
firTypeScope.processAllFunctions { symbol ->
if (!symbol.isIntersectionOverride) return@processAllFunctions
check(firTypeScope, symbol, declaration, context, reporter, emptyList(), emptyList())
}
firTypeScope.processAllProperties { symbol ->
if (!symbol.isIntersectionOverride) return@processAllProperties
check(firTypeScope, symbol, declaration, context, reporter, emptyList(), emptyList())
}
}
fun check(
firTypeScope: FirTypeScope,
memberSymbol: FirCallableSymbol<*>,
declarationToReport: FirDeclaration,
context: CheckerContext,
reporter: DiagnosticReporter,
objCAnnotations: List<FirAnnotation>,
swiftAnnotations: List<FirAnnotation>
) {
val overriddenMemberSymbols = firTypeScope.getDirectOverriddenSymbols(memberSymbol)
if (overriddenMemberSymbols.isEmpty()) return
var isHiddenFromObjC = objCAnnotations.isNotEmpty()
var isRefinedInSwift = swiftAnnotations.isNotEmpty()
val supersNotHiddenFromObjC = mutableListOf<FirCallableSymbol<*>>()
val supersNotRefinedInSwift = mutableListOf<FirCallableSymbol<*>>()
for (symbol in overriddenMemberSymbols) {
val (superIsHiddenFromObjC, superIsRefinedInSwift) = symbol.inheritsRefinedAnnotations(context.session, firTypeScope)
if (superIsHiddenFromObjC) isHiddenFromObjC = true else supersNotHiddenFromObjC.add(symbol)
if (superIsRefinedInSwift) isRefinedInSwift = true else supersNotRefinedInSwift.add(symbol)
}
if (isHiddenFromObjC && supersNotHiddenFromObjC.isNotEmpty()) {
reporter.reportIncompatibleOverride(declarationToReport, objCAnnotations, supersNotHiddenFromObjC, context)
}
if (isRefinedInSwift && supersNotRefinedInSwift.isNotEmpty()) {
reporter.reportIncompatibleOverride(declarationToReport, swiftAnnotations, supersNotRefinedInSwift, context)
}
}
private fun FirTypeScope.getDirectOverriddenSymbols(memberSymbol: FirCallableSymbol<*>): List<FirCallableSymbol<*>> {
return when (memberSymbol) {
is FirNamedFunctionSymbol -> {
processFunctionsByName(memberSymbol.name) {}
getDirectOverriddenFunctions(memberSymbol)
}
is FirPropertySymbol -> {
processPropertiesByName(memberSymbol.name) {}
getDirectOverriddenProperties(memberSymbol)
}
else -> error("unexpected member kind $memberSymbol")
}
}
private fun FirCallableSymbol<*>.inheritsRefinedAnnotations(session: FirSession, firTypeScope: FirTypeScope): Pair<Boolean, Boolean> {
val (hasObjC, hasSwift) = hasRefinedAnnotations(session)
if (hasObjC && hasSwift) return true to true
// Note: `checkMember` requires all overridden symbols to be either refined or not refined.
val overriddenMemberSymbol = firTypeScope.getDirectOverriddenSymbols(this).firstOrNull()
?: return hasObjC to hasSwift
val (inheritsObjC, inheritsSwift) = overriddenMemberSymbol.inheritsRefinedAnnotations(session, firTypeScope)
return (hasObjC || inheritsObjC) to (hasSwift || inheritsSwift)
}
private fun FirCallableSymbol<*>.hasRefinedAnnotations(session: FirSession): Pair<Boolean, Boolean> {
var hasObjC = false
var hasSwift = false
for (annotation in resolvedAnnotationsWithClassIds) {
val metaAnnotations = annotation.coneClassLikeType?.lookupTag?.toSymbol(session)?.resolvedAnnotationsWithClassIds.orEmpty()
for (metaAnnotation in metaAnnotations) {
when (metaAnnotation.toAnnotationClassId()) {
hidesFromObjCClassId -> {
hasObjC = true
break
}
refinesInSwiftClassId -> {
hasSwift = true
break
}
}
}
if (hasObjC && hasSwift) return true to true
}
return hasObjC to hasSwift
}
private fun DiagnosticReporter.reportIncompatibleOverride(
declaration: FirDeclaration,
annotations: List<FirAnnotation>,
notRefinedSupers: List<FirCallableSymbol<*>>,
context: CheckerContext
) {
val containingDeclarations = notRefinedSupers.mapNotNull { it.containingClass()?.toFirRegularClassSymbol(context.session) }
if (annotations.isEmpty()) {
reportOn(declaration.source, INCOMPATIBLE_OBJC_REFINEMENT_OVERRIDE, declaration.symbol, containingDeclarations, context)
} else {
for (annotation in annotations) {
reportOn(annotation.source, INCOMPATIBLE_OBJC_REFINEMENT_OVERRIDE, declaration.symbol, containingDeclarations, context)
}
}
}
}
@@ -5,8 +5,7 @@
package org.jetbrains.kotlin.fir.analysis.native.checkers
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.DeclarationCheckers
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirBasicDeclarationChecker
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.*
object NativeDeclarationCheckers : DeclarationCheckers() {
override val basicDeclarationCheckers: Set<FirBasicDeclarationChecker>
@@ -16,4 +15,19 @@ object NativeDeclarationCheckers : DeclarationCheckers() {
FirNativeThreadLocalChecker,
FirNativeIdentifierChecker
)
}
override val callableDeclarationCheckers: Set<FirCallableDeclarationChecker>
get() = setOf(
FirNativeObjCRefinementChecker
)
override val classCheckers: Set<FirClassChecker>
get() = setOf(
FirNativeObjCRefinementOverridesChecker
)
override val regularClassCheckers: Set<FirRegularClassChecker>
get() = setOf(
FirNativeObjCRefinementAnnotationChecker
)
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// FILE: kotlin.kt
package kotlin.native