From 70315afafb2e9f645b748b8aab35a111a9ccdf4f Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 7 Sep 2020 15:47:47 +0100 Subject: [PATCH 1/2] Filtering classes by annotation --- .../dev/nhachicha/AccessorModifierPlugin.kt | 102 ++++++++++++------ 1 file changed, 70 insertions(+), 32 deletions(-) diff --git a/compiler-plugin/src/main/kotlin/dev/nhachicha/AccessorModifierPlugin.kt b/compiler-plugin/src/main/kotlin/dev/nhachicha/AccessorModifierPlugin.kt index 8882935..8066021 100644 --- a/compiler-plugin/src/main/kotlin/dev/nhachicha/AccessorModifierPlugin.kt +++ b/compiler-plugin/src/main/kotlin/dev/nhachicha/AccessorModifierPlugin.kt @@ -17,35 +17,46 @@ 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.IrAnnotationContainer +import org.jetbrains.kotlin.ir.declarations.IrClass 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.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.isGetter -import org.jetbrains.kotlin.ir.visitors.* +import org.jetbrains.kotlin.ir.util.dump +import org.jetbrains.kotlin.ir.util.parentAsClass +import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid +import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid +import org.jetbrains.kotlin.ir.visitors.acceptVoid +import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid +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,29 +65,30 @@ 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() { + companion object { + fun generate( + irClass: IrClass, + context: IrPluginContext + ) { + if (irClass.isRealmObject) { + irClass.transformChildrenVoid(object : IrElementTransformerVoidWithContext() { 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 ")) @@ -85,24 +97,50 @@ class AccessorModifierIrGenerationExtension : IrGenerationExtension { } } }) - 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) + } +} From 204e3f3f9dc63347e8570854969cd67e68f78fe8 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 7 Sep 2020 17:26:26 +0100 Subject: [PATCH 2/2] Added missing type filter --- .../dev/nhachicha/AccessorModifierPlugin.kt | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/compiler-plugin/src/main/kotlin/dev/nhachicha/AccessorModifierPlugin.kt b/compiler-plugin/src/main/kotlin/dev/nhachicha/AccessorModifierPlugin.kt index 8066021..9b4d60d 100644 --- a/compiler-plugin/src/main/kotlin/dev/nhachicha/AccessorModifierPlugin.kt +++ b/compiler-plugin/src/main/kotlin/dev/nhachicha/AccessorModifierPlugin.kt @@ -29,20 +29,18 @@ 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.IrAnnotationContainer -import org.jetbrains.kotlin.ir.declarations.IrClass -import org.jetbrains.kotlin.ir.declarations.IrFile -import org.jetbrains.kotlin.ir.declarations.IrModuleFragment +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.IrElementVisitorVoid -import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid -import org.jetbrains.kotlin.ir.visitors.acceptVoid -import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid +import org.jetbrains.kotlin.ir.visitors.* import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import java.io.FileWriter @@ -87,13 +85,25 @@ private class RealmObjectClassLowering(val context: IrPluginContext) : ) { if (irClass.isRealmObject) { irClass.transformChildrenVoid(object : IrElementTransformerVoidWithContext() { - 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) + 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) } } }) @@ -115,7 +125,6 @@ fun ClassLoweringPass.runOnFileInOrder(irFile: IrFile) { }) } - // Annotation private val IrClass.isRealmObject: Boolean get() = kind == ClassKind.CLASS && hasRealmObjectAnnotationWithoutArgs() @@ -138,7 +147,7 @@ private fun IrAnnotationContainer.getAnnotation(name: FqName): IrConstructorCall // 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" + val formattedMessage = "[Kotlin Compiler] ${Instant.now()} $message\n" messageCollector.report(severity, formattedMessage) FileWriter("/tmp/kmp.log").use { it.append(formattedMessage)