Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Writing Kotlin Compiler Plugins with Arrow Meta

Mohit S
October 08, 2020

Writing Kotlin Compiler Plugins with Arrow Meta

We’ll learn about how to write and test compiler plugins with Arrow Meta. This library provides an API for source transformations, automatic code refactoring, and much more. We’ll look at main use cases from type classes, comprehensions, and lenses that are made possible with Arrow Meta. We’ll also look at how to test each use case.

Mohit S

October 08, 2020
Tweet

More Decks by Mohit S

Other Decks in Programming

Transcript

  1. Writing Compiler Plugins with Arrow Meta • Kotlin Compiler •

    Compiler Plugin with Meta • Testing Compiler Plugins • IDE Plugins
  2. Motivations • How do we build tools with Kotlin? •

    How do we introduce Functional Programming?
  3. Code Parsing Analysis Resolve Code-gen Done class UserRepo { fun

    getUsers(): List<User> fun addUser() fun deleteUser(id: Int) }
  4. Code Parsing Analysis Resolve Code-gen Done class UserRepo { fun

    getUsers(): List<User> fun addUser() fun deleteUser(id: Int) } Type Checking
  5. Code Parsing Analysis Resolve Code-gen Done if ("hello" "== 1)

    { } Operator ‘"==’cannot be applied to ‘String’ and ‘Int’ Type Checking
  6. arrow-kt/arrow-meta Code ! Issues Pull Requests rrow Meta Writing compiler

    plugins, source transformations, IDEA plugins, linters, type search engines, automatic code refactoring,""... Gradle Build passing latest snapshot v1.3.61
  7. Code Parsing Analysis Resolve Code-gen Done class UserRepo { fun

    getUsers(): List<User> fun addUser() fun deleteUser(id: Int) }
  8. Writing Complier Plugins with Arrow Meta - Simple Hello World

    Plugin - Write & Test Plugin for logging - Combine Transformations - Custom Type Checker
  9. fun main() { helloWorld() } fun helloWorld() { } HelloWorldKt

    src main kotlin com learn arrow-meta HelloWorldKt
  10. fun main() { helloWorld() } fun helloWorld() { println("Hello DroidCon

    EMEA!”) } Generate HelloWorldKt src main kotlin com learn arrow-meta HelloWorldKt
  11. val Meta.helloWorld: CliPlugin data class Plugin<A>( val name: String, val

    meta: A.() "-> List<ExtensionPhase> ) Name of compiler plugin
  12. val Meta.helloWorld: CliPlugin data class Plugin<A>( val name: String, val

    meta: A.() "-> List<ExtensionPhase> ) Phases of compilation
  13. val Meta.helloWorld: CliPlugin get() = "Hello World" { } operator

    fun String.invoke( phases: CompilerContext.() "-> List<ExtensionPhase> ): CliPlugin = Plugin(this, phases)
  14. val Meta.helloWorld: CliPlugin get() = "Hello World" { meta( )

    } fun meta( vararg phases: ExtensionPhase ): List<ExtensionPhase> Define list of phases
  15. val Meta.helloWorld: CliPlugin get() = "Hello World" { meta( namedFunction(this,

    { name "== "helloWorld" }) { c "-> } ) } Finds a function named “helloWorld"
  16. val Meta.helloWorld: CliPlugin get() = "Hello World" { meta( namedFunction(this,

    { name "== "helloWorld" }) { c "-> } ) } Quote Template DSL fun nodeName { } Function, Expression
  17. val Meta.helloWorld: CliPlugin get() = "Hello World" { meta( namedFunction(this,

    { name "== "helloWorld" }) { c "-> } ) } Quote Template DSL fun nodeName { } Specify Transformation
  18. val Meta.helloWorld: CliPlugin get() = “Hello World" { meta( namedFunction(this,

    { name "== "helloWorld" }) { c "-> Transform.replace( ) } ) } Replace Transformation
  19. val Meta.helloWorld: CliPlugin get() = “Hello World" { meta( namedFunction(this,

    { name "== "helloWorld" }) { c "-> Transform.replace( replacing = c newDeclaration = """|fun helloWorld(): Unit = | println("Hello DroidCon EMEA!") |""".function.syntheticScope ) } ) } Function to replace
  20. val Meta.helloWorld: CliPlugin get() = “Hello World" { meta( namedFunction(this,

    { name "== "helloWorld" }) { c "-> Transform.replace( replacing = c, newDeclaration = """|fun helloWorld(): Unit = | println("Hello DroidCon EMEA!") |""".function.syntheticScope ) } ) } Replacement
  21. val Meta.helloWorld: CliPlugin get() = “Hello World" { meta( namedFunction(this,

    { name "== "helloWorld" }) { c "-> Transform.replace( replacing = c, newDeclaration = """|fun helloWorld(): Unit = | println(“Hello DroidCon EMEA!") |""".function.syntheticScope ) } ) }
  22. class MetaPlugin : Meta { override fun intercept(ctx: CompilerContext): List<CliPlugin>

    = listOf( helloWorld ) } Register All Plugins with Kotlin Compiler
  23. fun main() { helloWorld() } fun helloWorld() { } HelloWorldKt

    src main kotlin com learn arrow-meta HelloWorldKt
  24. fun main() { helloWorld() } fun helloWorld() { println("Hello DroidCon

    EMEA!”) } HelloWorld.kt Hello DroidCon EMEA! HelloWorldKt src main kotlin com learn arrow-meta HelloWorldKt
  25. fun main() { helloWorld() } fun helloWorld() { } HelloWorldKt

    src main kotlin com learn arrow-meta HelloWorldKt
  26. fun main() { helloWorld() } fun helloWorld() { } HelloWorldKt

    src main kotlin com learn arrow-meta HelloWorldKt
  27. Writing Complier Plugins with Arrow Meta - Simple Hello World

    Plugin - Write & Test Plugin for logging - Combine Transformations - Custom Type Checker
  28. DebugLogPlugin src main kotlin com learn arrow-meta DebugLogPlugin @DebugLog fun

    calculate(n: Int): Long = listOf(1L,2L,3L).take(n).last() Annotation to log execution time
  29. DebugLogPlugin src main kotlin com learn arrow-meta DebugLogPlugin @DebugLog fun

    calculate(n: Int): Long = listOf(1L,2L,3L).take(n).last()
  30. DebugLogPlugin src main kotlin com learn arrow-meta DebugLogPlugin @DebugLog fun

    calculate(n: Int): Long { println(""-> calculate(n=${'$'}n)") val startTime = System.currentTimeMillis() val result = listOf(1L,2L,3L).take(n).last() val timeToRun = System.currentTimeMillis() - startTime println(""<- calculate[ran in ${'$'}timeToRun ms]") return result }
  31. DebugLogPlugin src main kotlin com learn arrow-meta DebugLogPlugin @DebugLog fun

    calculate(n: Int): Long { println(""-> calculate(n=${'$'}n)") val startTime = System.currentTimeMillis() val result = listOf(1L,2L,3L).take(n).last() val timeToRun = System.currentTimeMillis() - startTime println(""<- calculate[ran in ${'$'}timeToRun ms]") return result } Original Operation
  32. DebugLogPlugin src main kotlin com learn arrow-meta DebugLogPlugin @DebugLog fun

    calculate(n: Int): Long { println(""-> calculate(n=${'$'}n)") val startTime = System.currentTimeMillis() val result = listOf(1L,2L,3L).take(n).last() val timeToRun = System.currentTimeMillis() - startTime println(""<- calculate[ran in ${'$'}timeToRun ms]") return result } Log method name & argument
  33. DebugLogPlugin src main kotlin com learn arrow-meta DebugLogPlugin @DebugLog fun

    calculate(n: Int): Long { println(""-> calculate(n=${'$'}n)") val startTime = System.currentTimeMillis() val result = listOf(1L,2L,3L).take(n).last() val timeToRun = System.currentTimeMillis() - startTime println(""<- calculate[ran in ${'$'}timeToRun ms]") return result } Get current time
  34. DebugLogPlugin src main kotlin com learn arrow-meta DebugLogPlugin @DebugLog fun

    calculate(n: Int): Long { println(""-> calculate(n=${'$'}n)") val startTime = System.currentTimeMillis() val result = listOf(1L,2L,3L).take(n).last() val timeToRun = System.currentTimeMillis() - startTime println(""<- calculate[ran in ${'$'}timeToRun ms]") return result } Original Operation
  35. DebugLogPlugin src main kotlin com learn arrow-meta DebugLogPlugin @DebugLog fun

    calculate(n: Int): Long { println(""-> calculate(n=${'$'}n)") val startTime = System.currentTimeMillis() val result = listOf(1L,2L,3L).take(n).last() val timeToRun = System.currentTimeMillis() - startTime println(""<- calculate[ran in ${'$'}timeToRun ms]") return result } Calculate execution time
  36. DebugLogPlugin src main kotlin com learn arrow-meta DebugLogPlugin @DebugLog fun

    calculate(n: Int): Long { println(""-> calculate(n=${'$'}n)") val startTime = System.currentTimeMillis() val result = listOf(1L,2L,3L).take(n).last() val timeToRun = System.currentTimeMillis() - startTime println(""<- calculate[ran in ${'$'}timeToRun ms]") return result } Log execution time
  37. DebugLogPlugin src main kotlin com learn arrow-meta DebugLogPlugin @DebugLog fun

    calculate(n: Int): Long { println(""-> calculate(n=${'$'}n)") val startTime = System.currentTimeMillis() val result = listOf(1L,2L,3L).take(n).last() val timeToRun = System.currentTimeMillis() - startTime println(""<- calculate[ran in ${'$'}timeToRun ms]") return result } How do we generate this code?
  38. DebugLogPlugin src main kotlin com learn arrow-meta DebugLogPlugin @DebugLog fun

    calculate(n: Int): Long = listOf(1L,2L,3L).take(n).last()
  39. DebugLogPlugin src main kotlin com learn arrow-meta DebugLogPlugin @DebugLog fun

    calculate(n: Int): Long = listOf(1L,2L,3L).take(n).last() Filter
  40. DebugLogPlugin src main kotlin com learn arrow-meta DebugLogPlugin @DebugLog fun

    calculate(n: Int): Long = listOf(1L,2L,3L).take(n).last() Modify
  41. val Meta.debugLog: CliPlugin get() = "DebugLog" { meta( namedFunction(this, {

    validateFunction() }) { c "-> Transform.replace( replacing = c, newDeclaration = replace(c).function ) } ) } Specify plugin name
  42. val Meta.debugLog: CliPlugin get() = "DebugLog" { meta( namedFunction(this, {

    validateFunction() }) { c "-> Transform.replace( replacing = c, newDeclaration = replace(c).function ) } ) } Specify Extension Phases
  43. val Meta.debugLog: CliPlugin get() = "DebugLog" { meta( namedFunction(this, {

    validateFunction() }) { c "-> Transform.replace( replacing = c, newDeclaration = replace(c).function ) } ) } Find Function
  44. val Meta.debugLog: CliPlugin get() = "DebugLog" { meta( namedFunction(this, {

    validateFunction() }) { c "-> Transform.replace( replacing = c, newDeclaration = replace(c).function ) } ) } Function
  45. val Meta.debugLog: CliPlugin get() = "DebugLog" { meta( namedFunction(this, {

    validateFunction() }) { c "-> Transform.replace( replacing = c, newDeclaration = replace(c).function ) } ) } Filter Function with DebugLog Annotation
  46. val Meta.debugLog: CliPlugin get() = "DebugLog" { meta( namedFunction(this, {

    validateFunction() }) { c "-> Transform.replace( replacing = c, newDeclaration = replace(c).function ) } ) } Create extension to find method
  47. fun KtAnnotated.hasAnnotation( vararg annotationNames: String ): Boolean { val names

    = annotationNames.toHashSet() val predicate: (KtAnnotationEntry) "-> Boolean = { it.typeReference "?.typeElement "?.safeAs<KtUserType>() "?.referencedName in names } return annotationEntries.any(predicate) } Annotations to match
  48. fun KtAnnotated.hasAnnotation( vararg annotationNames: String ): Boolean { val names

    = annotationNames.toHashSet() val predicate: (KtAnnotationEntry) "-> Boolean = { it.typeReference "?.typeElement "?.safeAs<KtUserType>() "?.referencedName in names } return annotationEntries.any(predicate) } Convert to set
  49. fun KtAnnotated.hasAnnotation( vararg annotationNames: String ): Boolean { val names

    = annotationNames.toHashSet() val predicate: (KtAnnotationEntry) "-> Boolean = { it.typeReference "?.typeElement "?.safeAs<KtUserType>() "?.referencedName in names } return annotationEntries.any(predicate) } Match annotation’s name
  50. fun KtAnnotated.hasAnnotation( vararg annotationNames: String ): Boolean { val names

    = annotationNames.toHashSet() val predicate: (KtAnnotationEntry) "-> Boolean = { it.typeReference "?.typeElement "?.safeAs<KtUserType>() "?.referencedName in names } return annotationEntries.any(predicate) } Check function’s annotations
  51. fun KtAnnotated.hasAnnotation( vararg annotationNames: String ): Boolean { val names

    = annotationNames.toHashSet() val predicate: (KtAnnotationEntry) "-> Boolean = { it.typeReference "?.typeElement "?.safeAs<KtUserType>() "?.referencedName in names } return annotationEntries.any(predicate) }
  52. val Meta.debugLog: CliPlugin get() = "DebugLog" { meta( namedFunction(this, {

    validateFunction() }) { c "-> Transform.replace( replacing = c, newDeclaration = replace(c).function ) } ) } Filter function
  53. val Meta.debugLog: CliPlugin get() = "DebugLog" { meta( namedFunction(this, {

    validateFunction() }) { c "-> Transform.replace( replacing = c, newDeclaration = replace(c).function ) } ) } Transform function
  54. val Meta.debugLog: CliPlugin get() = "DebugLog" { meta( namedFunction(this, {

    validateFunction() }) { c "-> Transform.replace( replacing = c, newDeclaration = replace(c).function ) } ) } Add debug log to function
  55. val Meta.debugLog: CliPlugin get() = "DebugLog" { meta( namedFunction(this, {

    validateFunction() }) { c "-> Transform.replace( replacing = c, newDeclaration = replace(c).function ) } ) } Modify Function
  56. fun replace(function: KtNamedFunction): String { val functionName = function.name val

    paramName = function.valueParameters.first().name val functionBody = function.body()"?.bodySourceAsExpression() return """ |fun ${functionName}(${paramName}: Int): Long { | println(""-> $functionName(${paramName}=$${paramName})") | val startTime = System.currentTimeMillis() | val result = $functionBody | val timeToRun = System.currentTimeMillis() - startTime | println(""<- ${functionName}[ran in ${'$'}timeToRun ms]") | return result | }""" }
  57. fun replace(function: KtNamedFunction): String { val functionName = function.name val

    paramName = function.valueParameters.first().name val functionBody = function.body()"?.bodySourceAsExpression() return """ |fun ${functionName}(${paramName}: Int): Long { | println(""-> $functionName(${paramName}=$${paramName})") | val startTime = System.currentTimeMillis() | val result = $functionBody | val timeToRun = System.currentTimeMillis() - startTime | println(""<- ${functionName}[ran in ${'$'}timeToRun ms]") | return result | }""" } Get info on function
  58. fun replace(function: KtNamedFunction): String { val functionName = function.name val

    paramName = function.valueParameters.first().name val functionBody = function.body()"?.bodySourceAsExpression() return """ |fun ${functionName}(${paramName}: Int): Long { | println(""-> $functionName(${paramName}=$${paramName})") | val startTime = System.currentTimeMillis() | val result = $functionBody | val timeToRun = System.currentTimeMillis() - startTime | println(""<- ${functionName}[ran in ${'$'}timeToRun ms]") | return result | }""" } Create new function
  59. fun replace(function: KtNamedFunction): String { val functionName = function.name val

    paramName = function.valueParameters.first().name val functionBody = function.body()"?.bodySourceAsExpression() return """ |fun ${functionName}(${paramName}: Int): Long { | println(""-> $functionName(${paramName}=$${paramName})") | val startTime = System.currentTimeMillis() | val result = $functionBody | val timeToRun = System.currentTimeMillis() - startTime | println(""<- ${functionName}[ran in ${'$'}timeToRun ms]") | return result | }""" }
  60. fun replace(function: KtNamedFunction): String { val functionName = function.name val

    paramName = function.valueParameters.first().name val functionBody = function.body()"?.bodySourceAsExpression() return """ |fun ${functionName}(${paramName}: Int): Long { | println(""-> $functionName(${paramName}=$${paramName})") | val startTime = System.currentTimeMillis() | val result = $functionBody | val timeToRun = System.currentTimeMillis() - startTime | println(""<- ${functionName}[ran in ${'$'}timeToRun ms]") | return result | }""" }
  61. fun replace(function: KtNamedFunction): String { val functionName = function.name val

    paramName = function.valueParameters.first().name val functionBody = function.body()"?.bodySourceAsExpression() return """ |fun ${functionName}(${paramName}: Int): Long { | println(""-> $functionName(${paramName}=$${paramName})") | val startTime = System.currentTimeMillis() | val result = $functionBody | val timeToRun = System.currentTimeMillis() - startTime | println(""<- ${functionName}[ran in ${'$'}timeToRun ms]") | return result | }""" }
  62. fun replace(function: KtNamedFunction): String { val functionName = function.name val

    paramName = function.valueParameters.first().name val functionBody = function.body()"?.bodySourceAsExpression() return """ |fun ${functionName}(${paramName}: Int): Long { | println(""-> $functionName(${paramName}=$${paramName})") | val startTime = System.currentTimeMillis() | val result = $functionBody | val timeToRun = System.currentTimeMillis() - startTime | println(""<- ${functionName}[ran in ${'$'}timeToRun ms]") | return result | }""" }
  63. fun replace(function: KtNamedFunction): String { val functionName = function.name val

    paramName = function.valueParameters.first().name val functionBody = function.body()"?.bodySourceAsExpression() return """ |fun ${functionName}(${paramName}: Int): Long { | println(""-> $functionName(${paramName}=$${paramName})") | val startTime = System.currentTimeMillis() | val result = $functionBody | val timeToRun = System.currentTimeMillis() - startTime | println(""<- ${functionName}[ran in ${'$'}timeToRun ms]") | return result | }""" }
  64. fun replace(function: KtNamedFunction): String { val functionName = function.name val

    paramName = function.valueParameters.first().name val functionBody = function.body()"?.bodySourceAsExpression() return """ |fun ${functionName}(${paramName}: Int): Long { | println(""-> $functionName(${paramName}=$${paramName})") | val startTime = System.currentTimeMillis() | val result = $functionBody | val timeToRun = System.currentTimeMillis() - startTime | println(""<- ${functionName}[ran in ${'$'}timeToRun ms]") | return result | }""" }
  65. fun replace(function: KtNamedFunction): String { val functionName = function.name val

    paramName = function.valueParameters.first().name val functionBody = function.body()"?.bodySourceAsExpression() return """ |fun ${functionName}(${paramName}: Int): Long { | println(""-> $functionName(${paramName}=$${paramName})") | val startTime = System.currentTimeMillis() | val result = $functionBody | val timeToRun = System.currentTimeMillis() - startTime | println(""<- ${functionName}[ran in ${'$'}timeToRun ms]") | return result | }""" }
  66. fun replace(function: KtNamedFunction): String { val functionName = function.name val

    paramName = function.valueParameters.first().name val functionBody = function.body()"?.bodySourceAsExpression() return """ |fun ${functionName}(${paramName}: Int): Long { | println(""-> $functionName(${paramName}=$${paramName})") | val startTime = System.currentTimeMillis() | val result = $functionBody | val timeToRun = System.currentTimeMillis() - startTime | println(""<- ${functionName}[ran in ${'$'}timeToRun ms]") | return result | }""" }
  67. val Meta.debugLog: CliPlugin get() = "DebugLog" { meta( namedFunction(this, {

    validateFunction() }) { c "-> Transform.replace( replacing = c, newDeclaration = replace(c).function ) } ) } Convert to function
  68. val Meta.debugLog: CliPlugin get() = "DebugLog" { meta( namedFunction(this, {

    validateFunction() }) { c "-> Transform.replace( replacing = c, newDeclaration = replace(c).function ) } ) } val String.function get() = NamedFunction( delegate.createFunction(trimMargin().trim()) )
  69. val Meta.debugLog: CliPlugin get() = "DebugLog" { meta( namedFunction(this, {

    validateFunction() }) { c "-> Transform.replace( replacing = c, newDeclaration = replace(c).function ) } ) }
  70. DebugLogPlugin src main kotlin com learn arrow-meta DebugLogPlugin fun main()

    { calculate(10) } @DebugLog fun calculate(n: Int): Long = listOf(1L,2L,3L).take(n).last()
  71. DebugLogPlugin src main kotlin com learn arrow-meta DebugLogPlugin fun main()

    { calculate(10) } @DebugLog fun calculate(n: Int): Long = listOf(1L,2L,3L).take(n).last() DebugLogPlugin.kt "-> calculate(n=10) "<- calculate[ran in 36 ms]
  72. val Meta.debugLog: CliPlugin get() = "DebugLog" { meta( namedFunction(this, {

    validateFunction() }) { c "-> Transform.replace( replacing = c, newDeclaration = replace(c).function ) } ) } How do we test this?
  73. arrow-kt/arrow-meta Code ! Issues Pull Requests compiler-plugin 447 commits 2060af5

    2 days ago ide-plugin gradle-plugin prelude docs meta-testing
  74. arrow-kt/arrow-meta Code ! Issues Pull Requests compiler-plugin 447 commits 2060af5

    2 days ago ide-plugin gradle-plugin prelude docs meta-testing Testing utilities
  75. val source = """ |annotation class DebugLog | |@DebugLog |fun

    calculate(n: Int): Long = listOf(1L,2L,3L).take(n).last() | """.trimMargin().trim().source
  76. val source = """ |annotation class DebugLog | |@DebugLog |fun

    prime(n: Int): Long = listOf(1L,2L,3L).take(n).last() | """.trimMargin().trim().source val String.source = Code.Source(text = this)
  77. val expectedOutput = """ |annotation class DebugLog | |fun calculate(n:

    Int): Long { | println(""-> calculate(n=${'$'}n)") | val startTime = System.currentTimeMillis() | val result = listOf(1L,2L,3L).take(n).last() | val timeToRun = System.currentTimeMillis() - startTime | println(""<- calculate[ran in ${'$'}timeToRun ms]") | return result | }""".trimMargin().trim().source Plugin should generate this
  78. @Test fun `should log function time execution`() { CompilerTest( config

    = { listOf(addMetaPlugins(DebugLogMetaPlugin())) }, code = { source }, assert = { quoteOutputMatches(expectedOutput) } ) } Testing utility
  79. @Test fun `should log function time execution`() { CompilerTest( config

    = { listOf(addMetaPlugins(DebugLogMetaPlugin())) }, code = { source }, assert = { quoteOutputMatches(expectedOutput) } ) } Plugin to test
  80. @Test fun `should log function time execution`() { CompilerTest( config

    = { listOf(addMetaPlugins(DebugLogMetaPlugin())) }, code = { source }, assert = { quoteOutputMatches(expectedOutput) } ) } Source to test
  81. @Test fun `should log function time execution`() { CompilerTest( config

    = { listOf(addMetaPlugins(DebugLogMetaPlugin())) }, code = { source }, assert = { quoteOutputMatches(expectedOutput) } ) } Expected generate code
  82. @Test fun `should log function time execution`() { CompilerTest( config

    = { listOf(addMetaPlugins(DebugLogMetaPlugin())) }, code = { debuglog }, assert = { quoteOutputMatches(expectedOutput) } ) } How do we run this test?
  83. @Test fun `sshould log function time execution`() { CompilerTest( config

    = { listOf(addMetaPlugins(DebugLogMetaPlugin())) }, code = { debuglog }, assert = { quoteOutputMatches(expectedOutput) } ) } fun assertThis(compilerTest: CompilerTest) = compilerTest.run(interpreter)
  84. @Test fun `should log function time execution`() { val compilerTest

    = CompilerTest(…) assertThis(compilerTest) } Run test
  85. @Test fun `should log function time execution`() { assertThis(CompilerTest( config

    = { listOf(addMetaPlugins(DebugLogMetaPlugin())) }, code = { source}, assert = { quoteOutputMatches(expectedOutput) } )) } How does this test run?
  86. val source = """ |annotation class DebugLog | |@DebugLog |fun

    calculate(n: Int): Long = listOf(1L,2L,3L).take(n).last() | """.trimMargin().trim().source Source of compiler plugin
  87. val source = """ |annotation class DebugLog | |@DebugLog |fun

    calculate(n: Int): Long = listOf(1L,2L,3L).take(n).last() | """.trimMargin().trim().source DebugLogPlugin.kt START quote.doAnalysis: [KtFile: Source.kt] Create a file with source
  88. val source = """ |annotation class DebugLog | |@DebugLog |fun

    calculate(n: Int): Long = listOf(1L,2L,3L).take(n).last() | """.trimMargin().trim().source DebugLogPlugin.kt START quote.doAnalysis: [KtFile: Source.kt] Transform file into AST Transformed file: KtFile: Source.kt.
  89. val source = """ |annotation class DebugLog | |@DebugLog |fun

    calculate(n: Int): Long = listOf(1L,2L,3L).take(n).last() | """.trimMargin().trim().source DebugLogPlugin.kt Transformed file: KtFile: Source.kt. [(KtFile: Source.kt, File(decls=[Structured(mods=[Lit(keyword=ANNOTATION)], ""... Structure of file
  90. @Test fun `should log function time execution`() { assertThis(CompilerTest( config

    = { listOf(addMetaPlugins(DebugLogMetaPlugin())) }, code = { source}, assert = { quoteOutputMatches(expectedOutput) } )) } DebugLogPlugin.kt Transformed file: KtFile: Source.kt. [(KtFile: Source.kt, File(decls=[Structured(mods=[Lit(keyword=ANNOTATION)], ""... Run test
  91. @Test fun `should log function time execution`() { assertThis(CompilerTest( config

    = { listOf(addMetaPlugins(DebugLogMetaPlugin())) }, code = { source}, assert = { quoteOutputMatches(expectedOutput) } )) } DebugLogPlugin.kt ktFile: KtFile: Source.kt ——————————————— fun calculate(n: Int): Long { println(""-> calculate(n=$n)") val startTime = System.curentTimeMillis() val result = listOf(1L,2L,3L).take(n).last() val timeToRun = System.currentTimeMillis() - startTime println(“"<- calculate[ran in $timeToRun ms]") return result } Generate log
  92. Writing Complier Plugins with Arrow Meta - Simple Hello World

    Plugin - Write & Test Plugin for logging - Combine Transformations - Custom Type Checker
  93. fun main() { helloWorld() } fun helloWorld() { println("Hello DroidCon

    EMEA!”) } Remove ReplacePluginKt src main kotlin com learn arrow-meta ReplacePluginKt
  94. val Meta.removePlugin: CliPlugin get() = “Remove" { meta( namedFunction(this, {

    name "== “helloWorld" }) { c "-> Transform.remove( removeIn = c, declaration = """println("Hello Droidcon EMEA!")""".expressionIn(c) ) } ) } Define Plugin
  95. val Meta.removePlugin: CliPlugin get() = “Remove" { meta( namedFunction(this, {

    name "== “helloWorld" }) { c "-> Transform.remove( removeIn = c, declaration = """println("Hello Droidcon EMEA!")""".expressionIn(c) ) } ) } Quote
  96. val Meta.removePlugin: CliPlugin get() = “Remove" { meta( namedFunction(this, {

    name "== “helloWorld" }) { c "-> Transform.remove( removeIn = c, declaration = """println("Hello Droidcon EMEA!")""".expressionIn(c) ) } ) } Filter function
  97. val Meta.removePlugin: CliPlugin get() = “Remove" { meta( namedFunction(this, {

    name "== “helloWorld" }) { c "-> Transform.remove( removeIn = c, declaration = """println("Hello Droidcon EMEA!")""".expressionIn(c) ) } ) } NamedFunction
  98. val Meta.removePlugin: CliPlugin get() = “Remove" { meta( namedFunction(this, {

    name "== “helloWorld" }) { c "-> Transform.remove( removeIn = c, declaration = """println("Hello Droidcon EMEA!")""".expressionIn(c) ) } ) }
  99. val Meta.removePlugin: CliPlugin get() = “Remove" { meta( namedFunction(this, {

    name "== “helloWorld" }) { c "-> Transform.remove( removeIn = c, declaration = """println("Hello Droidcon EMEA!")""".expressionIn(c) ) } ) }
  100. val Meta.removePlugin: CliPlugin get() = “Remove" { meta( namedFunction(this, {

    name "== “helloWorld" }) { c "-> Transform.remove( removeIn = c, declaration = """println("Hello Droidcon EMEA!")""".expressionIn(c) ) } ) } Template
  101. val Meta.removePlugin: CliPlugin get() = “Remove" { meta( namedFunction(this, {

    name "== “helloWorld" }) { c "-> Transform.remove( removeIn = c, declaration = """println("Hello Droidcon EMEA!")""".expressionIn(c) ) } ) } Convert to expression
  102. val Meta.removePlugin: CliPlugin get() = “Remove" { meta( namedFunction(this, {

    name "== “helloWorld" }) { c "-> Transform.remove( removeIn = c, declaration = """println("Hello Droidcon EMEA!")""".expressionIn(c) ) } ) }
  103. fun main() { helloWorld() } fun helloWorld() { println("Hello DroidCon

    EMEA!”) } ReplacePluginKt ReplacePluginKt src main kotlin com learn arrow-meta ReplacePluginKt Greeting is not printed
  104. data class User(""...) NewSourcePluginKt src main kotlin com learn arrow-meta

    NewSourcePluginKt class UserAdapter_Generated { fun deserialize() = { } fun serialize() = { } } Generate adapter in new file
  105. val Meta.adapterGenerator: CliPlugin get() = "Adapter Plugin" { meta( classDeclaration(this,

    { name "== "User" }) { Transform.newSources( """ package adapters class ${name}Adapter_Generated { fun deserialize() = { } fun serialize() = { } } """.file("${name}Adapter_Generated") ) } ) } Create Plugin
  106. val Meta.adapterGenerator: CliPlugin get() = "Adapter Plugin" { meta( classDeclaration(this,

    { name "== "User" }) { Transform.newSources( """ package adapters class ${name}Adapter_Generated { fun deserialize() = { } fun serialize() = { } } """.file("${name}Adapter_Generated") ) } ) } Define Extension Phases
  107. val Meta.adapterGenerator: CliPlugin get() = "Adapter Plugin" { meta( classDeclaration(this,

    { name "== "User" }) { Transform.newSources( """ package adapters class ${name}Adapter_Generated { fun deserialize() = { } fun serialize() = { } } """.file("${name}Adapter_Generated") ) } ) } Quote
  108. val Meta.adapterGenerator: CliPlugin get() = "Adapter Plugin" { meta( classDeclaration(this,

    { name "== "User" }) { Transform.newSources( """ package adapters class ${name}Adapter_Generated { fun deserialize() = { } fun serialize() = { } } """.file("${name}Adapter_Generated") ) } ) } Find user class
  109. val Meta.adapterGenerator: CliPlugin get() = "Adapter Plugin" { meta( classDeclaration(this,

    { name "== "User" }) { Transform.newSources( """ package adapters class ${name}Adapter_Generated { fun deserialize() = { } fun serialize() = { } } """.file("${name}Adapter_Generated") ) } ) } Transform class
  110. val Meta.adapterGenerator: CliPlugin get() = "Adapter Plugin" { meta( classDeclaration(this,

    { name "== "User" }) { Transform.newSources( """ package adapters class ${name}Adapter_Generated { fun deserialize() = { } fun serialize() = { } } """.file("${name}Adapter_Generated") ) } ) } Create adapter in new file
  111. val Meta.adapterGenerator: CliPlugin get() = "Adapter Plugin" { meta( classDeclaration(this,

    { name "== "User" }) { Transform.newSources( """ package adapters class ${name}Adapter_Generated { fun deserialize() = { } fun serialize() = { } } """.file("${name}Adapter_Generated") ) } ) } Adapter
  112. val Meta.adapterGenerator: CliPlugin get() = "Adapter Plugin" { meta( classDeclaration(this,

    { name "== "User" }) { Transform.newSources( """ package adapters class ${name}Adapter_Generated { fun deserialize() = { } fun serialize() = { } } """.file("${name}Adapter_Generated") ) } ) } Create a new file
  113. CombineTransformKt src main kotlin com learn arrow-meta CombineTransformKt public class

    SimpleClass { fun printGreeting() { println("Hello DroidCon EMEA!”) } }
  114. CombineTransformKt src main kotlin com learn arrow-meta CombineTransformKt public class

    SimpleClass { fun printGreeting() { println("Hello DroidCon EMEA!”) } } Change class visibility to private
  115. CombineTransformKt src main kotlin com learn arrow-meta CombineTransformKt public class

    SimpleClass { fun printGreeting() { println("Hello DroidCon EMEA!”) } } Remove Expression
  116. val Meta.composeTransforms: CliPlugin get() = "Compose Transforms" { meta( classDeclaration(this,

    { name "== "ManySimpleCase" }) { c "-> changeClassVisibility("ManySimpleCase", c, this) + removeGreeting(c, this) } ) }
  117. val Meta.composeTransforms: CliPlugin get() = "Compose Transforms" { meta( classDeclaration(this,

    { name "== "SimpleClass" }) { c "-> changeClassVisibility("SimpleClass", c, this) + removeGreeting(c, this) } ) } Filter Class
  118. val Meta.composeTransforms: CliPlugin get() = "Compose Transforms" { meta( classDeclaration(this,

    { name "== "SimpleClass" }) { c "-> changeClassVisibility(“SimpleClass", c, this) + removeGreeting(c, this) } ) } Change Class visible to private
  119. fun CompilerContext.changeClassVisibility(""...): Transform<KtClass> = declaration.run { Transform.replace( replacing = context,

    newDeclaration = """ | private class $className { | $body | } """.`class`.syntheticScope ) }
  120. fun CompilerContext.changeClassVisibility(""...): Transform<KtClass> = declaration.run { Transform.replace( replacing = context,

    newDeclaration = """ | private class $className { | $body | } """.`class`.syntheticScope ) } New Identifier
  121. val Meta.composeTransforms: CliPlugin get() = "Compose Transforms" { meta( classDeclaration(this,

    { name "== "SimpleClass" }) { c "-> changeClassVisibility(“SimpleClass", c, this) + removeGreeting(c, this) } ) } Remove Greeting
  122. fun CompilerContext.removeGreeting( context: KtClass, declaration: ClassDeclaration ): Transform<KtClass> = declaration.run

    { Transform.remove( removeIn = context, declaration = """ fun printFirst() = println("Hello DroidCon EMEA!") """.expressionIn(context) ) }
  123. fun CompilerContext.removeGreeting( context: KtClass, declaration: ClassDeclaration ): Transform<KtClass> = declaration.run

    { Transform.remove( removeIn = context, declaration = """ fun printGreeting() = println("Hello DroidCon EMEA!") """.expressionIn(context) ) } Remove Method
  124. val Meta.composeTransforms: CliPlugin get() = "Compose Transforms" { meta( classDeclaration(this,

    { name "== "SimpleClass" }) { c "-> changeClassVisibility(“SimpleClass", c, this) + removeGreeting(c, this) } ) } Combine?
  125. val Meta.composeTransforms: CliPlugin get() = "Compose Transforms" { meta( classDeclaration(this,

    { name "== "SimpleClass" }) { c "-> changeClassVisibility(“SimpleClass", c, this) + removeGreeting(c, this) } ) } Use Plus Operator + +
  126. val Meta.composeTransforms: CliPlugin get() = "Compose Transforms" { meta( classDeclaration(this,

    { name "== "SimpleClass" }) { c "-> changeClassVisibility(“SimpleClass", c, this) + removeGreeting(c, this) } ) } Remove Greeting
  127. Writing Complier Plugins with Arrow Meta - Simple Hello World

    Plugin - Write & Test Plugin for logging - Combine Transformations - Custom Type Checker
  128. class CustomTypeChecker() : NewKotlinTypeChecker { override fun isSubtypeOf( subtype: KotlinType,

    supertype: KotlinType ): Boolean { override fun equalTypes( subtype: KotlinType, supertype: KotlinType ): Boolean = } Type Checker Provided by Kotlin Compiler
  129. class CustomTypeChecker() : NewKotlinTypeChecker { override fun isSubtypeOf( subtype: KotlinType,

    supertype: KotlinType ): Boolean override fun equalTypes( subtype: KotlinType, supertype: KotlinType ): Boolean } Define Sub-typing
  130. class CustomTypeChecker() : NewKotlinTypeChecker { override fun isSubtypeOf( subtype: KotlinType,

    supertype: KotlinType ): Boolean override fun equalTypes( subtype: KotlinType, supertype: KotlinType ): Boolean } Define Equality
  131. class CustomTypeChecker() : NewKotlinTypeChecker { override fun isSubtypeOf( subtype: KotlinType,

    supertype: KotlinType ): Boolean override fun equalTypes( subtype: KotlinType, supertype: KotlinType ): Boolean } Define Equality
  132. val MetaIde.mviPlugin: IdePlugin get() = “MVI Plugin” { } operator

    fun String.invoke( phases: IdeContext.() "-> List<ExtensionPhase> ): IdePlugin
  133. val MetaIde.mviPlugin: IdePlugin get() = “MVI Plugin".invoke { meta( addLineMarkerProvider(

    icon = PluginIcons.UPDATE, composite = KtNamedFunction"::class.java, message = { f: KtNamedFunction "-> “State will be updated by action.” }, transform = { it.safeAs<KtNamedFunction>()"?.takeIf { f "-> f.name "== "setState" } } ) ) } Add marker for a node in AST
  134. val MetaIde.mviPlugin: IdePlugin get() = “MVI Plugin".invoke { meta( addLineMarkerProvider(

    icon = PluginIcons.UPDATE, composite = KtNamedFunction"::class.java, message = { f: KtNamedFunction "-> “State will be updated by action.” }, transform = { it.safeAs<KtNamedFunction>()"?.takeIf { f "-> f.name "== "setState" } } ) ) } Path to icon in resources
  135. val MetaIde.mviPlugin: IdePlugin get() = “MVI Plugin".invoke { meta( addLineMarkerProvider(

    icon = PluginIcons.UPDATE, composite = KtNamedFunction"::class.java, message = { f: KtNamedFunction "-> “State will be updated by action.” }, transform = { it.safeAs<KtNamedFunction>()"?.takeIf { f "-> f.name "== "setState" } } ) ) } AST Node
  136. val MetaIde.mviPlugin: IdePlugin get() = “MVI Plugin".invoke { meta( addLineMarkerProvider(

    icon = PluginIcons.UPDATE, composite = KtNamedFunction"::class.java, message = { f: KtNamedFunction "-> “State will be updated by action.” }, transform = { it.safeAs<KtNamedFunction>()"?.takeIf { f "-> f.name "== "setState" } } ) ) } Message to show on mouse over
  137. val MetaIde.mviPlugin: IdePlugin get() = “MVI Plugin".invoke { meta( addLineMarkerProvider(

    icon = PluginIcons.UPDATE, composite = KtNamedFunction"::class.java, message = { f: KtNamedFunction "-> “State will be updated by action.” }, transform = { it.safeAs<KtNamedFunction>()"?.takeIf { f "-> f.name "== "setState" } } ) ) } Filter function with property
  138. val MetaIde.mviPlugin: IdePlugin get() = “MVI Plugin".invoke { meta( addLineMarkerProvider(

    icon = PluginIcons.UPDATE, composite = KtNamedFunction"::class.java, message = { f: KtNamedFunction "-> “State will be updated by action.” }, transform = { it.safeAs<KtNamedFunction>()"?.takeIf { f "-> f.name "== "setState" } } ) ) }
  139. class TasksViewModel { fun refreshTasks() { setState { ""... }

    } } Show message for feature State will be updated by action.
  140. Thank you Arrow Team & All Contributors • Rachel M.

    Carmena • Raúl Raja • Imran Settuba • Simon Veragauwen • and all contributors!
  141. Resources • Writing Kotlin Compiler Plugins with Arrow Meta •

    Lambda World 2019 - Arrow Meta - Enabling Functional Programming in the Kotlin Compiler • Arrow Meta. Kotlin Metaprogramming for the masses • Type Proofs and FP for the Kotlin Type System