The Kotlin compiler infrastructure providing JVM, JavaScript, and metadata compilation capabilities
—
The Kotlin compiler provides a comprehensive plugin architecture enabling custom compilation phases, code generation, IDE integration, and language extensions. The modern K2-compatible plugin system uses CompilerPluginRegistrar for clean separation of concerns and improved performance.
The current plugin system designed for the K2 compiler frontend with improved performance and maintainability.
/**
* Modern plugin registration interface for K2 compiler
* Provides clean separation between compiler phases and IDE integration
*/
interface CompilerPluginRegistrar {
/**
* Register compiler plugin components
* Called during compiler initialization to set up plugin infrastructure
*
* @param project MockProject instance for component registration
* @param configuration Compiler configuration with plugin settings
*/
fun registerProjectComponents(
project: MockProject,
configuration: CompilerConfiguration
)
companion object {
/**
* Extension point for plugin registrars
* Used by the compiler to discover and load plugins
*/
val EXTENSION_POINT_NAME: ExtensionPointName<CompilerPluginRegistrar>
}
}Plugin command-line option processing for configuration and parameter passing.
/**
* Processes command-line options for compiler plugins
* Handles option parsing and configuration setup
*/
interface CommandLineProcessor {
/** Unique identifier for the plugin */
val pluginId: String
/** Available plugin options */
val pluginOptions: Collection<PluginOption>
/**
* Process a single command-line option
*
* @param option The CLI option being processed
* @param value The option value from command line
* @param configuration Compiler configuration to update
* @return true if option was handled, false otherwise
*/
fun processOption(
option: AbstractCliOption,
value: String,
configuration: CompilerConfiguration
): Boolean
}
/**
* Definition of a plugin command-line option
*/
class PluginOption(
/** Option name (without -- prefix) */
val optionName: String,
/** Value description for help text */
val valueDescription: String,
/** Help description for the option */
val description: String,
/** Whether option requires a value */
val required: Boolean = true,
/** Allow multiple values for this option */
val allowMultipleOccurrences: Boolean = false
)Legacy plugin system maintained for backward compatibility.
/**
* Legacy plugin registration interface (deprecated)
* Maintained for K1 compiler compatibility
*/
@Deprecated("Use CompilerPluginRegistrar for K2 compatibility")
interface ComponentRegistrar {
/**
* Register plugin components with the compiler
*
* @param project Project instance
* @param configuration Compiler configuration
*/
fun registerProjectComponents(
project: MockProject,
configuration: CompilerConfiguration
)
companion object {
val EXTENSION_POINT_NAME: ExtensionPointName<ComponentRegistrar>
}
}Core extension points for implementing plugin functionality.
/**
* Extension for IR generation and transformation
* Allows plugins to generate and modify intermediate representation
*/
interface IrGenerationExtension {
/**
* Generate additional IR declarations
* Called during IR generation phase
*/
fun generate(
moduleFragment: IrModuleFragment,
pluginContext: IrPluginContext
)
}
/**
* Extension for frontend analysis and resolution
* Enables custom declarations and synthetic elements
*/
interface AnalysisExtension {
/**
* Perform additional analysis
* Called during frontend analysis phase
*/
fun doAnalysis(
project: Project,
module: ModuleDescriptor,
bindingTrace: BindingTrace,
files: Collection<KtFile>
): AnalysisResult?
}
/**
* Extension for code generation
* Allows custom bytecode generation and optimization
*/
interface CodegenExtension {
/**
* Generate additional code
* Called during code generation phase
*/
fun generateCode(
codegen: ExpressionCodegen,
expression: KtExpression,
type: Type
): StackValue?
}Plugin support for synthetic declarations and members.
/**
* Provides synthetic declarations not present in source code
* Useful for frameworks that generate members at compile time
*/
interface SyntheticResolveExtension {
/**
* Generate synthetic companion object
*/
fun generateSyntheticClasses(
moduleDescriptor: ModuleDescriptor,
name: Name,
ctx: LazyClassContext,
declarationProvider: PackageMemberDeclarationProvider,
result: MutableSet<ClassDescriptor>
)
/**
* Generate synthetic members for existing classes
*/
fun generateSyntheticMethods(
thisDescriptor: ClassDescriptor,
name: Name,
bindingContext: BindingContext,
fromSupertypes: List<SimpleFunctionDescriptor>,
result: MutableCollection<SimpleFunctionDescriptor>
)
/**
* Generate synthetic properties
*/
fun generateSyntheticProperties(
thisDescriptor: ClassDescriptor,
name: Name,
bindingContext: BindingContext,
fromSupertypes: ArrayList<PropertyDescriptor>,
result: MutableSet<PropertyDescriptor>
)
}Context and service access for plugin implementations.
/**
* Plugin context providing access to compiler services
* Available during plugin execution phases
*/
interface IrPluginContext {
/** Module descriptor being compiled */
val moduleDescriptor: ModuleDescriptor
/** Symbol table for IR operations */
val symbolTable: SymbolTable
/** Type translator for IR types */
val typeTranslator: TypeTranslator
/** IR factory for creating IR elements */
val irFactory: IrFactory
/** Access to built-in types and symbols */
val irBuiltIns: IrBuiltIns
}
/**
* Configuration keys specific to plugins
*/
object PluginConfigurationKeys {
/** Plugin options from command line */
val PLUGIN_OPTIONS: CompilerConfigurationKey<List<PluginOption>>
/** Plugin classpath */
val PLUGIN_CLASSPATH: CompilerConfigurationKey<List<String>>
}import org.jetbrains.kotlin.compiler.plugin.*
import org.jetbrains.kotlin.config.CompilerConfiguration
import com.intellij.mock.MockProject
// 1. Define plugin options
class MyPluginCommandLineProcessor : CommandLineProcessor {
override val pluginId = "my-plugin"
override val pluginOptions = listOf(
PluginOption(
optionName = "enabled",
valueDescription = "true|false",
description = "Enable my plugin functionality"
),
PluginOption(
optionName = "output-dir",
valueDescription = "path",
description = "Output directory for generated files"
)
)
override fun processOption(
option: AbstractCliOption,
value: String,
configuration: CompilerConfiguration
): Boolean {
return when (option.optionName) {
"enabled" -> {
configuration.put(MyPluginConfigurationKeys.ENABLED, value.toBoolean())
true
}
"output-dir" -> {
configuration.put(MyPluginConfigurationKeys.OUTPUT_DIR, value)
true
}
else -> false
}
}
}
// 2. Define configuration keys
object MyPluginConfigurationKeys {
val ENABLED = CompilerConfigurationKey<Boolean>("my.plugin.enabled")
val OUTPUT_DIR = CompilerConfigurationKey<String>("my.plugin.output.dir")
}
// 3. Implement plugin registrar
class MyCompilerPluginRegistrar : CompilerPluginRegistrar {
override fun registerProjectComponents(
project: MockProject,
configuration: CompilerConfiguration
) {
val enabled = configuration.get(MyPluginConfigurationKeys.ENABLED) ?: false
if (!enabled) return
// Register IR generation extension
IrGenerationExtension.registerExtension(
project,
MyIrGenerationExtension(configuration)
)
// Register synthetic resolve extension
SyntheticResolveExtension.registerExtension(
project,
MySyntheticResolveExtension()
)
}
}import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.util.*
class MyIrGenerationExtension(
private val configuration: CompilerConfiguration
) : IrGenerationExtension {
override fun generate(
moduleFragment: IrModuleFragment,
pluginContext: IrPluginContext
) {
// Generate additional IR declarations
moduleFragment.files.forEach { file ->
file.declarations.filterIsInstance<IrClass>().forEach { irClass ->
if (shouldProcessClass(irClass)) {
generateHelperMethod(irClass, pluginContext)
}
}
}
}
private fun shouldProcessClass(irClass: IrClass): Boolean {
// Check if class has specific annotation
return irClass.hasAnnotation(FqName("com.example.MyAnnotation"))
}
private fun generateHelperMethod(
irClass: IrClass,
pluginContext: IrPluginContext
) {
val function = pluginContext.irFactory.buildFun {
name = Name.identifier("generatedHelper")
returnType = pluginContext.irBuiltIns.unitType
visibility = Visibilities.PUBLIC
}
// Build function body
function.body = DeclarationIrBuilder(pluginContext, function.symbol).irBlockBody {
// Generate IR statements
+irReturn(irUnit())
}
irClass.declarations.add(function)
}
}import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension
class MySyntheticResolveExtension : SyntheticResolveExtension {
override fun generateSyntheticMethods(
thisDescriptor: ClassDescriptor,
name: Name,
bindingContext: BindingContext,
fromSupertypes: List<SimpleFunctionDescriptor>,
result: MutableCollection<SimpleFunctionDescriptor>
) {
// Generate synthetic getter/setter methods
if (name.asString().startsWith("get") || name.asString().startsWith("set")) {
val propertyName = name.asString().removePrefix("get").removePrefix("set")
if (hasPropertyAnnotation(thisDescriptor, propertyName)) {
val syntheticFunction = createSyntheticFunction(
thisDescriptor,
name,
propertyName
)
result.add(syntheticFunction)
}
}
}
override fun generateSyntheticProperties(
thisDescriptor: ClassDescriptor,
name: Name,
bindingContext: BindingContext,
fromSupertypes: ArrayList<PropertyDescriptor>,
result: MutableSet<PropertyDescriptor>
) {
// Generate synthetic properties based on annotations
if (hasGeneratePropertyAnnotation(thisDescriptor, name)) {
val syntheticProperty = createSyntheticProperty(thisDescriptor, name)
result.add(syntheticProperty)
}
}
private fun hasPropertyAnnotation(
classDescriptor: ClassDescriptor,
propertyName: String
): Boolean {
return classDescriptor.annotations.hasAnnotation(
FqName("com.example.GenerateProperty")
)
}
private fun createSyntheticFunction(
owner: ClassDescriptor,
name: Name,
propertyName: String
): SimpleFunctionDescriptor {
return SimpleFunctionDescriptorImpl.create(
owner,
Annotations.EMPTY,
name,
CallableMemberDescriptor.Kind.SYNTHESIZED,
owner.source
).apply {
// Configure function parameters and return type
initialize(
null, // extension receiver
owner.thisAsReceiverParameter,
emptyList(), // type parameters
emptyList(), // value parameters
owner.builtIns.stringType, // return type
Modality.FINAL,
Visibilities.PUBLIC
)
}
}
}// META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
com.example.MyCompilerPluginRegistrar
// META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor
com.example.MyPluginCommandLineProcessorGradle (build.gradle.kts):
plugins {
kotlin("jvm")
id("my-kotlin-plugin") version "1.0.0"
}
kotlinCompilerPluginOptions {
option("my-plugin", "enabled", "true")
option("my-plugin", "output-dir", "build/generated")
}Command Line:
kotlinc \
-Xplugin=my-plugin.jar \
-P plugin:my-plugin:enabled=true \
-P plugin:my-plugin:output-dir=build/generated \
src/main/kotlin/Main.kt// Multi-phase plugin with dependency ordering
class AdvancedPluginRegistrar : CompilerPluginRegistrar {
override fun registerProjectComponents(
project: MockProject,
configuration: CompilerConfiguration
) {
// Register multiple extensions with specific ordering
val analysisExtension = MyAnalysisExtension(configuration)
val irExtension = MyIrGenerationExtension(configuration)
val codegenExtension = MyCodegenExtension(configuration)
// Analysis phase
AnalysisExtension.registerExtension(project, analysisExtension)
// IR generation phase
IrGenerationExtension.registerExtension(project, irExtension)
// Code generation phase
CodegenExtension.registerExtension(project, codegenExtension)
// Cross-phase communication via configuration
configuration.put(ANALYSIS_RESULTS, analysisExtension.results)
}
}
// Plugin with custom diagnostic reporting
class DiagnosticPlugin : IrGenerationExtension {
override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
moduleFragment.files.forEach { file ->
validateFile(file, pluginContext)
}
}
private fun validateFile(file: IrFile, context: IrPluginContext) {
file.declarations.forEach { declaration ->
if (hasIssue(declaration)) {
// Report custom diagnostic
context.messageCollector.report(
CompilerMessageSeverity.WARNING,
"Custom plugin warning: ${declaration.name}",
CompilerMessageSourceLocation.create(
file.path,
declaration.startOffset,
declaration.endOffset,
null
)
)
}
}
}
}Package plugin with service registration files:
my-plugin.jar
├── com/example/MyPluginRegistrar.class
├── com/example/MyCommandLineProcessor.class
├── META-INF/
│ └── services/
│ ├── org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar
│ └── org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor
└── plugin.xml (optional, for IDE integration)class PluginException(message: String, cause: Throwable? = null) : Exception(message, cause)
class InvalidPluginConfigurationException(message: String) : PluginException(message)Common plugin error scenarios:
Install with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-kotlin--kotlin-compiler