From 728f42d02ab804ccd0ea6a3fb79690bb46247864 Mon Sep 17 00:00:00 2001 From: Dmitriy Novozhilov Date: Wed, 10 Jan 2024 14:20:01 +0200 Subject: [PATCH] [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 --- .../checkers/FirNativeObjCNameChecker.kt | 56 +-------- .../FirNativeObjCNameOverridesChecker.kt | 56 +-------- .../checkers/FirNativeObjCNameUtilities.kt | 109 ++++++++++++++++++ 3 files changed, 117 insertions(+), 104 deletions(-) create mode 100644 compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCNameUtilities.kt diff --git a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCNameChecker.kt b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCNameChecker.kt index 2c6c8ea3d5b..e58d96e3acb 100644 --- a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCNameChecker.kt +++ b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCNameChecker.kt @@ -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 = 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)) - } } + diff --git a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCNameOverridesChecker.kt b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCNameOverridesChecker.kt index 72fecbf69cd..3881a5e2b5d 100644 --- a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCNameOverridesChecker.kt +++ b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCNameOverridesChecker.kt @@ -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>.allNamesEquals(): Boolean { - val first = this[0] - for (i in 1 until size) { - if (first != this[i]) return false - } - return true - } } diff --git a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCNameUtilities.kt b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCNameUtilities.kt new file mode 100644 index 00000000000..c179f1f368c --- /dev/null +++ b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCNameUtilities.kt @@ -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 = 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>.allNamesEquals(): Boolean { + val first = this[0] + for (i in 1 until size) { + if (first != this[i]) return false + } + return true + } +}