From dbba6d09ffcb108b586f2a24748c7cfa3291aa9c Mon Sep 17 00:00:00 2001 From: Vladimir Sukharev Date: Tue, 14 Nov 2023 14:28:44 +0100 Subject: [PATCH] [K/N] Implement FirNativeObjCOverrideInitChecker ^KT-61564 --- .../diagnostics/FirNativeDiagnosticsList.kt | 9 ++ .../diagnostics/native/FirNativeErrors.kt | 3 + .../native/FirNativeErrorsDefaultMessages.kt | 18 +++ .../FirNativeObjCOverrideInitChecker.kt | 120 ++++++++++++++++++ .../checkers/NativeDeclarationCheckers.kt | 1 + .../FirNonSuppressibleErrorNames.kt | 3 + .../backend/native/interop/FirObjCInterop.kt | 2 +- .../specialBackendChecks/objCInterop/t32.kt | 4 +- .../specialBackendChecks/objCInterop/t33.kt | 4 +- 9 files changed, 159 insertions(+), 5 deletions(-) create mode 100644 compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCOverrideInitChecker.kt 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 30b87c37b20..d8ef9c1076d 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 @@ -94,5 +94,14 @@ object NATIVE_DIAGNOSTICS_LIST : DiagnosticList("FirNativeErrors") { parameter("annotationKind") parameter("unexpectedType") } + val CONSTRUCTOR_OVERRIDES_ALREADY_OVERRIDDEN_OBJC_INITIALIZER by error() { + parameter("annotation") + } + val CONSTRUCTOR_DOES_NOT_OVERRIDE_ANY_SUPER_CONSTRUCTOR by error() { + parameter("annotation") + } + val CONSTRUCTOR_MATCHES_SEVERAL_SUPER_CONSTRUCTORS by error() { + parameter("annotation") + } } } 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 200a9292ac4..2e4f0c38dcd 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 @@ -59,6 +59,9 @@ object FirNativeErrors { val MUST_NOT_HAVE_EXTENSION_RECEIVER: KtDiagnosticFactory1 by error1() val MUST_BE_OBJC_OBJECT_TYPE: KtDiagnosticFactory2 by error2() val MUST_BE_UNIT_TYPE: KtDiagnosticFactory2 by error2() + 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() 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 d95e14bd3d1..c99fc092480 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,9 @@ 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.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 import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.EMPTY_OBJC_NAME import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.FORWARD_DECLARATION_AS_CLASS_LITERAL import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.FORWARD_DECLARATION_AS_REIFIED_TYPE_ARGUMENT @@ -127,5 +130,20 @@ object FirNativeErrorsDefaultMessages : BaseDiagnosticRendererFactory() { TO_STRING, FirDiagnosticRenderers.RENDER_TYPE ) + map.put( + CONSTRUCTOR_OVERRIDES_ALREADY_OVERRIDDEN_OBJC_INITIALIZER, + "Constructor with @{0} overrides initializer that is already overridden explicitly", + TO_STRING + ) + map.put( + CONSTRUCTOR_DOES_NOT_OVERRIDE_ANY_SUPER_CONSTRUCTOR, + "Constructor with @{0} doesn''t override any super class constructor.\nIt must completely match by parameter names and types.", + TO_STRING + ) + map.put( + CONSTRUCTOR_MATCHES_SEVERAL_SUPER_CONSTRUCTORS, + "Constructor with @{0} matches more than one of super constructors", + TO_STRING + ) } } diff --git a/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCOverrideInitChecker.kt b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCOverrideInitChecker.kt new file mode 100644 index 00000000000..448eca5c3dd --- /dev/null +++ b/compiler/fir/checkers/checkers.native/src/org/jetbrains/kotlin/fir/analysis/native/checkers/FirNativeObjCOverrideInitChecker.kt @@ -0,0 +1,120 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.fir.analysis.native.checkers + +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.diagnostics.reportOn +import org.jetbrains.kotlin.fir.* +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 +import org.jetbrains.kotlin.fir.backend.native.interop.getObjCInitMethod +import org.jetbrains.kotlin.fir.backend.native.interop.isKotlinObjCClass +import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.declarations.utils.isExpect +import org.jetbrains.kotlin.fir.declarations.utils.isOverride +import org.jetbrains.kotlin.fir.resolve.ScopeSession +import org.jetbrains.kotlin.fir.resolve.fullyExpandedType +import org.jetbrains.kotlin.fir.resolve.toSymbol +import org.jetbrains.kotlin.fir.scopes.getDirectOverriddenMembers +import org.jetbrains.kotlin.fir.symbols.SymbolInternals +import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol +import org.jetbrains.kotlin.fir.types.* +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name + +object FirNativeObjCOverrideInitChecker : FirClassChecker() { + val objCOverrideInitClassId = ClassId(FqName("kotlinx.cinterop"), Name.identifier("ObjCObjectBase.OverrideInit")) + + @OptIn(SymbolInternals::class) + override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) { + val session = context.session + + fun FirConstructor.overridesConstructor(other: FirConstructor): Boolean { + if (valueParameters.size != other.valueParameters.size) + return false + return valueParameters.zip(other.valueParameters).all { (thisParameter, otherParameter) -> + thisParameter.name == otherParameter.name && thisParameter.returnTypeRef.coneType == otherParameter.returnTypeRef.coneType + } + } + + fun checkCanGenerateOverrideInit(firClass: FirClass, constructor: FirConstructor) { + val superClass = (firClass as FirRegularClass).symbol.getSuperClassSymbolOrAny(session) + val superConstructors = superClass.fir.constructors(session).filter { + constructor.overridesConstructor(it.fir) + }.toList() + + val superConstructor: FirConstructorSymbol = superConstructors.singleOrNull() ?: run { + if (superConstructors.isEmpty()) + reporter.reportOn( + constructor.source, + FirNativeErrors.CONSTRUCTOR_DOES_NOT_OVERRIDE_ANY_SUPER_CONSTRUCTOR, + objCOverrideInitClassId.asSingleFqName(), + context + ) + else + reporter.reportOn( + constructor.source, + FirNativeErrors.CONSTRUCTOR_MATCHES_SEVERAL_SUPER_CONSTRUCTORS, + objCOverrideInitClassId.asSingleFqName(), + context + ) + return + } + + val initMethod = superConstructor.fir.getObjCInitMethod(session, ScopeSession())!! + + // Remove fake overrides of this init method, also check for explicit overriding: + firClass.declarations.forEach { + if (it is FirSimpleFunction && initMethod.symbol in it.getOverriddenSymbols(context) && !it.isSubstitutionOrIntersectionOverride) { + reporter.reportOn( + constructor.source, + FirNativeErrors.CONSTRUCTOR_OVERRIDES_ALREADY_OVERRIDDEN_OBJC_INITIALIZER, + objCOverrideInitClassId.asSingleFqName(), + context + ) + } + } + } + + fun checkKotlinObjCClass(firClass: FirClass) { + for (decl in firClass.declarations) { + if (decl is FirConstructor && decl.annotations.hasAnnotation(objCOverrideInitClassId, session)) + checkCanGenerateOverrideInit(firClass, decl) + } + } + + if (!declaration.isExpect && declaration.symbol.isKotlinObjCClass(context.session)) { + checkKotlinObjCClass(declaration) + } + } +} + +// Copy-pasted from plugins/noarg/noarg.k2/src/org/jetbrains/kotlin/noarg/fir/FirNoArgDeclarationChecker.kt +private fun FirRegularClassSymbol.getSuperClassSymbolOrAny(session: FirSession): FirRegularClassSymbol { + for (superType in resolvedSuperTypes) { + val symbol = superType.fullyExpandedType(session).toRegularClassSymbol(session) ?: continue + if (symbol.classKind == ClassKind.CLASS) return symbol + } + return session.builtinTypes.anyType.type.toRegularClassSymbol(session) ?: error("Symbol for Any not found") +} + +// copy-pasted from org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirInlineDeclarationChecker +private fun FirCallableDeclaration.getOverriddenSymbols(context: CheckerContext): List> { + if (!this.isOverride) return emptyList() + val classSymbol = this.containingClassLookupTag()?.toSymbol(context.session) as? FirClassSymbol<*> ?: return emptyList() + val scope = classSymbol.unsubstitutedScope(context) + //this call is needed because AbstractFirUseSiteMemberScope collect overrides in it only, + //and not in processDirectOverriddenFunctionsWithBaseScope + scope.processFunctionsByName(this.symbol.name) { } + return scope.getDirectOverriddenMembers(this.symbol, true) +} 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 3d66c35eee2..7ec275334c5 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 @@ -28,6 +28,7 @@ object NativeDeclarationCheckers : DeclarationCheckers() { FirNativeObjCNameOverridesChecker, FirNativeObjCOutletChecker, FirNativeObjCActionChecker, + FirNativeObjCOverrideInitChecker, ) override val regularClassCheckers: 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 477c214b693..a1d73bc2ae0 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 @@ -672,6 +672,9 @@ val FIR_NON_SUPPRESSIBLE_ERROR_NAMES: Set = setOf( "MUST_NOT_HAVE_EXTENSION_RECEIVER", "MUST_BE_OBJC_OBJECT_TYPE", "MUST_BE_UNIT_TYPE", + "CONSTRUCTOR_OVERRIDES_ALREADY_OVERRIDDEN_OBJC_INITIALIZER", + "CONSTRUCTOR_DOES_NOT_OVERRIDE_ANY_SUPER_CONSTRUCTOR", + "CONSTRUCTOR_MATCHES_SEVERAL_SUPER_CONSTRUCTORS", "NESTED_EXTERNAL_DECLARATION", "WRONG_EXTERNAL_DECLARATION", "NESTED_CLASS_IN_EXTERNAL_INTERFACE", 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 9cfb1c0e3e9..bff1ead2e12 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 @@ -53,7 +53,7 @@ internal fun FirFunction.getObjCMethodInfoFromOverriddenFunctions(session: FirSe * mimics ConstructorDescriptor.getObjCInitMethod() */ @OptIn(SymbolInternals::class) -private fun FirConstructor.getObjCInitMethod(session: FirSession, scopeSession: ScopeSession): FirFunction? { +fun FirConstructor.getObjCInitMethod(session: FirSession, scopeSession: ScopeSession): FirFunction? { this.annotations.getAnnotationByClassId(NativeStandardInteropNames.objCConstructorClassId, session)?.let { annotation -> val initSelector: String = annotation.constStringArgument("initSelector") val classSymbol = containingClassLookupTag()?.toSymbol(session) as FirClassSymbol<*> diff --git a/compiler/testData/diagnostics/nativeTests/specialBackendChecks/objCInterop/t32.kt b/compiler/testData/diagnostics/nativeTests/specialBackendChecks/objCInterop/t32.kt index b1d155e3941..55e9584de19 100644 --- a/compiler/testData/diagnostics/nativeTests/specialBackendChecks/objCInterop/t32.kt +++ b/compiler/testData/diagnostics/nativeTests/specialBackendChecks/objCInterop/t32.kt @@ -5,7 +5,7 @@ import platform.darwin.* import platform.Foundation.* class Zzz : NSAssertionHandler { - @OptIn(kotlinx.cinterop.BetaInteropApi::class) + @OptIn(kotlinx.cinterop.BetaInteropApi::class) @OverrideInit - constructor(x: Int) { } + constructor(x: Int) { } } diff --git a/compiler/testData/diagnostics/nativeTests/specialBackendChecks/objCInterop/t33.kt b/compiler/testData/diagnostics/nativeTests/specialBackendChecks/objCInterop/t33.kt index 51e3f534fcd..42f3f2ff662 100644 --- a/compiler/testData/diagnostics/nativeTests/specialBackendChecks/objCInterop/t33.kt +++ b/compiler/testData/diagnostics/nativeTests/specialBackendChecks/objCInterop/t33.kt @@ -5,9 +5,9 @@ import platform.darwin.* import platform.Foundation.* class Zzz : NSString { - @OptIn(kotlinx.cinterop.BetaInteropApi::class) + @OptIn(kotlinx.cinterop.BetaInteropApi::class) @OverrideInit - constructor(coder: NSCoder) { } + constructor(coder: NSCoder) { } @Suppress("OVERRIDE_DEPRECATION") override fun initWithCoder(coder: NSCoder): String? = "zzz"