
- Frontend, responsible for parsing and transforming Kotlin code into a representation that can be interpreted by the backend and used for Kotlin code analysis.
- Backend, responsible for generating actual low-level code based on the representation received from the frontend.

Compiler frontend is responsible for parsing and analyzing Kotlin code and transforming it into a representation that is sent to the backend, on the basis of which the backend generates platform-specific files. The frontend is target-independent, but there are two frontends: older K1, and newer K2. The backend is target-specific.

Each backend shares a part that transforms the representation provided by the frontend into Kotlin intermediate representation, which is used to generate target-specific files.
Fir prefix and end with the Extension suffix. Here is the complete list of the currently supported K2 extensions[^12_1]:FirStatusTransformerExtension- called when an element status (visibility, modifiers, etc.) is established and allows it to be changed. The All-open compiler plugin uses it to make all classes with appropriate annotations open by default (e.g., used by Spring Framework).FirDeclarationGenerationExtension- can specify additional declarations to be generated for a Kotlin file. Its different methods are called at different phases of compilation and allow the generation of different kinds of elements, like classes or methods. Used by many plugins, including the Kotlin Serialization plugin, to generate serialization methods.FirAdditionalCheckersExtension- allows the specification of additional checkers that will be called when the compiler checks the code; it can also report additional errors or warnings that can be visualized by IntelliJ.FirSupertypeGenerationExtension- called when the compiler generates supertypes for a class and allows additional supertypes to be added. For instance, if the classAinherits fromBand implementsC, and the extension decides it should also have supertypesDandF, then the compiler will considerAto have supertypesB,C,DandF. Used by many plugins, including the Kotlin Serialization plugin, which uses it to make all classes annotated with theSerializerannotation have an implicitKSerializersupertype with appropriate type arguments.FirTypeAttributeExtension- allows an attribute to be added to a type based on an annotation or determines an annotation based on an attribute. Used by the experimental Kotlin Assignment plugin, which allows a number type to be annotated as either positive or negative and then uses this information to throw an error if this contract is broken. Works with the code of libraries used by our project.FirExpressionResolutionExtension- can be used to add an implicit extension receiver when a function is called. Used by the experimental Kotlin Assignment plugin, which injectsAlgebra<T>as an implicit receiver ifinjectAlgebra<T>()is called.FirSamConversionTransformerExtension- called when the compiler converts a Java SAM interface to a Kotlin function type and allows the result type to be changed. Used by theSAM-with-receiver compiler pluginto generate a function type with a receiver instead of a regular function type for SAM interfaces with appropriate annotation.FirAssignExpressionAltererExtension- allows a variable assignment to be transformed into any kind of statement. Used by the experimental Kotlin Assignment plugin, which allows the assignment operator to be overloaded.FirFunctionTypeKindExtension- allows additional function types to be registered. Works with the code of libraries used by our project.FirDeclarationsForMetadataProviderExtension- currently allows additional declarations to be added in Kotlin metadata. Used by the Kotlin Serialization plugin to generate a deserialization constructor or a method to write itself. Its behavior might change in the future.FirScriptConfiguratorExtension- currently called when the compiler processes a script; it also allows the script configuration to be changed. Its behavior might change in the future.FirExtensionSessionComponent- currently allows additional extension session components to be added for a session. In other words, it allows a component to be registered so that it can be reused by different extensions. Used by many plugins. For instance, the Kotlin Serialization plugin uses it to register a component that keeps a cache of serializers in a file or KClass first from file annotation. Its behavior might change in the future.
Beware! In this chapter we only discuss K2 frontend extensions because the K1 frontend is deprecated and will be removed in the future. However, the K2 compiler frontend is currently not used by default. To use it, you need to have at least Kotlin version 1.9.0-Beta and add the-Pkotlin.experimental.tryK2=truecompiler option.
IrGenerationExtension. It is used after IR (Kotlin intermediate representation) is generated from the FIR (frontend intermediate representation) but before it is used to generate platform-specific files. IrGenerationExtension is used to modify the IR tree. This means that IrGenerationExtension can change absolutely anything in the generated code, but using it is hard as we can easily introduce breaking changes, so it must be used with great care. Also, IrGenerationExtension cannot influence code analysis, so it cannot impact IDE suggestions, warnings, etc.
Backend plugin extensions are used after IR (Kotlin Intermediate Representation) is generated from the FIR (frontend intermediate representation), but before it is used to generate platform-specific files.
IrGenerationExtension to add a method to a class, you won’t be able to call it directly in IntelliJ because it won’t recognize such a method, so you will only be able to call it using reflection. In contrast, a method added to a class using the frontend FirDeclarationGenerationExtension can be used directly because the IDE knows about its existence.- Kotlin Serialization - a plugin that generates serialization methods for Kotlin classes. It’s multiplatform and very efficient because it uses a compiler plugin instead of reflection.
- Jetpack Compose - a popular UI framework that uses a compiler plugin to support its view element definitions. All the composable functions are transformed into a special representation that is then used by the framework to generate the UI.
- Arrow Meta - a powerful plugin introducing support for features known from functional programming languages, like optics or refined types. It also supports Aspect Oriented Programming.
- Parcelize - a plugin that generates
Parcelableimplementations for Kotlin classes. It uses a compiler plugin to add appropriate methods to existing classes. - All-open - a plugin that makes all classes with appropriate annotations open by default. The Spring Framework uses it to make all classes with
@Componentannotation open by default (to be able to create proxies for them).
IrGenerationExtensionto generate functions and properties that are used under the hood.FirDeclarationGenerationExtensionto generate the functions required for the project to compile.FirAdditionalCheckersExtensionto show errors and warnings.
build.gradle(.kts) in the plugins section:plugins { id("kotlin-parcelize") }
kotlin-compiler-embeddable that offers us the classes we can use for defining plugins.resources/META-INF/services with the registrar's name. The name of this file should be org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar, which is the fully qualified name of the CompilerPluginRegistrar class. Inside it, you should place the fully qualified name of your registrar class. In our case, this will be com.marcinmoskala.AllOpenComponentRegistrar.// org.jetbrains.kotlin.compiler.plugin.
// CompilerPluginRegistrar
com.marcinmoskala.AllOpenComponentRegistrar
AllOpenComponentRegistrar registrar needs to register an extension registrar (we’ll call it FirAllOpenExtensionRegistrar), which registers our extension. Note that the registrar has access to the configuration so that we can pass parameters to our plugin, but we don’t need this configuration now. Our extension is just a class that extends FirStatusTransformerExtension; it has two methods: needTransformStatus and transformStatus. The former determines whether the transformation should be applied; the latter applies it. In our case, we apply our extension to all classes, and we change their status to open, regardless of what this status was before.@file:OptIn(ExperimentalCompilerApi::class) class AllOpenComponentRegistrar : CompilerPluginRegistrar() { override fun ExtensionStorage.registerExtensions( configuration: CompilerConfiguration ) { FirExtensionRegistrarAdapter .registerExtension(FirAllOpenExtensionRegistrar()) } override val supportsK2: Boolean get() = true } class FirAllOpenExtensionRegistrar : FirExtensionRegistrar(){ override fun ExtensionRegistrarContext.configurePlugin() { +::FirAllOpenStatusTransformer } } class FirAllOpenStatusTransformer( session: FirSession ) : FirStatusTransformerExtension(session) { override fun needTransformStatus( declaration: FirDeclaration ): Boolean = declaration is FirRegularClass override fun transformStatus( status: FirDeclarationStatus, declaration: FirDeclaration ): FirDeclarationStatus = status.transform(modality = Modality.OPEN) }
FirAllOpenExtensionRegistrar registers a plugin that is used by FirAllOpenStatusTransformer to determine if a specific class should be opened or not. If you are interested in the details, see the AllOpen plugin in the plugins folder in the Kotlin repository.FirSamConversionTransformerExtension, which is quite specific to this plugin because it is only called when a SAM interface is converted to a function type, and it allows the type that will be generated to be changed. This example is interesting because it adds a type that will be recognized by the IDE and can be used directly in code. The complete implementation can be found in the Kotlin repository in the plugins/sam-with-receiver folder, but here I only want to show a simplified implementation of this extension:class FirScriptSamWithReceiverConventionTransformer( session: FirSession ) : FirSamConversionTransformerExtension(session) { override fun getCustomFunctionTypeForSamConversion( function: FirSimpleFunction ): ConeLookupTagBasedType? { val containingClassSymbol = function .containingClassLookupTag() ?.toFirRegularClassSymbol(session) ?: return null return if (shouldTransform(it)) { val parameterTypes = function.valueParameters .map { it.returnTypeRef.coneType } if (parameterTypes.isEmpty()) return null createFunctionType( getFunctionType(it), parameters = parameterTypes.drop(1), receiverType = parameterTypes[0], rawReturnType = function.returnTypeRef .coneType ) } else null } // ... }
getCustomFunctionTypeForSamConversion function doesn’t return null, it overrides the type that will be generated for a SAM interface. In our case, we determine whether the function should be transformed; if so, we create a function type with a receiver by using the createFunctionType function. There are builder functions that help us to create many elements that are represented in FIR. Examples include buildSimpleFunction or buildRegularClass, and most of them offer a simple DSL. Here, the createFunctionType function creates a function type with a receiver representation of type ConeLookupTagBasedType, which will replace automatically generated types from a SAM interface. In essence, this is how this plugin works.runBlocking to wrap it in a regular function that calls the suspend function in a coroutine.suspend fun suspendFunction() = ... fun blockingFunction() = runBlocking { suspendFunction() }
IrGenerationExtension that generates an additional wrapper function in IR for the appropriate function. These wrapper functions will be present in the generated platform-specific code and are therefore available for Java, Groovy, and other languages. The problem is that these wrapper classes will not be visible in Kotlin code. This is fine if our wrapper functions are meant to be used from other languages anyway, but we need to know about this serious limitation. There is an open-source plugin called kotlin-jvm-blocking-bridge that generates blocking wrappers for suspend functions using a backend plugin; you can find its source code under the link github.com/Him188/kotlin-jvm-blocking-bridge.FirDeclarationGenerationExtension to generate wrapper functions for the appropriate suspend functions in FIR. These additional functions would then be used to generate IR and finally platform-specific code. Those functions would also be visible in IntelliJ, so we would be able to use them in both Kotlin and Java. However, such a plugin would only work with the K2 compiler, so since Kotlin 2.0. To support the previous language version, we need to define an additional extension that supports K1.plugins folder.
FirExtensionRegistrar class. To analyze how the compiler uses an extension, you can search for the usage of its open methods. To do this, hit command/Ctrl and click on a method name to jump to its usage. This should show you where the Kotlin Compiler uses this extension. Beware, though, that all the knowledge that is not documented is more likely to change in the future.