Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we should expose the annotation as part of the plugin.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you would inject these kind of dependencies by adding them to the project by overriding the apply(target: Project) in the gradle plugins implementation of KotlinCompilerPluginSupportPlugin.


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)
}
}