[FIR] Implement ObjCName checkers
This commit is contained in:
+8
@@ -36,6 +36,14 @@ object FirNativeErrors {
|
||||
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>()
|
||||
val INAPPLICABLE_OBJC_NAME by error0<KtElement>()
|
||||
val INVALID_OBJC_NAME by error0<KtElement>()
|
||||
val INVALID_OBJC_NAME_CHARS by error1<KtElement, String>()
|
||||
val INVALID_OBJC_NAME_FIRST_CHAR by error1<KtElement, String>()
|
||||
val INCOMPATIBLE_OBJC_NAME_OVERRIDE by error2<KtElement, FirBasedSymbol<*>, Collection<FirRegularClassSymbol>>()
|
||||
val INAPPLICABLE_EXACT_OBJC_NAME by error0<KtElement>()
|
||||
val MISSING_EXACT_OBJC_NAME by error0<KtElement>()
|
||||
val NON_LITERAL_OBJC_NAME_ARG by error0<KtElement>()
|
||||
|
||||
init {
|
||||
RootDiagnosticRendererFactory.registerFactory(FirNativeErrorsDefaultMessages)
|
||||
|
||||
+16
@@ -11,17 +11,25 @@ import org.jetbrains.kotlin.diagnostics.rendering.BaseDiagnosticRendererFactory
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.SYMBOL
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.SYMBOLS
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.checkMissingMessages
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INAPPLICABLE_EXACT_OBJC_NAME
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INAPPLICABLE_OBJC_NAME
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INAPPLICABLE_SHARED_IMMUTABLE_PROPERTY
|
||||
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_NAME_OVERRIDE
|
||||
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_NAME
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INVALID_OBJC_NAME_CHARS
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INVALID_OBJC_NAME_FIRST_CHAR
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INVALID_OBJC_REFINEMENT_TARGETS
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.MISSING_EXACT_OBJC_NAME
|
||||
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.NON_LITERAL_OBJC_NAME_ARG
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.THROWS_LIST_EMPTY
|
||||
|
||||
object FirNativeErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
|
||||
@@ -55,6 +63,14 @@ object FirNativeErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
|
||||
INVALID_OBJC_REFINEMENT_TARGETS,
|
||||
"Refines annotations are only applicable to annotations with targets FUNCTION and/or PROPERTY"
|
||||
)
|
||||
map.put(INAPPLICABLE_OBJC_NAME, "@ObjCName is not applicable on overrides")
|
||||
map.put(INVALID_OBJC_NAME, "@ObjCName should have a name and/or swiftName")
|
||||
map.put(INVALID_OBJC_NAME_CHARS, "@ObjCName contains illegal characters: {0}", TO_STRING)
|
||||
map.put(INVALID_OBJC_NAME_FIRST_CHAR, "@ObjCName contains illegal first characters: {0}", TO_STRING)
|
||||
map.put(INCOMPATIBLE_OBJC_NAME_OVERRIDE, "Member \"{0}\" inherits inconsistent @ObjCName from {1}", SYMBOL, SYMBOLS)
|
||||
map.put(INAPPLICABLE_EXACT_OBJC_NAME, "Exact @ObjCName is only applicable to classes, objects and interfaces")
|
||||
map.put(MISSING_EXACT_OBJC_NAME, "Exact @ObjCName is required to have an ObjC name")
|
||||
map.put(NON_LITERAL_OBJC_NAME_ARG, "@ObjCName accepts only literal string and boolean values")
|
||||
|
||||
map.checkMissingMessages(FirNativeErrors)
|
||||
}
|
||||
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.diagnostics.reportOn
|
||||
import org.jetbrains.kotlin.fir.FirAnnotationContainer
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirBasicDeclarationChecker
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.unsubstitutedScope
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INAPPLICABLE_EXACT_OBJC_NAME
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INAPPLICABLE_OBJC_NAME
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INVALID_OBJC_NAME
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INVALID_OBJC_NAME_CHARS
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INVALID_OBJC_NAME_FIRST_CHAR
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.MISSING_EXACT_OBJC_NAME
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.NON_LITERAL_OBJC_NAME_ARG
|
||||
import org.jetbrains.kotlin.fir.analysis.native.checkers.FirNativeObjCNameOverridesChecker.check
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.isOverride
|
||||
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
|
||||
import org.jetbrains.kotlin.fir.expressions.FirConstExpression
|
||||
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
|
||||
object FirNativeObjCNameChecker : FirBasicDeclarationChecker() {
|
||||
|
||||
private val objCNameClassId = ClassId.topLevel(FqName("kotlin.native.ObjCName"))
|
||||
private val nameName = Name.identifier("name")
|
||||
private val swiftNameName = Name.identifier("swiftName")
|
||||
private val exactName = Name.identifier("exact")
|
||||
|
||||
override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
checkDeclaration(declaration, context, reporter)
|
||||
if (declaration is FirCallableDeclaration && (declaration is FirSimpleFunction || declaration is FirProperty)) {
|
||||
val containingClass = context.containingDeclarations.lastOrNull() as? FirClass
|
||||
if (containingClass != null) {
|
||||
val firTypeScope = containingClass.unsubstitutedScope(context)
|
||||
check(firTypeScope, declaration.symbol, declaration, context, reporter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkDeclaration(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
if (declaration is FirValueParameter) return // those are checked with the FirFunction
|
||||
val objCNames = declaration.symbol.getObjCNames().filterNotNull()
|
||||
if (objCNames.isEmpty()) return
|
||||
if (declaration is FirCallableDeclaration && declaration.isOverride) {
|
||||
for (objCName in objCNames) {
|
||||
reporter.reportOn(objCName.annotation.source, INAPPLICABLE_OBJC_NAME, context)
|
||||
}
|
||||
}
|
||||
objCNames.forEach { checkObjCName(it, declaration, context, reporter) }
|
||||
}
|
||||
|
||||
// We only allow valid ObjC identifiers (even for Swift names)
|
||||
private val validFirstChars = ('A'..'Z').toSet() + ('a'..'z').toSet() + '_'
|
||||
private val validChars = validFirstChars + ('0'..'9').toSet()
|
||||
|
||||
private fun checkObjCName(objCName: ObjCName, declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
val annotationSource = objCName.annotation.source
|
||||
for ((_, argument) in objCName.annotation.argumentMapping.mapping) {
|
||||
if (argument is FirConstExpression<*>) continue
|
||||
reporter.reportOn(argument.source, NON_LITERAL_OBJC_NAME_ARG, context)
|
||||
}
|
||||
if (objCName.name == null && objCName.swiftName == null) {
|
||||
reporter.reportOn(annotationSource, INVALID_OBJC_NAME, context)
|
||||
}
|
||||
val invalidNameFirstChar = objCName.name?.firstOrNull()?.takeUnless(validFirstChars::contains)
|
||||
val invalidSwiftNameFirstChar = objCName.swiftName?.firstOrNull()?.takeUnless(validFirstChars::contains)
|
||||
val invalidFirstChars = setOfNotNull(invalidNameFirstChar, invalidSwiftNameFirstChar)
|
||||
if (invalidFirstChars.isNotEmpty()) {
|
||||
reporter.reportOn(annotationSource, INVALID_OBJC_NAME_FIRST_CHAR, invalidFirstChars.joinToString(""), context)
|
||||
}
|
||||
val invalidNameChars = objCName.name?.toSet()?.subtract(validChars) ?: emptySet()
|
||||
val invalidSwiftNameChars = objCName.swiftName?.toSet()?.subtract(validChars) ?: emptySet()
|
||||
val invalidChars = invalidNameChars + invalidSwiftNameChars
|
||||
if (invalidChars.isNotEmpty()) {
|
||||
reporter.reportOn(annotationSource, INVALID_OBJC_NAME_CHARS, invalidFirstChars.joinToString(""), context)
|
||||
}
|
||||
if (objCName.exact && (declaration !is FirClass || declaration.classKind == ClassKind.ENUM_ENTRY)) {
|
||||
reporter.reportOn(annotationSource, INAPPLICABLE_EXACT_OBJC_NAME, context)
|
||||
}
|
||||
if (objCName.exact && objCName.name == null) {
|
||||
reporter.reportOn(annotationSource, MISSING_EXACT_OBJC_NAME, context)
|
||||
}
|
||||
}
|
||||
|
||||
class ObjCName(
|
||||
val annotation: FirAnnotation
|
||||
) {
|
||||
val name: String? = annotation.getStringArgument(nameName)?.takeIf { it.isNotBlank() }
|
||||
val swiftName: String? = annotation.getStringArgument(swiftNameName)?.takeIf { it.isNotBlank() }
|
||||
val exact: Boolean = annotation.getBooleanArgument(exactName) ?: false
|
||||
|
||||
override fun equals(other: Any?): Boolean =
|
||||
other is ObjCName && name == other.name && swiftName == other.swiftName && exact == other.exact
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = name.hashCode()
|
||||
result = 31 * result + swiftName.hashCode()
|
||||
result = 31 * result + exact.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private fun FirAnnotationContainer.getObjCName(): ObjCName? =
|
||||
getAnnotationByClassId(objCNameClassId)?.let(::ObjCName)
|
||||
|
||||
private fun FirBasedSymbol<*>.getObjCName(): ObjCName? =
|
||||
getAnnotationByClassId(objCNameClassId)?.let(::ObjCName)
|
||||
|
||||
fun FirBasedSymbol<*>.getObjCNames(): List<ObjCName?> = when (this) {
|
||||
is FirFunctionSymbol<*> -> buildList {
|
||||
add((this@getObjCNames as FirBasedSymbol<*>).getObjCName())
|
||||
add(resolvedReceiverTypeRef?.getObjCName())
|
||||
valueParameterSymbols.forEach { add(it.getObjCName()) }
|
||||
}
|
||||
|
||||
else -> listOf(getObjCName())
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.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_NAME_OVERRIDE
|
||||
import org.jetbrains.kotlin.fir.analysis.native.checkers.FirNativeObjCNameChecker.getObjCNames
|
||||
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.isIntersectionOverride
|
||||
import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
|
||||
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 FirNativeObjCNameOverridesChecker : FirClassChecker() {
|
||||
|
||||
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
// We just need to check intersection overrides, all other declarations are checked by FirNativeObjCNameChecker
|
||||
val firTypeScope = declaration.unsubstitutedScope(context)
|
||||
firTypeScope.processAllFunctions { symbol ->
|
||||
if (!symbol.isIntersectionOverride) return@processAllFunctions
|
||||
check(firTypeScope, symbol, declaration, context, reporter)
|
||||
}
|
||||
firTypeScope.processAllProperties { symbol ->
|
||||
if (!symbol.isIntersectionOverride) return@processAllProperties
|
||||
check(firTypeScope, symbol, declaration, context, reporter)
|
||||
}
|
||||
}
|
||||
|
||||
fun check(
|
||||
firTypeScope: FirTypeScope,
|
||||
memberSymbol: FirCallableSymbol<*>,
|
||||
declarationToReport: FirDeclaration,
|
||||
context: CheckerContext,
|
||||
reporter: DiagnosticReporter
|
||||
) {
|
||||
val overriddenSymbols = firTypeScope.getDirectOverriddenSymbols(memberSymbol)
|
||||
if (overriddenSymbols.isEmpty()) return
|
||||
val objCNames = overriddenSymbols.map { it.getFirstBaseSymbol(firTypeScope).getObjCNames() }
|
||||
if (!objCNames.allNamesEquals()) {
|
||||
val containingDeclarations = overriddenSymbols.mapNotNull { it.containingClass()?.toFirRegularClassSymbol(context.session) }
|
||||
reporter.reportOn(
|
||||
declarationToReport.source,
|
||||
INCOMPATIBLE_OBJC_NAME_OVERRIDE,
|
||||
declarationToReport.symbol,
|
||||
containingDeclarations,
|
||||
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<*>.getFirstBaseSymbol(firTypeScope: FirTypeScope): FirCallableSymbol<*> {
|
||||
val overriddenMemberSymbols = firTypeScope.getDirectOverriddenSymbols(this)
|
||||
return if (overriddenMemberSymbols.isEmpty()) this else overriddenMemberSymbols.first().getFirstBaseSymbol(firTypeScope)
|
||||
}
|
||||
|
||||
private fun List<List<FirNativeObjCNameChecker.ObjCName?>>.allNamesEquals(): Boolean {
|
||||
val first = this[0]
|
||||
for (i in 1 until size) {
|
||||
if (first != this[i]) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
+4
-2
@@ -13,7 +13,8 @@ object NativeDeclarationCheckers : DeclarationCheckers() {
|
||||
FirNativeThrowsChecker,
|
||||
FirNativeSharedImmutableChecker,
|
||||
FirNativeThreadLocalChecker,
|
||||
FirNativeIdentifierChecker
|
||||
FirNativeIdentifierChecker,
|
||||
FirNativeObjCNameChecker
|
||||
)
|
||||
|
||||
override val callableDeclarationCheckers: Set<FirCallableDeclarationChecker>
|
||||
@@ -23,7 +24,8 @@ object NativeDeclarationCheckers : DeclarationCheckers() {
|
||||
|
||||
override val classCheckers: Set<FirClassChecker>
|
||||
get() = setOf(
|
||||
FirNativeObjCRefinementOverridesChecker
|
||||
FirNativeObjCRefinementOverridesChecker,
|
||||
FirNativeObjCNameOverridesChecker
|
||||
)
|
||||
|
||||
override val regularClassCheckers: Set<FirRegularClassChecker>
|
||||
|
||||
Reference in New Issue
Block a user