From 42c8b0409430dba42609d56285acf6c0677af0d9 Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Thu, 5 Dec 2024 12:29:30 +0100 Subject: [PATCH] Opt-out from annotations type safety analysis (#246) --- .../kotlinx/rpc/codegen/RpcCompilerPlugin.kt | 15 ++++++++++++- .../rpc/codegen/FirRpcAdditionalCheckers.kt | 11 ++++++++-- .../rpc/codegen/FirRpcExtensionRegistrar.kt | 3 +-- .../kotlinx/rpc/codegen/RpcFirCliOptions.kt | 22 +++++++++++++++++++ .../checkers/FirCheckedAnnotationCheckers.kt | 4 ++++ .../src/main/kotlin/kotlinx/rpc/Extensions.kt | 10 +++++++++ .../kotlin/kotlinx/rpc/RpcDangerousApi.kt | 8 +++++++ .../kotlin/kotlinx/rpc/compilerPlugins.kt | 2 ++ .../ExtensionRegistrarConfigurator.kt | 10 +++++++++ .../diagnostics/checkedAnnotation.fir.txt | 16 +++++++++++++- .../testData/diagnostics/checkedAnnotation.kt | 18 +++++++++++++++ 11 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/RpcFirCliOptions.kt create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/RpcDangerousApi.kt diff --git a/compiler-plugin/compiler-plugin-cli/src/main/latest/kotlinx/rpc/codegen/RpcCompilerPlugin.kt b/compiler-plugin/compiler-plugin-cli/src/main/latest/kotlinx/rpc/codegen/RpcCompilerPlugin.kt index 3d2fb50e..441f6794 100644 --- a/compiler-plugin/compiler-plugin-cli/src/main/latest/kotlinx/rpc/codegen/RpcCompilerPlugin.kt +++ b/compiler-plugin/compiler-plugin-cli/src/main/latest/kotlinx/rpc/codegen/RpcCompilerPlugin.kt @@ -25,6 +25,7 @@ class RpcCommandLineProcessor : CommandLineProcessor { StrictModeCliOptions.SUSPENDING_SERVER_STREAMING, StrictModeCliOptions.NOT_TOP_LEVEL_SERVER_FLOW, StrictModeCliOptions.FIELDS, + RpcFirCliOptions.ANNOTATION_TYPE_SAFETY, ) override fun processOption( @@ -32,7 +33,19 @@ class RpcCommandLineProcessor : CommandLineProcessor { value: String, configuration: CompilerConfiguration, ) { - option.processAsStrictModeOption(value, configuration) + if (option.processAsStrictModeOption(value, configuration)) { + return + } + + when (option) { + RpcFirCliOptions.ANNOTATION_TYPE_SAFETY -> { + @Suppress("NullableBooleanElvis") + configuration.put( + RpcFirConfigurationKeys.ANNOTATION_TYPE_SAFETY, + value.toBooleanStrictOrNull() ?: true, + ) + } + } } } diff --git a/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcAdditionalCheckers.kt b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcAdditionalCheckers.kt index aab618a1..2a8f3812 100644 --- a/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcAdditionalCheckers.kt +++ b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcAdditionalCheckers.kt @@ -8,6 +8,7 @@ import kotlinx.rpc.codegen.checkers.FirCheckedAnnotationHelper import kotlinx.rpc.codegen.checkers.FirRpcDeclarationCheckers import kotlinx.rpc.codegen.checkers.FirRpcExpressionCheckers import kotlinx.rpc.codegen.checkers.diagnostics.FirRpcStrictModeDiagnostics +import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.analysis.checkers.declaration.DeclarationCheckers import org.jetbrains.kotlin.fir.analysis.checkers.expression.ExpressionCheckers @@ -20,14 +21,19 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol class FirRpcAdditionalCheckers( session: FirSession, serializationIsPresent: Boolean, - modes: StrictModeAggregator, + configuration: CompilerConfiguration, ) : FirAdditionalCheckersExtension(session) { override fun FirDeclarationPredicateRegistrar.registerPredicates() { register(FirRpcPredicates.rpc) register(FirRpcPredicates.checkedAnnotationMeta) } - private val ctx = FirCheckersContext(session, serializationIsPresent, modes) + private val ctx = FirCheckersContext( + session = session, + serializationIsPresent = serializationIsPresent, + annotationTypeSafetyEnabled = configuration.get(RpcFirConfigurationKeys.ANNOTATION_TYPE_SAFETY, true), + modes = configuration.strictModeAggregator(), + ) override val declarationCheckers: DeclarationCheckers = FirRpcDeclarationCheckers(ctx) override val expressionCheckers: ExpressionCheckers = FirRpcExpressionCheckers(ctx) @@ -36,6 +42,7 @@ class FirRpcAdditionalCheckers( class FirCheckersContext( private val session: FirSession, val serializationIsPresent: Boolean, + val annotationTypeSafetyEnabled: Boolean, modes: StrictModeAggregator, ) { val strictModeDiagnostics = FirRpcStrictModeDiagnostics(modes) diff --git a/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcExtensionRegistrar.kt b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcExtensionRegistrar.kt index a29d06e3..ae29208d 100644 --- a/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcExtensionRegistrar.kt +++ b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcExtensionRegistrar.kt @@ -16,7 +16,6 @@ import org.jetbrains.kotlin.fir.extensions.FirSupertypeGenerationExtension.Facto class FirRpcExtensionRegistrar(private val configuration: CompilerConfiguration) : FirExtensionRegistrar() { override fun ExtensionRegistrarContext.configurePlugin() { val logger = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE) - val modes = configuration.strictModeAggregator() val serializationIsPresent = try { Class.forName("org.jetbrains.kotlinx.serialization.compiler.fir.SerializationFirResolveExtension") @@ -30,7 +29,7 @@ class FirRpcExtensionRegistrar(private val configuration: CompilerConfiguration) false } - +CFactory { FirRpcAdditionalCheckers(it, serializationIsPresent, modes) } + +CFactory { FirRpcAdditionalCheckers(it, serializationIsPresent, configuration) } +SFactory { FirRpcSupertypeGenerator(it, logger) } } diff --git a/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/RpcFirCliOptions.kt b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/RpcFirCliOptions.kt new file mode 100644 index 00000000..51c72067 --- /dev/null +++ b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/RpcFirCliOptions.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.codegen + +import org.jetbrains.kotlin.compiler.plugin.CliOption +import org.jetbrains.kotlin.config.CompilerConfigurationKey + +object RpcFirCliOptions { + val ANNOTATION_TYPE_SAFETY = CliOption( + optionName = "annotation-type-safety", + valueDescription = "true or false", + description = "Enables/disables annotation type safety analysis.", + required = false, + allowMultipleOccurrences = false, + ) +} + +object RpcFirConfigurationKeys { + val ANNOTATION_TYPE_SAFETY = CompilerConfigurationKey.create("annotation type safety") +} diff --git a/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/FirCheckedAnnotationCheckers.kt b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/FirCheckedAnnotationCheckers.kt index cc798f31..e9bb8aa3 100644 --- a/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/FirCheckedAnnotationCheckers.kt +++ b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/FirCheckedAnnotationCheckers.kt @@ -153,6 +153,10 @@ object FirCheckedAnnotationHelper { typeArgumentsMapper: (TypeArgument) -> ConeTypeProjection?, sourceProvider: (Origin, TypeArgument) -> KtSourceElement?, ) { + if (!ctx.annotationTypeSafetyEnabled) { + return + } + val originTransformed = originMapper(origin) ?: return val symbol = symbolProvider(originTransformed) ?: return diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt index d2c535e4..eaf6dbf3 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt @@ -10,9 +10,19 @@ import org.gradle.api.Action import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Property import org.gradle.kotlin.dsl.newInstance +import org.gradle.kotlin.dsl.property import javax.inject.Inject open class RpcExtension @Inject constructor(objects: ObjectFactory) { + /** + * Controls `@Rpc` [annotation type-safety](https://github.com/Kotlin/kotlinx-rpc/pull/240) compile-time checkers. + * + * CAUTION: Disabling is considered unsafe. + * This option is only needed to prevent cases where type-safety analysis fails and valid code can't be compiled. + */ + @RpcDangerousApi + val annotationTypeSafetyEnabled = objects.property().convention(true) + /** * Strict mode settings. * Allows configuring the reporting state of deprecated features. diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/RpcDangerousApi.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/RpcDangerousApi.kt new file mode 100644 index 00000000..17efa87e --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/RpcDangerousApi.kt @@ -0,0 +1,8 @@ +/* + * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc + +@RequiresOptIn("This API is dangerous. It is not recommended to use it, unless you know what you are doing.") +annotation class RpcDangerousApi diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/compilerPlugins.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/compilerPlugins.kt index 496fbbd7..9f3d3852 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/compilerPlugins.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/compilerPlugins.kt @@ -44,6 +44,8 @@ class CompilerPluginCli : KotlinCompilerPluginSupportPlugin by compilerPlugin({ // ), SubpluginOption("strict-not-top-level-server-flow", strict.notTopLevelServerFlow.get().toCompilerArg()), SubpluginOption("strict-fields", strict.fields.get().toCompilerArg()), + @OptIn(RpcDangerousApi::class) + SubpluginOption("annotation-type-safety", extension.annotationTypeSafetyEnabled.get().toString()), ) } } diff --git a/tests/compiler-plugin-tests/src/test/kotlin/kotlinx/rpc/codegen/test/services/ExtensionRegistrarConfigurator.kt b/tests/compiler-plugin-tests/src/test/kotlin/kotlinx/rpc/codegen/test/services/ExtensionRegistrarConfigurator.kt index 22a1814a..4907097d 100644 --- a/tests/compiler-plugin-tests/src/test/kotlin/kotlinx/rpc/codegen/test/services/ExtensionRegistrarConfigurator.kt +++ b/tests/compiler-plugin-tests/src/test/kotlin/kotlinx/rpc/codegen/test/services/ExtensionRegistrarConfigurator.kt @@ -4,6 +4,7 @@ package kotlinx.rpc.codegen.test.services +import kotlinx.rpc.codegen.RpcFirConfigurationKeys import kotlinx.rpc.codegen.StrictMode import kotlinx.rpc.codegen.StrictModeConfigurationKeys import kotlinx.rpc.codegen.registerRpcExtensions @@ -36,6 +37,14 @@ class ExtensionRegistrarConfigurator(testServices: TestServices) : EnvironmentCo configuration.put(StrictModeConfigurationKeys.FIELDS, mode) } + val annotationTypeSafety = module.directives[RpcDirectives.ANNOTATION_TYPE_SAFETY] + if (annotationTypeSafety.isNotEmpty()) { + configuration.put( + RpcFirConfigurationKeys.ANNOTATION_TYPE_SAFETY, + annotationTypeSafety.single().toBooleanStrict(), + ) + } + registerRpcExtensions(configuration) // libs @@ -45,4 +54,5 @@ class ExtensionRegistrarConfigurator(testServices: TestServices) : EnvironmentCo object RpcDirectives : SimpleDirectivesContainer() { val RPC_STRICT_MODE by stringDirective("none, warning or error", DirectiveApplicability.Module) + val ANNOTATION_TYPE_SAFETY by stringDirective("true or false", DirectiveApplicability.Module) } diff --git a/tests/compiler-plugin-tests/src/testData/diagnostics/checkedAnnotation.fir.txt b/tests/compiler-plugin-tests/src/testData/diagnostics/checkedAnnotation.fir.txt index a94a05e5..7d404e7c 100644 --- a/tests/compiler-plugin-tests/src/testData/diagnostics/checkedAnnotation.fir.txt +++ b/tests/compiler-plugin-tests/src/testData/diagnostics/checkedAnnotation.fir.txt @@ -1,4 +1,18 @@ -FILE: checkedAnnotation.kt +Module: disabled +FILE: a.kt + @R|kotlinx/rpc/annotations/CheckedTypeAnnotation|() @R|kotlin/annotation/Target|(allowedTargets = vararg(Q|kotlin/annotation/AnnotationTarget|.R|kotlin/annotation/AnnotationTarget.ANNOTATION_CLASS|, Q|kotlin/annotation/AnnotationTarget|.R|kotlin/annotation/AnnotationTarget.TYPE_PARAMETER|, Q|kotlin/annotation/AnnotationTarget|.R|kotlin/annotation/AnnotationTarget.CLASS|)) private final annotation class NotWorkingChecked : R|kotlin/Annotation| { + public constructor(): R|NotWorkingChecked| { + super() + } + + } + private final fun <@R|NotWorkingChecked|() T> checked(): R|kotlin/Unit| { + } + public final fun test(): R|kotlin/Unit| { + R|/checked|() + } +Module: main +FILE: module_main_checkedAnnotation.kt @R|kotlinx/rpc/annotations/CheckedTypeAnnotation|() @R|kotlin/annotation/Target|(allowedTargets = vararg(Q|kotlin/annotation/AnnotationTarget|.R|kotlin/annotation/AnnotationTarget.ANNOTATION_CLASS|, Q|kotlin/annotation/AnnotationTarget|.R|kotlin/annotation/AnnotationTarget.TYPE_PARAMETER|, Q|kotlin/annotation/AnnotationTarget|.R|kotlin/annotation/AnnotationTarget.CLASS|)) public final annotation class Checked : R|kotlin/Annotation| { public constructor(): R|Checked| { super() diff --git a/tests/compiler-plugin-tests/src/testData/diagnostics/checkedAnnotation.kt b/tests/compiler-plugin-tests/src/testData/diagnostics/checkedAnnotation.kt index 726b5c2b..4838f0ff 100644 --- a/tests/compiler-plugin-tests/src/testData/diagnostics/checkedAnnotation.kt +++ b/tests/compiler-plugin-tests/src/testData/diagnostics/checkedAnnotation.kt @@ -2,6 +2,24 @@ * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ +// MODULE: disabled +// ANNOTATION_TYPE_SAFETY: false +// FILE: a.kt + +import kotlinx.rpc.annotations.CheckedTypeAnnotation + +@CheckedTypeAnnotation +@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.TYPE_PARAMETER, AnnotationTarget.CLASS) +private annotation class NotWorkingChecked + +private fun <@NotWorkingChecked T> checked() {} + +fun test() { + checked() +} + +// MODULE: main + import kotlinx.rpc.annotations.CheckedTypeAnnotation // annotations