diff --git a/compiler-plugin/src/main/kotlin/dev/nhachicha/AccessorModifierPlugin.kt b/compiler-plugin/src/main/kotlin/dev/nhachicha/AccessorModifierPlugin.kt index 8882935..9b4d60d 100644 --- a/compiler-plugin/src/main/kotlin/dev/nhachicha/AccessorModifierPlugin.kt +++ b/compiler-plugin/src/main/kotlin/dev/nhachicha/AccessorModifierPlugin.kt @@ -17,35 +17,44 @@ package dev.nhachicha import com.google.auto.service.AutoService -import org.jetbrains.kotlin.backend.common.FileLoweringPass +import org.jetbrains.kotlin.backend.common.ClassLoweringPass import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext +import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity +import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.com.intellij.mock.MockProject import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.builders.* -import org.jetbrains.kotlin.ir.declarations.IrFile -import org.jetbrains.kotlin.ir.declarations.IrFunction -import org.jetbrains.kotlin.ir.declarations.IrModuleFragment -import org.jetbrains.kotlin.ir.declarations.isPropertyAccessor +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.IrConstructorCall import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.IrReturn import org.jetbrains.kotlin.ir.types.isNullableString import org.jetbrains.kotlin.ir.types.isString +import org.jetbrains.kotlin.ir.util.dump import org.jetbrains.kotlin.ir.util.isGetter +import org.jetbrains.kotlin.ir.util.parentAsClass import org.jetbrains.kotlin.ir.visitors.* +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import java.io.FileWriter +import java.time.Instant @AutoService(ComponentRegistrar::class) class AccessorModifierComponentRegistrar : ComponentRegistrar { override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) { - registerExtensions(project, configuration) + messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE) + registerExtensions(project) } companion object { - fun registerExtensions(project: MockProject, configuration: CompilerConfiguration) { + fun registerExtensions(project: MockProject) { IrGenerationExtension.registerExtension( project, AccessorModifierIrGenerationExtension() @@ -54,55 +63,93 @@ class AccessorModifierComponentRegistrar : ComponentRegistrar { } } -class AccessorModifierIrGenerationExtension : IrGenerationExtension { - +private class AccessorModifierIrGenerationExtension : IrGenerationExtension { override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { + val realmObjectClassLowering = RealmObjectClassLowering(pluginContext) for (file in moduleFragment.files) { - AccessorCallTransformer(pluginContext).runOnFileInOrder(file) + realmObjectClassLowering.runOnFileInOrder(file) } } +} - class AccessorCallTransformer( - val context: IrPluginContext - ) : IrElementTransformerVoidWithContext(), FileLoweringPass { - - override fun lower(irFile: IrFile) { - irFile.transformChildrenVoid() - } +private class RealmObjectClassLowering(val context: IrPluginContext) : + ClassLoweringPass { + override fun lower(irClass: IrClass) { + generate(irClass, context) + } - override fun visitFunctionNew(declaration: IrFunction): IrStatement { - return if (declaration.isPropertyAccessor - && declaration.isGetter - && (declaration.returnType.isString() || declaration.returnType.isNullableString()) - ) { - declaration.body?.transformChildrenVoid(object : IrElementTransformerVoid() { - override fun visitReturn(expression: IrReturn): IrExpression { - return IrBlockBuilder(context, currentScope?.scope!!, expression.startOffset, expression.endOffset).irBlock { - val irConcat = irConcat() - irConcat.addArgument(irString("Hello ")) - irConcat.addArgument(expression.value) - +irReturn(irConcat) + companion object { + fun generate( + irClass: IrClass, + context: IrPluginContext + ) { + if (irClass.isRealmObject) { + irClass.transformChildrenVoid(object : IrElementTransformerVoidWithContext() { + override fun visitFunctionNew(declaration: IrFunction): IrStatement { + return if (declaration.isPropertyAccessor + && declaration.isGetter + && (declaration.returnType.isString() || declaration.returnType.isNullableString()) + ) { + declaration.body?.transformChildrenVoid(object : IrElementTransformerVoid() { + override fun visitReturn(expression: IrReturn): IrExpression { + logger("modifying expression: ${expression.dump()}") + return IrBlockBuilder(context, currentScope?.scope!!, expression.startOffset, expression.endOffset).irBlock { + val irConcat = irConcat() + irConcat.addArgument(irString("Hello ")) + irConcat.addArgument(expression.value) + +irReturn(irConcat) + } + } + }) + super.visitFunctionNew(declaration) + } else { + super.visitFunctionNew(declaration) } } }) - super.visitFunctionNew(declaration) - } else { - super.visitFunctionNew(declaration) } } - } } -fun FileLoweringPass.runOnFileInOrder(irFile: IrFile) { +fun ClassLoweringPass.runOnFileInOrder(irFile: IrFile) { irFile.acceptVoid(object : IrElementVisitorVoid { override fun visitElement(element: IrElement) { element.acceptChildrenVoid(this) } - override fun visitFile(declaration: IrFile) { + override fun visitClass(declaration: IrClass) { lower(declaration) declaration.acceptChildrenVoid(this) } }) } + +// Annotation +private val IrClass.isRealmObject: Boolean get() = kind == ClassKind.CLASS && hasRealmObjectAnnotationWithoutArgs() + +private val realmObjectAnnotationFqName = FqName("io.realm.kmmapplication.shared.RealmObject") + +private fun IrClass.hasRealmObjectAnnotationWithoutArgs(): Boolean { + val annotation = getAnnotation(realmObjectAnnotationFqName) ?: return false + for (i in 0 until annotation.valueArgumentsCount) { + if (annotation.getValueArgument(i) != null) return false // TODO consider raising a compiler error with proper line number and message + } + return true +} + +private fun IrAnnotationContainer.getAnnotation(name: FqName): IrConstructorCall? = + annotations.find { + it.symbol.owner.parentAsClass.descriptor.fqNameSafe == name + } + + +// Logging to a temp file and to console/IDE (Build Output) +lateinit var messageCollector: MessageCollector +fun logger(message: String, severity: CompilerMessageSeverity = CompilerMessageSeverity.WARNING) { + val formattedMessage = "[Kotlin Compiler] ${Instant.now()} $message\n" + messageCollector.report(severity, formattedMessage) + FileWriter("/tmp/kmp.log").use { + it.append(formattedMessage) + } +}