[ObjCInterop] Implement @ObjCSignatureOverride

This annotation leads to conflicting overloads error supression,
in case where several function with the same argument types,
but different argument names are inherited from ObjC class.

We need to implement it in both K1 and K2 to make the IDE experience
better.
But the annotation itself wouldn't be available in K1.

    ^KT-61323
This commit is contained in:
Pavel Kunyavskiy
2024-01-18 14:56:57 +01:00
committed by Space Team
parent 77eee0d3fc
commit 6e8a7d4662
30 changed files with 698 additions and 41 deletions
@@ -103,5 +103,9 @@ object NATIVE_DIAGNOSTICS_LIST : DiagnosticList("FirNativeErrors") {
val CONSTRUCTOR_MATCHES_SEVERAL_SUPER_CONSTRUCTORS by error<KtElement>() {
parameter<FqName>("annotation")
}
val CONFLICTING_OBJC_OVERLOADS by error<PsiElement>() {
parameter<Collection<Symbol>>("conflictingOverloads")
}
val INAPPLICABLE_OBJC_OVERRIDE by error<PsiElement>()
}
}
@@ -62,6 +62,8 @@ object FirNativeErrors {
val CONSTRUCTOR_OVERRIDES_ALREADY_OVERRIDDEN_OBJC_INITIALIZER: KtDiagnosticFactory1<FqName> by error1<KtElement, FqName>()
val CONSTRUCTOR_DOES_NOT_OVERRIDE_ANY_SUPER_CONSTRUCTOR: KtDiagnosticFactory1<FqName> by error1<KtElement, FqName>()
val CONSTRUCTOR_MATCHES_SEVERAL_SUPER_CONSTRUCTORS: KtDiagnosticFactory1<FqName> by error1<KtElement, FqName>()
val CONFLICTING_OBJC_OVERLOADS: KtDiagnosticFactory1<Collection<FirBasedSymbol<*>>> by error1<PsiElement, Collection<FirBasedSymbol<*>>>()
val INAPPLICABLE_OBJC_OVERRIDE: KtDiagnosticFactory0 by error0<PsiElement>()
init {
RootDiagnosticRendererFactory.registerFactory(FirNativeErrorsDefaultMessages)
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers
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.native.FirNativeErrors.CANNOT_CHECK_FOR_FORWARD_DECLARATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.CONFLICTING_OBJC_OVERLOADS
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.CONSTRUCTOR_DOES_NOT_OVERRIDE_ANY_SUPER_CONSTRUCTOR
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.CONSTRUCTOR_MATCHES_SEVERAL_SUPER_CONSTRUCTORS
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.CONSTRUCTOR_OVERRIDES_ALREADY_OVERRIDDEN_OBJC_INITIALIZER
@@ -20,6 +21,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.FORW
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.FORWARD_DECLARATION_AS_REIFIED_TYPE_ARGUMENT
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_OBJC_OVERRIDE
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
@@ -145,5 +147,14 @@ object FirNativeErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
"Constructor with ''@{0}'' matches more than one of super constructors.",
TO_STRING
)
map.put(
CONFLICTING_OBJC_OVERLOADS,
"Conflicting overloads: {0}. Add @ObjCSignatureOverride to allow collision for functions inherited from Objective-C.",
SYMBOLS
)
map.put(
INAPPLICABLE_OBJC_OVERRIDE,
"@ObjCSignatureOverride is only allowed on methods overriding methods from Objective-C.",
)
}
}
@@ -0,0 +1,74 @@
/*
* 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.config.LanguageFeature
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactory1
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.MppCheckerKind
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirFunctionChecker
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.PlatformConflictDeclarationsDiagnosticDispatcher
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors
import org.jetbrains.kotlin.fir.backend.native.interop.getObjCMethodInfoFromOverriddenFunctions
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
import org.jetbrains.kotlin.name.NativeStandardInteropNames.Annotations.objCSignatureOverrideClassId
import org.jetbrains.kotlin.utils.SmartSet
private fun FirFunctionSymbol<*>.isInheritedFromObjc(context: CheckerContext): Boolean {
return getObjCMethodInfoFromOverriddenFunctions(context.session, context.scopeSession) != null
}
/**
* This function basically checks that these two functions have different objective-C signature.
*
* This signature consists of function name and parameter names except first.
*
* So we ignore the first parameter name, but check others
*/
private fun FirFunctionSymbol<*>.hasDifferentParameterNames(other: FirFunctionSymbol<*>) : Boolean {
return valueParameterSymbols.drop(1).map { it.name } != other.valueParameterSymbols.drop(1).map { it.name }
}
object NativeConflictDeclarationsDiagnosticDispatcher : PlatformConflictDeclarationsDiagnosticDispatcher {
override fun getDiagnostic(
conflictingDeclaration: FirBasedSymbol<*>,
symbols: SmartSet<FirBasedSymbol<*>>,
context: CheckerContext
): KtDiagnosticFactory1<Collection<FirBasedSymbol<*>>>? {
if (context.languageVersionSettings.supportsFeature(LanguageFeature.ObjCSignatureOverrideAnnotation)) {
if (conflictingDeclaration is FirFunctionSymbol<*> && symbols.all { it is FirFunctionSymbol<*> }) {
if (conflictingDeclaration.isInheritedFromObjc(context) && symbols.all { (it as FirFunctionSymbol<*>).isInheritedFromObjc(context) }) {
if (symbols.all { (it as FirFunctionSymbol<*>).hasDifferentParameterNames(conflictingDeclaration) }) {
if (conflictingDeclaration.hasAnnotation(objCSignatureOverrideClassId, context.session)) {
return null
} else {
return FirNativeErrors.CONFLICTING_OBJC_OVERLOADS
}
}
}
}
}
return PlatformConflictDeclarationsDiagnosticDispatcher.DEFAULT.getDiagnostic(conflictingDeclaration, symbols, context)
}
}
object FirNativeObjcOverrideApplicabilityChecker : FirFunctionChecker(MppCheckerKind.Platform) {
override fun check(
declaration: FirFunction,
context: CheckerContext,
reporter: DiagnosticReporter,
) {
if (declaration.hasAnnotation(objCSignatureOverrideClassId, context.session)) {
if (!declaration.symbol.isInheritedFromObjc(context)) {
reporter.reportOn(declaration.getAnnotationByClassId(objCSignatureOverrideClassId, context.session)?.source, FirNativeErrors.INAPPLICABLE_OBJC_OVERRIDE, context)
}
}
}
}
@@ -15,7 +15,12 @@ object NativeDeclarationCheckers : DeclarationCheckers() {
FirNativeSharedImmutableChecker,
FirNativeThreadLocalChecker,
FirNativeIdentifierChecker,
FirNativeObjCNameChecker
FirNativeObjCNameChecker,
)
override val functionCheckers: Set<FirFunctionChecker>
get() = setOf(
FirNativeObjcOverrideApplicabilityChecker
)
override val callableDeclarationCheckers: Set<FirCallableDeclarationChecker>
@@ -679,6 +679,8 @@ val FIR_NON_SUPPRESSIBLE_ERROR_NAMES: Set<String> = setOf(
"CONSTRUCTOR_OVERRIDES_ALREADY_OVERRIDDEN_OBJC_INITIALIZER",
"CONSTRUCTOR_DOES_NOT_OVERRIDE_ANY_SUPER_CONSTRUCTOR",
"CONSTRUCTOR_MATCHES_SEVERAL_SUPER_CONSTRUCTORS",
"CONFLICTING_OBJC_OVERLOADS",
"INAPPLICABLE_OBJC_OVERRIDE",
"NESTED_EXTERNAL_DECLARATION",
"WRONG_EXTERNAL_DECLARATION",
"NESTED_CLASS_IN_EXTERNAL_INTERFACE",
@@ -6,9 +6,13 @@
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.KtFakeSourceElementKind
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactory1
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.FirNameConflictsTrackerComponent
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.FirSessionComponent
import org.jetbrains.kotlin.fir.analysis.checkers.*
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
@@ -18,14 +22,43 @@ import org.jetbrains.kotlin.fir.scopes.impl.FirPackageMemberScope
import org.jetbrains.kotlin.fir.scopes.impl.PACKAGE_MEMBER
import org.jetbrains.kotlin.fir.scopes.impl.typeAliasForConstructor
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.utils.SmartSet
interface PlatformConflictDeclarationsDiagnosticDispatcher : FirSessionComponent {
fun getDiagnostic(
conflictingDeclaration: FirBasedSymbol<*>,
symbols: SmartSet<FirBasedSymbol<*>>,
context: CheckerContext
): KtDiagnosticFactory1<Collection<FirBasedSymbol<*>>>?
object DEFAULT : PlatformConflictDeclarationsDiagnosticDispatcher {
override fun getDiagnostic(
conflictingDeclaration: FirBasedSymbol<*>,
symbols: SmartSet<FirBasedSymbol<*>>,
context: CheckerContext
): KtDiagnosticFactory1<Collection<FirBasedSymbol<*>>> {
return when {
conflictingDeclaration is FirNamedFunctionSymbol || conflictingDeclaration is FirConstructorSymbol -> {
FirErrors.CONFLICTING_OVERLOADS
}
conflictingDeclaration is FirClassLikeSymbol<*> &&
conflictingDeclaration.getContainingClassSymbol(context.session) == null &&
symbols.any { it is FirClassLikeSymbol<*> } -> {
FirErrors.PACKAGE_OR_CLASSIFIER_REDECLARATION
}
else -> {
FirErrors.REDECLARATION
}
}
}
}
}
val FirSession.conflictDeclarationsDiagnosticDispatcher: PlatformConflictDeclarationsDiagnosticDispatcher? by FirSession.nullableSessionComponentAccessor()
object FirConflictsDeclarationChecker : FirBasicDeclarationChecker(MppCheckerKind.Platform) {
override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
when (declaration) {
@@ -90,21 +123,16 @@ object FirConflictsDeclarationChecker : FirBasicDeclarationChecker(MppCheckerKin
conflictingDeclaration.isPrimaryConstructor && symbols.all { it.isPrimaryConstructor }
) return@forEach
when {
symbols.singleOrNull()?.let { isExpectAndActual(conflictingDeclaration, it) } == true -> {
reporter.reportOn(source, FirErrors.EXPECT_AND_ACTUAL_IN_THE_SAME_MODULE, conflictingDeclaration, context)
}
conflictingDeclaration is FirNamedFunctionSymbol || conflictingDeclaration is FirConstructorSymbol -> {
reporter.reportOn(source, FirErrors.CONFLICTING_OVERLOADS, symbols, context)
}
conflictingDeclaration is FirClassLikeSymbol<*> &&
conflictingDeclaration.getContainingClassSymbol(context.session) == null &&
symbols.any { it is FirClassLikeSymbol<*> } -> {
reporter.reportOn(source, FirErrors.PACKAGE_OR_CLASSIFIER_REDECLARATION, symbols, context)
}
else -> {
reporter.reportOn(source, FirErrors.REDECLARATION, symbols, context)
}
if (symbols.singleOrNull()?.let { isExpectAndActual(conflictingDeclaration, it) } == true) {
reporter.reportOn(source, FirErrors.EXPECT_AND_ACTUAL_IN_THE_SAME_MODULE, conflictingDeclaration, context)
return@forEach
}
val dispatcher = context.session.conflictDeclarationsDiagnosticDispatcher ?: PlatformConflictDeclarationsDiagnosticDispatcher.DEFAULT
val factory = dispatcher.getDiagnostic(conflictingDeclaration, symbols, context)
if (factory != null) {
reporter.reportOn(source, factory, symbols, context)
}
}
}