[FIR] Extract common utilities for ObjCName checkers

Previously there were cross-references between `FirNativeObjCNameChecker`
  and `FirNativeObjCNameOverridesChecker` checkers, which made the flow
  of code quite non-intuitive

This commit extracts common methods and classes used by them into separate
  `FirNativeObjCNameUtilities` object
This commit is contained in:
Dmitriy Novozhilov
2024-01-10 14:20:01 +02:00
committed by Space Team
parent 72b0372927
commit 728f42d02a
3 changed files with 117 additions and 104 deletions
@@ -5,12 +5,9 @@
package org.jetbrains.kotlin.fir.analysis.native.checkers
import org.jetbrains.kotlin.builtins.StandardNames
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.FirSession
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
@@ -22,30 +19,21 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INVA
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.analysis.native.checkers.FirNativeObjCNameUtilities.ObjCName
import org.jetbrains.kotlin.fir.analysis.native.checkers.FirNativeObjCNameUtilities.checkCallableMember
import org.jetbrains.kotlin.fir.analysis.native.checkers.FirNativeObjCNameUtilities.getObjCNames
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 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)
checkCallableMember(firTypeScope, declaration.symbol, declaration, context, reporter)
}
}
}
@@ -97,39 +85,5 @@ object FirNativeObjCNameChecker : FirBasicDeclarationChecker() {
reporter.reportOn(annotationSource, MISSING_EXACT_OBJC_NAME, context)
}
}
class ObjCName(
val annotation: FirAnnotation
) {
val name: String? = annotation.getStringArgument(StandardNames.NAME)
val swiftName: String? = annotation.getStringArgument(swiftNameName)
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(session: FirSession): ObjCName? =
getAnnotationByClassId(objCNameClassId, session)?.let(::ObjCName)
private fun FirBasedSymbol<*>.getObjCName(session: FirSession): ObjCName? =
getAnnotationByClassId(objCNameClassId, session)?.let(::ObjCName)
fun FirBasedSymbol<*>.getObjCNames(session: FirSession): List<ObjCName?> = when (this) {
is FirFunctionSymbol<*> -> buildList {
add((this@getObjCNames as FirBasedSymbol<*>).getObjCName(session))
add(resolvedReceiverTypeRef?.getObjCName(session))
add(receiverParameter?.getObjCName(session))
valueParameterSymbols.forEach { add(it.getObjCName(session)) }
}
else -> listOf(getObjCName(session))
}
}
@@ -6,24 +6,14 @@
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.containingClassLookupTag
import org.jetbrains.kotlin.fir.analysis.native.checkers.FirNativeObjCNameUtilities.checkCallableMember
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.fullyExpandedClass
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.FirTypeScope
import org.jetbrains.kotlin.fir.scopes.processAllFunctions
import org.jetbrains.kotlin.fir.scopes.processAllProperties
import org.jetbrains.kotlin.fir.scopes.retrieveDirectOverriddenOf
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
object FirNativeObjCNameOverridesChecker : FirClassChecker() {
@@ -32,51 +22,11 @@ object FirNativeObjCNameOverridesChecker : FirClassChecker() {
val firTypeScope = declaration.unsubstitutedScope(context)
firTypeScope.processAllFunctions { symbol ->
if (!symbol.isIntersectionOverride) return@processAllFunctions
check(firTypeScope, symbol, declaration, context, reporter)
checkCallableMember(firTypeScope, symbol, declaration, context, reporter)
}
firTypeScope.processAllProperties { symbol ->
if (!symbol.isIntersectionOverride) return@processAllProperties
check(firTypeScope, symbol, declaration, context, reporter)
checkCallableMember(firTypeScope, symbol, declaration, context, reporter)
}
}
fun check(
firTypeScope: FirTypeScope,
memberSymbol: FirCallableSymbol<*>,
declarationToReport: FirDeclaration,
context: CheckerContext,
reporter: DiagnosticReporter
) {
val overriddenSymbols = firTypeScope.retrieveDirectOverriddenOf(memberSymbol)
if (overriddenSymbols.isEmpty()) return
val objCNames = overriddenSymbols.map { it.getFirstBaseSymbol(context).getObjCNames(context.session) }
if (!objCNames.allNamesEquals()) {
val containingDeclarations = overriddenSymbols.mapNotNull {
it.containingClassLookupTag()?.toFirRegularClassSymbol(context.session)
}
reporter.reportOn(
declarationToReport.source,
INCOMPATIBLE_OBJC_NAME_OVERRIDE,
declarationToReport.symbol,
containingDeclarations,
context
)
}
}
private fun FirCallableSymbol<*>.getFirstBaseSymbol(context: CheckerContext): FirCallableSymbol<*> {
val session = context.session
val ownScope = containingClassLookupTag()?.toSymbol(session)?.fullyExpandedClass(session)?.unsubstitutedScope(context)
?: return this
val overriddenMemberSymbols = ownScope.retrieveDirectOverriddenOf(this)
return if (overriddenMemberSymbols.isEmpty()) this else overriddenMemberSymbols.first().getFirstBaseSymbol(context)
}
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
}
}
@@ -0,0 +1,109 @@
/*
* Copyright 2010-2024 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.builtins.StandardNames
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.FirAnnotationContainer
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.unsubstitutedScope
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors
import org.jetbrains.kotlin.fir.containingClassLookupTag
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.scopes.FirTypeScope
import org.jetbrains.kotlin.fir.scopes.retrieveDirectOverriddenOf
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
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 FirNativeObjCNameUtilities {
private val objCNameClassId = ClassId.topLevel(FqName("kotlin.native.ObjCName"))
private val swiftNameName = Name.identifier("swiftName")
private val exactName = Name.identifier("exact")
fun FirBasedSymbol<*>.getObjCNames(session: FirSession): List<ObjCName?> = when (this) {
is FirFunctionSymbol<*> -> buildList {
add((this@getObjCNames as FirBasedSymbol<*>).getObjCName(session))
add(resolvedReceiverTypeRef?.getObjCName(session))
add(receiverParameter?.getObjCName(session))
valueParameterSymbols.forEach { add(it.getObjCName(session)) }
}
else -> listOf(getObjCName(session))
}
private fun FirAnnotationContainer.getObjCName(session: FirSession): ObjCName? =
getAnnotationByClassId(objCNameClassId, session)?.let(::ObjCName)
private fun FirBasedSymbol<*>.getObjCName(session: FirSession): ObjCName? =
getAnnotationByClassId(objCNameClassId, session)?.let(::ObjCName)
class ObjCName(
val annotation: FirAnnotation
) {
val name: String? = annotation.getStringArgument(StandardNames.NAME)
val swiftName: String? = annotation.getStringArgument(swiftNameName)
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
}
}
fun checkCallableMember(
firTypeScope: FirTypeScope,
memberSymbol: FirCallableSymbol<*>,
declarationToReport: FirDeclaration,
context: CheckerContext,
reporter: DiagnosticReporter
) {
val overriddenSymbols = firTypeScope.retrieveDirectOverriddenOf(memberSymbol)
if (overriddenSymbols.isEmpty()) return
val objCNames = overriddenSymbols.map { it.getFirstBaseSymbol(context).getObjCNames(context.session) }
if (!objCNames.allNamesEquals()) {
val containingDeclarations = overriddenSymbols.mapNotNull {
it.containingClassLookupTag()?.toFirRegularClassSymbol(context.session)
}
reporter.reportOn(
declarationToReport.source,
FirNativeErrors.INCOMPATIBLE_OBJC_NAME_OVERRIDE,
declarationToReport.symbol,
containingDeclarations,
context
)
}
}
private fun FirCallableSymbol<*>.getFirstBaseSymbol(context: CheckerContext): FirCallableSymbol<*> {
val session = context.session
val ownScope = containingClassLookupTag()?.toSymbol(session)?.fullyExpandedClass(session)?.unsubstitutedScope(context)
?: return this
val overriddenMemberSymbols = ownScope.retrieveDirectOverriddenOf(this)
return if (overriddenMemberSymbols.isEmpty()) this else overriddenMemberSymbols.first().getFirstBaseSymbol(context)
}
private fun List<List<ObjCName?>>.allNamesEquals(): Boolean {
val first = this[0]
for (i in 1 until size) {
if (first != this[i]) return false
}
return true
}
}