diff --git a/analysis/low-level-api-fir/low-level-api-fir-native/tests-gen/org/jetbrains/kotlin/analysis/low/level/api/fir/konan/compiler/based/LLFirNativeTestGenerated.java b/analysis/low-level-api-fir/low-level-api-fir-native/tests-gen/org/jetbrains/kotlin/analysis/low/level/api/fir/konan/compiler/based/LLFirNativeTestGenerated.java index f114770a8be..fc533ca5b5c 100644 --- a/analysis/low-level-api-fir/low-level-api-fir-native/tests-gen/org/jetbrains/kotlin/analysis/low/level/api/fir/konan/compiler/based/LLFirNativeTestGenerated.java +++ b/analysis/low-level-api-fir/low-level-api-fir-native/tests-gen/org/jetbrains/kotlin/analysis/low/level/api/fir/konan/compiler/based/LLFirNativeTestGenerated.java @@ -68,6 +68,18 @@ public class LLFirNativeTestGenerated extends AbstractLLFirNativeTest { runTest("compiler/testData/diagnostics/nativeTests/nativeProtectedFunCall.kt"); } + @Test + @TestMetadata("noObjcOverrideConflictingOverloadsDisabled.kt") + public void testNoObjcOverrideConflictingOverloadsDisabled() throws Exception { + runTest("compiler/testData/diagnostics/nativeTests/noObjcOverrideConflictingOverloadsDisabled.kt"); + } + + @Test + @TestMetadata("noObjcOverrideConflictingOverloadsEnabled.kt") + public void testNoObjcOverrideConflictingOverloadsEnabled() throws Exception { + runTest("compiler/testData/diagnostics/nativeTests/noObjcOverrideConflictingOverloadsEnabled.kt"); + } + @Test @TestMetadata("objCName.kt") public void testObjCName() throws Exception { @@ -104,6 +116,12 @@ public class LLFirNativeTestGenerated extends AbstractLLFirNativeTest { runTest("compiler/testData/diagnostics/nativeTests/objCRefinement.kt"); } + @Test + @TestMetadata("objcOverrideApplicability.kt") + public void testObjcOverrideApplicability() throws Exception { + runTest("compiler/testData/diagnostics/nativeTests/objcOverrideApplicability.kt"); + } + @Test @TestMetadata("resolveToDelegatedProperty.kt") public void testResolveToDelegatedProperty() throws Exception { diff --git a/analysis/low-level-api-fir/low-level-api-fir-native/tests-gen/org/jetbrains/kotlin/analysis/low/level/api/fir/konan/compiler/based/LLFirReversedNativeTestGenerated.java b/analysis/low-level-api-fir/low-level-api-fir-native/tests-gen/org/jetbrains/kotlin/analysis/low/level/api/fir/konan/compiler/based/LLFirReversedNativeTestGenerated.java index e0bed3dc3dd..580e10d418e 100644 --- a/analysis/low-level-api-fir/low-level-api-fir-native/tests-gen/org/jetbrains/kotlin/analysis/low/level/api/fir/konan/compiler/based/LLFirReversedNativeTestGenerated.java +++ b/analysis/low-level-api-fir/low-level-api-fir-native/tests-gen/org/jetbrains/kotlin/analysis/low/level/api/fir/konan/compiler/based/LLFirReversedNativeTestGenerated.java @@ -68,6 +68,18 @@ public class LLFirReversedNativeTestGenerated extends AbstractLLFirReversedNativ runTest("compiler/testData/diagnostics/nativeTests/nativeProtectedFunCall.kt"); } + @Test + @TestMetadata("noObjcOverrideConflictingOverloadsDisabled.kt") + public void testNoObjcOverrideConflictingOverloadsDisabled() throws Exception { + runTest("compiler/testData/diagnostics/nativeTests/noObjcOverrideConflictingOverloadsDisabled.kt"); + } + + @Test + @TestMetadata("noObjcOverrideConflictingOverloadsEnabled.kt") + public void testNoObjcOverrideConflictingOverloadsEnabled() throws Exception { + runTest("compiler/testData/diagnostics/nativeTests/noObjcOverrideConflictingOverloadsEnabled.kt"); + } + @Test @TestMetadata("objCName.kt") public void testObjCName() throws Exception { @@ -104,6 +116,12 @@ public class LLFirReversedNativeTestGenerated extends AbstractLLFirReversedNativ runTest("compiler/testData/diagnostics/nativeTests/objCRefinement.kt"); } + @Test + @TestMetadata("objcOverrideApplicability.kt") + public void testObjcOverrideApplicability() throws Exception { + runTest("compiler/testData/diagnostics/nativeTests/objcOverrideApplicability.kt"); + } + @Test @TestMetadata("resolveToDelegatedProperty.kt") public void testResolveToDelegatedProperty() throws Exception { diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirNativeDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirNativeDiagnosticsList.kt index d8ef9c1076d..b1a908560c2 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirNativeDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirNativeDiagnosticsList.kt @@ -103,5 +103,9 @@ object NATIVE_DIAGNOSTICS_LIST : DiagnosticList("FirNativeErrors") { val CONSTRUCTOR_MATCHES_SEVERAL_SUPER_CONSTRUCTORS by error() { parameter("annotation") } + val CONFLICTING_OBJC_OVERLOADS by error() { + parameter>("conflictingOverloads") + } + val INAPPLICABLE_OBJC_OVERRIDE by error() } } diff --git a/compiler/fir/checkers/checkers.native/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/native/FirNativeErrors.kt b/compiler/fir/checkers/checkers.native/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/native/FirNativeErrors.kt index 2e4f0c38dcd..52edf5c5798 100644 --- a/compiler/fir/checkers/checkers.native/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/native/FirNativeErrors.kt +++ b/compiler/fir/checkers/checkers.native/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/native/FirNativeErrors.kt @@ -62,6 +62,8 @@ object FirNativeErrors { val CONSTRUCTOR_OVERRIDES_ALREADY_OVERRIDDEN_OBJC_INITIALIZER: KtDiagnosticFactory1 by error1() val CONSTRUCTOR_DOES_NOT_OVERRIDE_ANY_SUPER_CONSTRUCTOR: KtDiagnosticFactory1 by error1() val CONSTRUCTOR_MATCHES_SEVERAL_SUPER_CONSTRUCTORS: KtDiagnosticFactory1 by error1() + val CONFLICTING_OBJC_OVERLOADS: KtDiagnosticFactory1>> by error1>>() + val INAPPLICABLE_OBJC_OVERRIDE: KtDiagnosticFactory0 by error0() init { RootDiagnosticRendererFactory.registerFactory(FirNativeErrorsDefaultMessages) diff --git a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/diagnostics/native/FirNativeErrorsDefaultMessages.kt b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/diagnostics/native/FirNativeErrorsDefaultMessages.kt index 0522c143401..0a7a9b4f303 100644 --- a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/diagnostics/native/FirNativeErrorsDefaultMessages.kt +++ b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/diagnostics/native/FirNativeErrorsDefaultMessages.kt @@ -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.", + ) } } diff --git a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjcOverrideApplicabilityChecker.kt b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjcOverrideApplicabilityChecker.kt new file mode 100644 index 00000000000..65361ffdd2e --- /dev/null +++ b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjcOverrideApplicabilityChecker.kt @@ -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>, + context: CheckerContext + ): KtDiagnosticFactory1>>? { + 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) + } + } + } +} \ No newline at end of file diff --git a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/NativeDeclarationCheckers.kt b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/NativeDeclarationCheckers.kt index 18a4f9dece9..079e203d43f 100644 --- a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/NativeDeclarationCheckers.kt +++ b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/NativeDeclarationCheckers.kt @@ -15,7 +15,12 @@ object NativeDeclarationCheckers : DeclarationCheckers() { FirNativeSharedImmutableChecker, FirNativeThreadLocalChecker, FirNativeIdentifierChecker, - FirNativeObjCNameChecker + FirNativeObjCNameChecker, + ) + + override val functionCheckers: Set + get() = setOf( + FirNativeObjcOverrideApplicabilityChecker ) override val callableDeclarationCheckers: Set diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirNonSuppressibleErrorNames.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirNonSuppressibleErrorNames.kt index 66af294e8a2..0cb2cf9b13e 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirNonSuppressibleErrorNames.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirNonSuppressibleErrorNames.kt @@ -679,6 +679,8 @@ val FIR_NON_SUPPRESSIBLE_ERROR_NAMES: Set = 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", diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirConflictsDeclarationChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirConflictsDeclarationChecker.kt index 7329e2b3d2a..b7d2de6ed5c 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirConflictsDeclarationChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirConflictsDeclarationChecker.kt @@ -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>, + context: CheckerContext + ): KtDiagnosticFactory1>>? + + object DEFAULT : PlatformConflictDeclarationsDiagnosticDispatcher { + override fun getDiagnostic( + conflictingDeclaration: FirBasedSymbol<*>, + symbols: SmartSet>, + context: CheckerContext + ): KtDiagnosticFactory1>> { + 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) } } } diff --git a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/FirNativeSessionFactory.kt b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/FirNativeSessionFactory.kt index 5185404104c..9ae4603140f 100644 --- a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/FirNativeSessionFactory.kt +++ b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/FirNativeSessionFactory.kt @@ -7,23 +7,25 @@ package org.jetbrains.kotlin.fir.session import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.fir.* +import org.jetbrains.kotlin.fir.analysis.checkers.declaration.PlatformConflictDeclarationsDiagnosticDispatcher import org.jetbrains.kotlin.fir.analysis.native.checkers.FirNativeCastChecker +import org.jetbrains.kotlin.fir.analysis.native.checkers.NativeConflictDeclarationsDiagnosticDispatcher import org.jetbrains.kotlin.fir.backend.native.FirNativeClassMapper +import org.jetbrains.kotlin.fir.backend.native.FirNativeOverrideChecker import org.jetbrains.kotlin.fir.checkers.registerNativeCheckers import org.jetbrains.kotlin.fir.deserialization.ModuleDataProvider import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar import org.jetbrains.kotlin.fir.java.FirProjectSessionProvider import org.jetbrains.kotlin.fir.resolve.providers.impl.FirBuiltinSyntheticFunctionInterfaceProvider import org.jetbrains.kotlin.fir.scopes.FirKotlinScopeProvider +import org.jetbrains.kotlin.fir.scopes.FirOverrideChecker import org.jetbrains.kotlin.fir.scopes.FirPlatformClassMapper import org.jetbrains.kotlin.fir.session.FirSessionFactoryHelper.registerDefaultComponents -import org.jetbrains.kotlin.library.isNativeStdlib import org.jetbrains.kotlin.library.metadata.impl.KlibResolvedModuleDescriptorsFactoryImpl.Companion.FORWARD_DECLARATIONS_MODULE_NAME import org.jetbrains.kotlin.library.metadata.resolver.KotlinResolvedLibrary import org.jetbrains.kotlin.name.Name object FirNativeSessionFactory : FirAbstractSessionFactory() { - @OptIn(SessionConfiguration::class) fun createLibrarySession( mainModuleName: Name, resolvedLibraries: List, @@ -63,7 +65,6 @@ object FirNativeSessionFactory : FirAbstractSessionFactory() { }) } - @OptIn(SessionConfiguration::class) fun createModuleBasedSession( moduleData: FirModuleData, sessionProvider: FirProjectSessionProvider, @@ -102,5 +103,7 @@ object FirNativeSessionFactory : FirAbstractSessionFactory() { fun FirSession.registerNativeComponents() { register(FirPlatformClassMapper::class, FirNativeClassMapper()) register(FirPlatformSpecificCastChecker::class, FirNativeCastChecker) + register(PlatformConflictDeclarationsDiagnosticDispatcher::class, NativeConflictDeclarationsDiagnosticDispatcher) + register(FirOverrideChecker::class, FirNativeOverrideChecker(this)) } } diff --git a/compiler/fir/native/src/org/jetbrains/kotlin/fir/backend/native/interop/FirObjCInterop.kt b/compiler/fir/native/src/org/jetbrains/kotlin/fir/backend/native/interop/FirObjCInterop.kt index e109da7e12d..5872c028ec0 100644 --- a/compiler/fir/native/src/org/jetbrains/kotlin/fir/backend/native/interop/FirObjCInterop.kt +++ b/compiler/fir/native/src/org/jetbrains/kotlin/fir/backend/native/interop/FirObjCInterop.kt @@ -23,7 +23,7 @@ import org.jetbrains.kotlin.name.NativeStandardInteropNames import org.jetbrains.kotlin.native.interop.ObjCMethodInfo -internal fun FirFunctionSymbol<*>.getObjCMethodInfoFromOverriddenFunctions(session: FirSession, scopeSession: ScopeSession): ObjCMethodInfo? { +fun FirFunctionSymbol<*>.getObjCMethodInfoFromOverriddenFunctions(session: FirSession, scopeSession: ScopeSession): ObjCMethodInfo? { decodeObjCMethodAnnotation(session)?.let { return it } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/OverloadResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/OverloadResolver.kt index fa1f75b3b62..1d56c9111e6 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/OverloadResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/OverloadResolver.kt @@ -16,9 +16,12 @@ package org.jetbrains.kotlin.resolve +import com.intellij.psi.PsiElement import com.intellij.util.containers.MultiMap import org.jetbrains.kotlin.config.LanguageVersionSettings +import org.jetbrains.kotlin.container.DefaultImplementation import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1 import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.reportOnDeclaration import org.jetbrains.kotlin.idea.MainFunctionDetector @@ -30,11 +33,35 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.platform import org.jetbrains.kotlin.resolve.scopes.MemberScope import java.util.* +@DefaultImplementation(impl = ConflictingOverloadsDispatcher.Default::class) +interface ConflictingOverloadsDispatcher { + fun getDiagnostic( + languageVersionSettings: LanguageVersionSettings, + declaration: DeclarationDescriptor, + redeclarations: Collection + ): DiagnosticFactory1>? + + object Default : ConflictingOverloadsDispatcher { + override fun getDiagnostic( + languageVersionSettings: LanguageVersionSettings, + declaration: DeclarationDescriptor, + redeclarations: Collection + ): DiagnosticFactory1>? { + return when (declaration) { + is PropertyDescriptor, is ClassifierDescriptor -> Errors.REDECLARATION + is FunctionDescriptor -> Errors.CONFLICTING_OVERLOADS + else -> null + } + } + } +} + class OverloadResolver( private val trace: BindingTrace, private val overloadFilter: OverloadFilter, private val overloadChecker: OverloadChecker, - languageVersionSettings: LanguageVersionSettings, + private val errorDispatcher: ConflictingOverloadsDispatcher, + private val languageVersionSettings: LanguageVersionSettings, mainFunctionDetectorFactory: MainFunctionDetector.Factory ) { @@ -299,12 +326,9 @@ class OverloadResolver( if (redeclarations.isEmpty()) return for (memberDescriptor in redeclarations) { - when (memberDescriptor) { - is PropertyDescriptor, - is ClassifierDescriptor -> - reportOnDeclaration(trace, memberDescriptor) { Errors.REDECLARATION.on(it, redeclarations) } - is FunctionDescriptor -> - reportOnDeclaration(trace, memberDescriptor) { Errors.CONFLICTING_OVERLOADS.on(it, redeclarations) } + val diagnostic = errorDispatcher.getDiagnostic(languageVersionSettings, memberDescriptor, redeclarations) ?: continue + reportOnDeclaration(trace, memberDescriptor) { + diagnostic.on(it, redeclarations) } } } diff --git a/compiler/testData/codegen/box/cinterop/objc/overridabilityCondition.kt b/compiler/testData/codegen/box/cinterop/objc/overridabilityCondition.kt index 68a289d39ed..e5ffd29e9e4 100644 --- a/compiler/testData/codegen/box/cinterop/objc/overridabilityCondition.kt +++ b/compiler/testData/codegen/box/cinterop/objc/overridabilityCondition.kt @@ -42,28 +42,34 @@ headerFilter = lib.h // FILE: main.kt @file:OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) -import lib.ObjCClass +// required for K1 +@file:Suppress("CONFLICTING_OVERLOADS", "UNRESOLVED_REFERENCE", "API_NOT_AVAILABLE") + +import lib.ObjCClass +import kotlinx.cinterop.ObjCSignatureOverride -@Suppress("CONFLICTING_OVERLOADS") class OverrideAll : ObjCClass() { + @ObjCSignatureOverride override fun fooWithArg(arg: Int, arg2: String?) = "D" + @ObjCSignatureOverride override fun fooWithArg(ohNoOtherName: Int, name2: String?) = "E" + @ObjCSignatureOverride override fun fooWithArg(arg: Int, name3: String?) = "F" } -@Suppress("CONFLICTING_OVERLOADS") class OverrideNone : ObjCClass() { } -@Suppress("CONFLICTING_OVERLOADS") class OverrideOne : ObjCClass() { override fun fooWithArg(arg: Int, arg2: String?) = "G" } -@Suppress("CONFLICTING_OVERLOADS") class OverrideWithDifferentFirstArgName : ObjCClass() { + @ObjCSignatureOverride override fun fooWithArg(a: Int, arg2: String?) = "H" + @ObjCSignatureOverride override fun fooWithArg(b: Int, name2: String?) = "I" + @ObjCSignatureOverride override fun fooWithArg(c: Int, name3: String?) = "J" } diff --git a/compiler/testData/diagnostics/nativeTests/noObjcOverrideConflictingOverloadsDisabled.kt b/compiler/testData/diagnostics/nativeTests/noObjcOverrideConflictingOverloadsDisabled.kt new file mode 100644 index 00000000000..6e35b3a1d5c --- /dev/null +++ b/compiler/testData/diagnostics/nativeTests/noObjcOverrideConflictingOverloadsDisabled.kt @@ -0,0 +1,131 @@ +// !LANGUAGE: -ObjCSignatureOverrideAnnotation +// FIR_IDENTICAL +// WITH_PLATFORM_LIBS + +import kotlinx.cinterop.* +import platform.darwin.* +import platform.Foundation.* +import platform.CoreFoundation.* +import platform.CoreBluetooth.* + +class Delelegate1 : CBCentralManagerDelegateProtocol, NSObject() { + override fun centralManager(central: CBCentralManager, willRestoreState: Map): Unit = TODO() + override fun centralManager( + central: CBCentralManager, + didDiscoverPeripheral: CBPeripheral, + advertisementData: Map, + RSSI: NSNumber + ): Unit = TODO() + + override fun centralManager(central: CBCentralManager, didConnectPeripheral: CBPeripheral): Unit = TODO() + override fun centralManager(central: CBCentralManager, didFailToConnectPeripheral: CBPeripheral, error: NSError?): Unit = TODO() + override fun centralManager(central: CBCentralManager, didDisconnectPeripheral: CBPeripheral, error: NSError?): Unit = TODO() + + override fun centralManager( + central: CBCentralManager, + didDisconnectPeripheral: CBPeripheral, + timestamp: Double, + isReconnecting: Boolean, + error: NSError? + ): Unit = TODO() + + override fun centralManagerDidUpdateState(central: CBCentralManager): Unit = TODO() +} + +class Delegate2 : CBCentralManagerDelegateProtocol, NSObject() { + override fun centralManager(central: CBCentralManager, willRestoreState: Map): Unit = TODO() + override fun centralManager( + central: CBCentralManager, + didDiscoverPeripheral: CBPeripheral, + advertisementData: Map, + RSSI: NSNumber + ): Unit = TODO() + + override fun centralManager(central: CBCentralManager, didConnectPeripheral: CBPeripheral): Unit = TODO() + override fun centralManager( + central: CBCentralManager, + didDisconnectPeripheral: CBPeripheral, + timestamp: Double, + isReconnecting: Boolean, + error: NSError? + ): Unit = TODO() + + override fun centralManagerDidUpdateState(central: CBCentralManager): Unit = TODO() + + override fun centralManager(central: CBCentralManager, didFailToConnectPeripheral: CBPeripheral, error: NSError?): Unit = TODO() + override fun centralManager(central: CBCentralManager, didDisconnectPeripheral: CBPeripheral, error: NSError?): Unit = TODO() +} + +class Delegate3 : CBCentralManagerDelegateProtocol, NSObject() { + override fun centralManager(central: CBCentralManager, willRestoreState: Map): Unit = TODO() + override fun centralManager( + central: CBCentralManager, + didDiscoverPeripheral: CBPeripheral, + advertisementData: Map, + RSSI: NSNumber + ): Unit = TODO() + + override fun centralManager(central: CBCentralManager, didConnectPeripheral: CBPeripheral): Unit = TODO() + override fun centralManager( + central: CBCentralManager, + didDisconnectPeripheral: CBPeripheral, + timestamp: Double, + isReconnecting: Boolean, + error: NSError? + ): Unit = TODO() + + override fun centralManagerDidUpdateState(central: CBCentralManager): Unit = TODO() + + override fun centralManager(central: CBCentralManager, didFailToConnectPeripheral: CBPeripheral, error: NSError?): Unit = TODO() + override fun centralManager(central: CBCentralManager, didDisconnectPeripheral: CBPeripheral, error: NSError?): Unit = TODO() +} + +class Delegate4 : CBCentralManagerDelegateProtocol, NSObject() { + override fun centralManager(central: CBCentralManager, willRestoreState: Map): Unit = TODO() + override fun centralManager( + central: CBCentralManager, + didDiscoverPeripheral: CBPeripheral, + advertisementData: Map, + RSSI: NSNumber + ): Unit = TODO() + + override fun centralManager(central: CBCentralManager, didConnectPeripheral: CBPeripheral): Unit = TODO() + override fun centralManager( + central: CBCentralManager, + didDisconnectPeripheral: CBPeripheral, + timestamp: Double, + isReconnecting: Boolean, + error: NSError? + ): Unit = TODO() + + override fun centralManagerDidUpdateState(central: CBCentralManager): Unit = TODO() + + override fun centralManager(central: CBCentralManager, didFailToConnectPeripheral: CBPeripheral, error: NSError?): Unit = TODO() + override fun centralManager(central: CBCentralManager, didDisconnectPeripheral: CBPeripheral, error: NSError?): Unit = TODO() +} + +class Delegate5 : CBCentralManagerDelegateProtocol, NSObject() { + override fun centralManager(central: CBCentralManager, willRestoreState: Map): Unit = TODO() + override fun centralManager( + central: CBCentralManager, + didDiscoverPeripheral: CBPeripheral, + advertisementData: Map, + RSSI: NSNumber + ): Unit = TODO() + + override fun centralManager(central: CBCentralManager, didConnectPeripheral: CBPeripheral): Unit = TODO() + override fun centralManager( + central: CBCentralManager, + didDisconnectPeripheral: CBPeripheral, + timestamp: Double, + isReconnecting: Boolean, + error: NSError? + ): Unit = TODO() + + override fun centralManagerDidUpdateState(central: CBCentralManager): Unit = TODO() + + override fun centralManager(central: CBCentralManager, didFailToConnectPeripheral: CBPeripheral, error: NSError?): Unit = TODO() + override fun centralManager(central: CBCentralManager, didFailToConnectPeripheral: CBPeripheral, error: NSError?): Unit = TODO() + override fun centralManager(central: CBCentralManager, didDisconnectPeripheral: CBPeripheral, error: NSError?): Unit = TODO() +} + diff --git a/compiler/testData/diagnostics/nativeTests/noObjcOverrideConflictingOverloadsEnabled.kt b/compiler/testData/diagnostics/nativeTests/noObjcOverrideConflictingOverloadsEnabled.kt new file mode 100644 index 00000000000..0948a3891f5 --- /dev/null +++ b/compiler/testData/diagnostics/nativeTests/noObjcOverrideConflictingOverloadsEnabled.kt @@ -0,0 +1,140 @@ +// !LANGUAGE: +ObjCSignatureOverrideAnnotation +// !API_VERSION: 2.0 +// FIR_IDENTICAL +// WITH_PLATFORM_LIBS + +import kotlinx.cinterop.* +import platform.darwin.* +import platform.Foundation.* +import platform.CoreFoundation.* +import platform.CoreBluetooth.* + +class Delelegate1 : CBCentralManagerDelegateProtocol, NSObject() { + override fun centralManager(central: CBCentralManager, willRestoreState: Map): Unit = TODO() + override fun centralManager( + central: CBCentralManager, + didDiscoverPeripheral: CBPeripheral, + advertisementData: Map, + RSSI: NSNumber + ): Unit = TODO() + + override fun centralManager(central: CBCentralManager, didConnectPeripheral: CBPeripheral): Unit = TODO() + @ObjCSignatureOverride + override fun centralManager(central: CBCentralManager, didFailToConnectPeripheral: CBPeripheral, error: NSError?): Unit = TODO() + @ObjCSignatureOverride + override fun centralManager(central: CBCentralManager, didDisconnectPeripheral: CBPeripheral, error: NSError?): Unit = TODO() + + override fun centralManager( + central: CBCentralManager, + didDisconnectPeripheral: CBPeripheral, + timestamp: Double, + isReconnecting: Boolean, + error: NSError? + ): Unit = TODO() + + override fun centralManagerDidUpdateState(central: CBCentralManager): Unit = TODO() +} + +class Delegate2 : CBCentralManagerDelegateProtocol, NSObject() { + override fun centralManager(central: CBCentralManager, willRestoreState: Map): Unit = TODO() + override fun centralManager( + central: CBCentralManager, + didDiscoverPeripheral: CBPeripheral, + advertisementData: Map, + RSSI: NSNumber + ): Unit = TODO() + + override fun centralManager(central: CBCentralManager, didConnectPeripheral: CBPeripheral): Unit = TODO() + override fun centralManager( + central: CBCentralManager, + didDisconnectPeripheral: CBPeripheral, + timestamp: Double, + isReconnecting: Boolean, + error: NSError? + ): Unit = TODO() + + override fun centralManagerDidUpdateState(central: CBCentralManager): Unit = TODO() + + override fun centralManager(central: CBCentralManager, didFailToConnectPeripheral: CBPeripheral, error: NSError?): Unit = TODO() + override fun centralManager(central: CBCentralManager, didDisconnectPeripheral: CBPeripheral, error: NSError?): Unit = TODO() +} + +class Delegate3 : CBCentralManagerDelegateProtocol, NSObject() { + override fun centralManager(central: CBCentralManager, willRestoreState: Map): Unit = TODO() + override fun centralManager( + central: CBCentralManager, + didDiscoverPeripheral: CBPeripheral, + advertisementData: Map, + RSSI: NSNumber + ): Unit = TODO() + + override fun centralManager(central: CBCentralManager, didConnectPeripheral: CBPeripheral): Unit = TODO() + override fun centralManager( + central: CBCentralManager, + didDisconnectPeripheral: CBPeripheral, + timestamp: Double, + isReconnecting: Boolean, + error: NSError? + ): Unit = TODO() + + override fun centralManagerDidUpdateState(central: CBCentralManager): Unit = TODO() + + @ObjCSignatureOverride + override fun centralManager(central: CBCentralManager, didFailToConnectPeripheral: CBPeripheral, error: NSError?): Unit = TODO() + override fun centralManager(central: CBCentralManager, didDisconnectPeripheral: CBPeripheral, error: NSError?): Unit = TODO() +} + +class Delegate4 : CBCentralManagerDelegateProtocol, NSObject() { + override fun centralManager(central: CBCentralManager, willRestoreState: Map): Unit = TODO() + override fun centralManager( + central: CBCentralManager, + didDiscoverPeripheral: CBPeripheral, + advertisementData: Map, + RSSI: NSNumber + ): Unit = TODO() + + override fun centralManager(central: CBCentralManager, didConnectPeripheral: CBPeripheral): Unit = TODO() + override fun centralManager( + central: CBCentralManager, + didDisconnectPeripheral: CBPeripheral, + timestamp: Double, + isReconnecting: Boolean, + error: NSError? + ): Unit = TODO() + + override fun centralManagerDidUpdateState(central: CBCentralManager): Unit = TODO() + + @ObjCSignatureOverride + override fun centralManager(central: CBCentralManager, didFailToConnectPeripheral: CBPeripheral, error: NSError?): Unit = TODO() + @ObjCSignatureOverride + override fun centralManager(central: CBCentralManager, didDisconnectPeripheral: CBPeripheral, error: NSError?): Unit = TODO() +} + +class Delegate5 : CBCentralManagerDelegateProtocol, NSObject() { + override fun centralManager(central: CBCentralManager, willRestoreState: Map): Unit = TODO() + override fun centralManager( + central: CBCentralManager, + didDiscoverPeripheral: CBPeripheral, + advertisementData: Map, + RSSI: NSNumber + ): Unit = TODO() + + override fun centralManager(central: CBCentralManager, didConnectPeripheral: CBPeripheral): Unit = TODO() + override fun centralManager( + central: CBCentralManager, + didDisconnectPeripheral: CBPeripheral, + timestamp: Double, + isReconnecting: Boolean, + error: NSError? + ): Unit = TODO() + + override fun centralManagerDidUpdateState(central: CBCentralManager): Unit = TODO() + + @ObjCSignatureOverride + override fun centralManager(central: CBCentralManager, didFailToConnectPeripheral: CBPeripheral, error: NSError?): Unit = TODO() + @ObjCSignatureOverride + override fun centralManager(central: CBCentralManager, didFailToConnectPeripheral: CBPeripheral, error: NSError?): Unit = TODO() + @ObjCSignatureOverride + override fun centralManager(central: CBCentralManager, didDisconnectPeripheral: CBPeripheral, error: NSError?): Unit = TODO() +} + diff --git a/compiler/testData/diagnostics/nativeTests/objcOverrideApplicability.kt b/compiler/testData/diagnostics/nativeTests/objcOverrideApplicability.kt new file mode 100644 index 00000000000..4b27b55fc2c --- /dev/null +++ b/compiler/testData/diagnostics/nativeTests/objcOverrideApplicability.kt @@ -0,0 +1,22 @@ +// !API_VERSION: 2.0 +// FIR_IDENTICAL +// WITH_PLATFORM_LIBS + +import kotlinx.cinterop.* +import platform.darwin.* + +@ObjCSignatureOverride +fun foo() = 1 + +class A { + @ObjCSignatureOverride + fun foo() = 1 +} + +@ObjCSignatureOverride +class B : NSObject() { + @ObjCSignatureOverride + fun foo() = 1 + @ObjCSignatureOverride + val v = 1 +} diff --git a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt index e02c2186233..64b32346d5a 100644 --- a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt +++ b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt @@ -310,6 +310,7 @@ enum class LanguageFeature( DisableCompatibilityModeForNewInference(KOTLIN_2_0, kind = OTHER), // KT-63558 (umbrella), KT-64306, KT-64307, KT-64308 DfaBooleanVariables(KOTLIN_2_0), // KT-25747 LightweightLambdas(KOTLIN_2_0, kind = OTHER), // KT-45375 + ObjCSignatureOverrideAnnotation(KOTLIN_2_0, sinceApiVersion = ApiVersion.KOTLIN_2_0, kind = OTHER), // KT-61323 // 2.1 diff --git a/core/compiler.common.native/src/org/jetbrains/kotlin/name/NativeStandardInteropNames.kt b/core/compiler.common.native/src/org/jetbrains/kotlin/name/NativeStandardInteropNames.kt index 5f59b84067c..34accf4b8e9 100644 --- a/core/compiler.common.native/src/org/jetbrains/kotlin/name/NativeStandardInteropNames.kt +++ b/core/compiler.common.native/src/org/jetbrains/kotlin/name/NativeStandardInteropNames.kt @@ -25,6 +25,9 @@ object NativeStandardInteropNames { val objCOutletClassId = ClassId(cInteropPackage, Name.identifier("ObjCOutlet")) val objCOverrideInitClassId = ClassId(cInteropPackage, Name.identifier("ObjCObjectBase.OverrideInit")) + object Annotations { + val objCSignatureOverrideClassId = ClassId(cInteropPackage, Name.identifier("ObjCSignatureOverride")) + } object ForwardDeclarations { private val cNamesPackage = FqName("cnames") diff --git a/kotlin-native/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Annotations.kt b/kotlin-native/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Annotations.kt index 13a5f252d19..869b08ddc7a 100644 --- a/kotlin-native/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Annotations.kt +++ b/kotlin-native/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Annotations.kt @@ -63,3 +63,21 @@ public annotation class BetaInteropApi @Retention(AnnotationRetention.BINARY) @RequiresOptIn(level = RequiresOptIn.Level.ERROR) public annotation class ExperimentalForeignApi + +/** + * Marks functions for which Objective-C rules should be used for determinating whether two functions are conflicting. + * + * Objective-C allows overloads by selector. Such functions can have same types, and differ only by parameter names in Kotlin, + * while it is not generally supported. + * + * This leads to an error, when a Kotlin class attempts to override both versions of a method that is overloaded by names only. + * + * If all such methods are annotated by this annotation, the error would be suppressed. + * + * Annotation is only applicable to overrides of methods coming from Objective-C interop. + */ +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.SOURCE) +@Suppress("NEWER_VERSION_IN_SINCE_KOTLIN") +@SinceKotlin("2.0") +public annotation class ObjCSignatureOverride \ No newline at end of file diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/FirFrontend.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/FirFrontend.kt index ba5931b75c4..bef07c1a1f8 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/FirFrontend.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/FirFrontend.kt @@ -68,9 +68,6 @@ internal inline fun PhaseContext.firFrontend( metadataCompilationMode = config.metadataKlib, isCommonSource = isCommonSource, fileBelongsToModule = fileBelongsToModule, - registerExtraComponents = { - it.register(FirOverrideChecker::class, FirNativeOverrideChecker(it)) - }, ) val outputs = sessionsWithSources.map { (session, sources) -> diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/DefaultErrorMessagesNative.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/DefaultErrorMessagesNative.kt index b951b62bbda..8ea7873be69 100644 --- a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/DefaultErrorMessagesNative.kt +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/DefaultErrorMessagesNative.kt @@ -90,6 +90,15 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy { "Can't refer to forward declaration ''{0}'' from class literal", Renderers.RENDER_TYPE ) + put( + ErrorsNative.CONFLICTING_OBJC_OVERLOADS, + "Conflicting overloads: {0}. Add @ObjCSignatureOverride to allow collision for functions inherited from Objective-C.", + CommonRenderers.commaSeparated(Renderers.FQ_NAMES_IN_TYPES) + ) + put( + ErrorsNative.INAPPLICABLE_OBJC_OVERRIDE, + "@ObjCSignatureOverride is only allowed on functions inherited from Objective-C.", + ) } } diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/ErrorsNative.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/ErrorsNative.kt index 28c1beacf17..83618388f34 100644 --- a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/ErrorsNative.kt +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/ErrorsNative.kt @@ -69,6 +69,10 @@ object ErrorsNative { val FORWARD_DECLARATION_AS_REIFIED_TYPE_ARGUMENT = DiagnosticFactory1.create(Severity.ERROR) @JvmField val FORWARD_DECLARATION_AS_CLASS_LITERAL = DiagnosticFactory1.create(Severity.ERROR) + @JvmField + val CONFLICTING_OBJC_OVERLOADS = DiagnosticFactory1.create>(Severity.ERROR) + @JvmField + val INAPPLICABLE_OBJC_OVERRIDE = DiagnosticFactory0.create(Severity.ERROR) init { Errors.Initializer.initializeFactoryNames(ErrorsNative::class.java) diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeConflictingOverloadsDispatcher.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeConflictingOverloadsDispatcher.kt new file mode 100644 index 00000000000..c67797a10f7 --- /dev/null +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeConflictingOverloadsDispatcher.kt @@ -0,0 +1,50 @@ +/* + * 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.resolve.konan.diagnostics + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.config.LanguageVersionSettings +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1 +import org.jetbrains.kotlin.ir.objcinterop.getObjCMethodInfo +import org.jetbrains.kotlin.name.NativeStandardInteropNames.Annotations.objCSignatureOverrideClassId +import org.jetbrains.kotlin.resolve.ConflictingOverloadsDispatcher + +/** + * 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 FunctionDescriptor.hasDifferentParameterNames(other: FunctionDescriptor) : Boolean { + return valueParameters.drop(1).map { it.name } != other.valueParameters.drop(1).map { it.name } +} + +object NativeConflictingOverloadsDispatcher : ConflictingOverloadsDispatcher { + override fun getDiagnostic( + languageVersionSettings: LanguageVersionSettings, + declaration: DeclarationDescriptor, + redeclarations: Collection + ): DiagnosticFactory1>? { + if (languageVersionSettings.supportsFeature(LanguageFeature.ObjCSignatureOverrideAnnotation)) { + if (declaration is FunctionDescriptor && redeclarations.all { it is FunctionDescriptor }) { + if (declaration.getObjCMethodInfo() != null && redeclarations.all { (it as FunctionDescriptor).getObjCMethodInfo() != null }) { + if (redeclarations.all { it === declaration || (it as FunctionDescriptor).hasDifferentParameterNames(declaration) }) { + if (declaration.annotations.hasAnnotation(objCSignatureOverrideClassId.asSingleFqName())) { + return null + } else { + return ErrorsNative.CONFLICTING_OBJC_OVERLOADS + } + } + } + } + } + return ConflictingOverloadsDispatcher.Default.getDiagnostic(languageVersionSettings, declaration, redeclarations) + } +} \ No newline at end of file diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeObjCOverrideApplicabilityChecker.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeObjCOverrideApplicabilityChecker.kt new file mode 100644 index 00000000000..826e9d6e1e6 --- /dev/null +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeObjCOverrideApplicabilityChecker.kt @@ -0,0 +1,31 @@ +/* + * 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.resolve.konan.diagnostics + +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.ir.objcinterop.getObjCMethodInfo +import org.jetbrains.kotlin.name.NativeStandardInteropNames.Annotations.objCSignatureOverrideClassId +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils +import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker +import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext + + +object NativeObjcOverrideApplicabilityChecker : DeclarationChecker { + override fun check( + declaration: KtDeclaration, + descriptor: DeclarationDescriptor, + context: DeclarationCheckerContext, + ) { + if (descriptor is FunctionDescriptor) { + val annotation = descriptor.annotations.findAnnotation(objCSignatureOverrideClassId.asSingleFqName()) ?: return + if (descriptor.getObjCMethodInfo() == null) { + context.trace.report(ErrorsNative.INAPPLICABLE_OBJC_OVERRIDE.on(DescriptorToSourceUtils.getSourceFromAnnotation(annotation) ?: declaration)) + } + } + } +} \ No newline at end of file diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/platform/NativePlatformConfigurator.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/platform/NativePlatformConfigurator.kt index f070c6f903a..32d03b4bd8c 100644 --- a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/platform/NativePlatformConfigurator.kt +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/platform/NativePlatformConfigurator.kt @@ -30,6 +30,7 @@ object NativePlatformConfigurator : PlatformConfiguratorBase( NativeObjCNameChecker, NativeObjCNameOverridesChecker, NativeObjCRefinementChecker, NativeObjCRefinementAnnotationChecker, NativeObjCRefinementOverridesChecker, NativeHiddenFromObjCInheritanceChecker, + NativeObjcOverrideApplicabilityChecker, ), platformSpecificCastChecker = NativePlatformSpecificCastChecker ) { @@ -37,6 +38,7 @@ object NativePlatformConfigurator : PlatformConfiguratorBase( container.useInstance(NativeInliningRule) container.useImpl() container.useImpl() + container.useInstance(NativeConflictingOverloadsDispatcher) } override fun configureModuleDependentCheckers(container: StorageComponentContainer) { diff --git a/native/native.tests/testData/interop/objc/smoke.kt b/native/native.tests/testData/interop/objc/smoke.kt index 3d9fb5e5c91..953fe0f2458 100644 --- a/native/native.tests/testData/interop/objc/smoke.kt +++ b/native/native.tests/testData/interop/objc/smoke.kt @@ -119,7 +119,7 @@ class Bar : Foo() { } } -@Suppress("CONFLICTING_OVERLOADS") +@Suppress("CONFLICTING_OBJC_OVERLOADS") class MutablePairImpl(first: Int, second: Int) : NSObject(), MutablePairProtocol { private var elements = intArrayOf(first, second) diff --git a/native/native.tests/testData/interop/objc/smoke_noopgc.kt b/native/native.tests/testData/interop/objc/smoke_noopgc.kt index 3d9fb5e5c91..953fe0f2458 100644 --- a/native/native.tests/testData/interop/objc/smoke_noopgc.kt +++ b/native/native.tests/testData/interop/objc/smoke_noopgc.kt @@ -119,7 +119,7 @@ class Bar : Foo() { } } -@Suppress("CONFLICTING_OVERLOADS") +@Suppress("CONFLICTING_OBJC_OVERLOADS") class MutablePairImpl(first: Int, second: Int) : NSObject(), MutablePairProtocol { private var elements = intArrayOf(first, second) diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/diagnostics/DiagnosticsNativeTestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/diagnostics/DiagnosticsNativeTestGenerated.java index 773be1533c4..dc1d58f20da 100644 --- a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/diagnostics/DiagnosticsNativeTestGenerated.java +++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/diagnostics/DiagnosticsNativeTestGenerated.java @@ -66,6 +66,18 @@ public class DiagnosticsNativeTestGenerated extends AbstractDiagnosticsNativeTes runTest("compiler/testData/diagnostics/nativeTests/nativeProtectedFunCall.kt"); } + @Test + @TestMetadata("noObjcOverrideConflictingOverloadsDisabled.kt") + public void testNoObjcOverrideConflictingOverloadsDisabled() throws Exception { + runTest("compiler/testData/diagnostics/nativeTests/noObjcOverrideConflictingOverloadsDisabled.kt"); + } + + @Test + @TestMetadata("noObjcOverrideConflictingOverloadsEnabled.kt") + public void testNoObjcOverrideConflictingOverloadsEnabled() throws Exception { + runTest("compiler/testData/diagnostics/nativeTests/noObjcOverrideConflictingOverloadsEnabled.kt"); + } + @Test @TestMetadata("objCName.kt") public void testObjCName() throws Exception { @@ -102,6 +114,12 @@ public class DiagnosticsNativeTestGenerated extends AbstractDiagnosticsNativeTes runTest("compiler/testData/diagnostics/nativeTests/objCRefinement.kt"); } + @Test + @TestMetadata("objcOverrideApplicability.kt") + public void testObjcOverrideApplicability() throws Exception { + runTest("compiler/testData/diagnostics/nativeTests/objcOverrideApplicability.kt"); + } + @Test @TestMetadata("resolveToDelegatedProperty.kt") public void testResolveToDelegatedProperty() throws Exception { diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/diagnostics/FirLightTreeOldFrontendNativeDiagnosticsTestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/diagnostics/FirLightTreeOldFrontendNativeDiagnosticsTestGenerated.java index b401a5a771c..6e2a384ffa8 100644 --- a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/diagnostics/FirLightTreeOldFrontendNativeDiagnosticsTestGenerated.java +++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/diagnostics/FirLightTreeOldFrontendNativeDiagnosticsTestGenerated.java @@ -70,6 +70,18 @@ public class FirLightTreeOldFrontendNativeDiagnosticsTestGenerated extends Abstr runTest("compiler/testData/diagnostics/nativeTests/nativeProtectedFunCall.kt"); } + @Test + @TestMetadata("noObjcOverrideConflictingOverloadsDisabled.kt") + public void testNoObjcOverrideConflictingOverloadsDisabled() throws Exception { + runTest("compiler/testData/diagnostics/nativeTests/noObjcOverrideConflictingOverloadsDisabled.kt"); + } + + @Test + @TestMetadata("noObjcOverrideConflictingOverloadsEnabled.kt") + public void testNoObjcOverrideConflictingOverloadsEnabled() throws Exception { + runTest("compiler/testData/diagnostics/nativeTests/noObjcOverrideConflictingOverloadsEnabled.kt"); + } + @Test @TestMetadata("objCName.kt") public void testObjCName() throws Exception { @@ -106,6 +118,12 @@ public class FirLightTreeOldFrontendNativeDiagnosticsTestGenerated extends Abstr runTest("compiler/testData/diagnostics/nativeTests/objCRefinement.kt"); } + @Test + @TestMetadata("objcOverrideApplicability.kt") + public void testObjcOverrideApplicability() throws Exception { + runTest("compiler/testData/diagnostics/nativeTests/objcOverrideApplicability.kt"); + } + @Test @TestMetadata("resolveToDelegatedProperty.kt") public void testResolveToDelegatedProperty() throws Exception { diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/diagnostics/FirPsiOldFrontendNativeDiagnosticsTestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/diagnostics/FirPsiOldFrontendNativeDiagnosticsTestGenerated.java index 58565265b3e..fa22bbb762f 100644 --- a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/diagnostics/FirPsiOldFrontendNativeDiagnosticsTestGenerated.java +++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/diagnostics/FirPsiOldFrontendNativeDiagnosticsTestGenerated.java @@ -70,6 +70,18 @@ public class FirPsiOldFrontendNativeDiagnosticsTestGenerated extends AbstractFir runTest("compiler/testData/diagnostics/nativeTests/nativeProtectedFunCall.kt"); } + @Test + @TestMetadata("noObjcOverrideConflictingOverloadsDisabled.kt") + public void testNoObjcOverrideConflictingOverloadsDisabled() throws Exception { + runTest("compiler/testData/diagnostics/nativeTests/noObjcOverrideConflictingOverloadsDisabled.kt"); + } + + @Test + @TestMetadata("noObjcOverrideConflictingOverloadsEnabled.kt") + public void testNoObjcOverrideConflictingOverloadsEnabled() throws Exception { + runTest("compiler/testData/diagnostics/nativeTests/noObjcOverrideConflictingOverloadsEnabled.kt"); + } + @Test @TestMetadata("objCName.kt") public void testObjCName() throws Exception { @@ -106,6 +118,12 @@ public class FirPsiOldFrontendNativeDiagnosticsTestGenerated extends AbstractFir runTest("compiler/testData/diagnostics/nativeTests/objCRefinement.kt"); } + @Test + @TestMetadata("objcOverrideApplicability.kt") + public void testObjcOverrideApplicability() throws Exception { + runTest("compiler/testData/diagnostics/nativeTests/objcOverrideApplicability.kt"); + } + @Test @TestMetadata("resolveToDelegatedProperty.kt") public void testResolveToDelegatedProperty() throws Exception {