Writing Kotlin Compiler Plugins with Arrow Meta

B3f560d34c14a9113e5024bc34ac26a0?s=47 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.

B3f560d34c14a9113e5024bc34ac26a0?s=128

Mohit S

October 08, 2020
Tweet

Transcript

  1. Mohit Sarveiya Writing Compiler Plugins with Arrow Meta www.twitter.com/heyitsmohit www.codingwithmohit.com

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

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

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

    getUsers(): List<User> fun addUser() fun deleteUser(id: Int) }
  5. Code Parsing Analysis Resolve Code-gen Done Abstract Syntax Tree (AST)

  6. Code Parsing Analysis Resolve Code-gen Done Semantics

  7. Code Parsing Analysis Resolve Code-gen Done Type Information

  8. Code Parsing Analysis Resolve Code-gen Done Type Checking

  9. Code Parsing Analysis Resolve Code-gen Done class UserRepo { fun

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

    { } Operator ‘"==’cannot be applied to ‘String’ and ‘Int’ Type Checking
  11. Code Parsing Analysis Resolve Code-gen Done Intermediate Representation (IR) Js

    JVM Native
  12. Code Parsing Analysis Resolve Code-gen Done Compiled!

  13. ARROW META

  14. 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
  15. Code Parsing Analysis Resolve Code-gen Done

  16. Code Parsing Analysis Resolve Code-gen Done class UserRepo { fun

    getUsers(): List<User> fun addUser() fun deleteUser(id: Int) }
  17. Code Parsing Analysis Resolve Code-gen Done Abstract Syntax Tree (AST)

  18. Code Parsing Analysis Resolve Code-gen Done Quote Templates Search and

    transform
  19. Code Parsing Analysis Resolve Code-gen Done Type Checking

  20. Code Parsing Analysis Resolve Code-gen Done Type Checker

  21. Code Parsing Analysis Resolve Code-gen Done Type Checker Custom Type

    Checker
  22. Code Parsing Analysis Resolve Code-gen Done Intermediate Representation (IR) Js

    JVM Native Hook into IR
  23. Code Parsing Analysis Resolve Code-gen Done Compiled!

  24. Compiler Plugins Write & Test

  25. Writing Complier Plugins with Arrow Meta - Simple Hello World

    Plugin - Write & Test Plugin for logging - Combine Transformations - Custom Type Checker
  26. Steps to Write Compiler Plugin 1. Create Compiler Plugin(s) 2.

    Register Plugin
  27. fun main() { helloWorld() } fun helloWorld() { } HelloWorldKt

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

    EMEA!”) } Generate HelloWorldKt src main kotlin com learn arrow-meta HelloWorldKt
  29. val Meta.helloWorld: CliPlugin Entry Point for compiler

  30. val Meta.helloWorld: CliPlugin Type alias

  31. val Meta.helloWorld: CliPlugin typealias CliPlugin = Plugin<CompilerContext>

  32. val Meta.helloWorld: CliPlugin data class Plugin<A>( val name: String, val

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

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

    a Plugin object
  35. val Meta.helloWorld: CliPlugin get() = "Hello World" { } operator

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

    do we hook into the parsing phase?
  37. val Meta.helloWorld: CliPlugin get() = "Hello World" { meta( )

    } Intercept compilation phases
  38. val Meta.helloWorld: CliPlugin get() = "Hello World" { meta( )

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

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

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

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

    { name "== "helloWorld" }) { c "-> } ) } Function
  43. val Meta.helloWorld: CliPlugin get() = “Hello World" { meta( namedFunction(this,

    { name "== "helloWorld" }) { c "-> Transform.replace( ) } ) } Replace Transformation
  44. 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
  45. 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
  46. 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 ) } ) }
  47. Writing Complier Plugins with Arrow Meta 1. Create Compiler Plugin(s)

    2. Register Plugin
  48. class MetaPlugin : Meta { override fun intercept(ctx: CompilerContext): List<CliPlugin>

    = listOf( helloWorld ) } Create Meta Class
  49. class MetaPlugin : Meta { override fun intercept(ctx: CompilerContext): List<CliPlugin>

    = listOf( helloWorld ) } Register All Plugins with Kotlin Compiler
  50. class MetaPlugin : Meta { override fun intercept(ctx: CompilerContext): List<CliPlugin>

    = listOf( helloWorld ) } Register Hello World Plugin
  51. Plugin Build

  52. compileKotlin { kotlinOptions { jvmTarget = "$JVM_TARGET_VERSION" freeCompilerArgs = [“-Xplugin=${project.rootDir}/libs/my-plugin-all.jar"]

    } } Add Plugin jar
  53. fun main() { helloWorld() } fun helloWorld() { } HelloWorldKt

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

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

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

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

    Plugin - Write & Test Plugin for logging - Combine Transformations - Custom Type Checker
  58. DebugLogPlugin src main kotlin com learn arrow-meta DebugLogPlugin fun calculate(n:

    Int): Long = listOf(1L,2L,3L).take(n).last()
  59. 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
  60. DebugLogPlugin src main kotlin com learn arrow-meta DebugLogPlugin @DebugLog fun

    calculate(n: Int): Long = listOf(1L,2L,3L).take(n).last()
  61. 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 }
  62. 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
  63. 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
  64. 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
  65. 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
  66. 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
  67. 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
  68. 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?
  69. DebugLogPlugin src main kotlin com learn arrow-meta DebugLogPlugin @DebugLog fun

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

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

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

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

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

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

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

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

    validateFunction() }) { c "-> Transform.replace( replacing = c, newDeclaration = replace(c).function ) } ) } Create extension to find method
  78. fun KtNamedFunction.validateFunction(): Boolean = Extension for getting a function with

    annotation
  79. fun KtNamedFunction.validateFunction(): Boolean = hasOneIntParam() "&& hasLongReturnType() "&& hasAnnotation(DEBUG_LOG) Find

    method with a signature
  80. fun KtNamedFunction.validateFunction(): Boolean = hasOneIntParam() "&& hasLongReturnType() "&& hasAnnotation(DEBUG_LOG) Check

    method has debug annotation
  81. 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
  82. 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
  83. 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
  84. 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
  85. 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) }
  86. fun KtNamedFunction.validateFunction(): Boolean = hasOneIntParam() "&& hasLongReturnType() "&& hasAnnotation(DEBUG_LOG)

  87. val Meta.debugLog: CliPlugin get() = "DebugLog" { meta( namedFunction(this, {

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

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

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

    validateFunction() }) { c "-> Transform.replace( replacing = c, newDeclaration = replace(c).function ) } ) } Modify Function
  91. 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 | }""" }
  92. 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
  93. 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
  94. 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 | }""" }
  95. 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 | }""" }
  96. 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 | }""" }
  97. 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 | }""" }
  98. 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 | }""" }
  99. 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 | }""" }
  100. 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 | }""" }
  101. 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 | }""" }
  102. val Meta.debugLog: CliPlugin get() = "DebugLog" { meta( namedFunction(this, {

    validateFunction() }) { c "-> Transform.replace( replacing = c, newDeclaration = replace(c).function ) } ) } Convert to function
  103. 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()) )
  104. val Meta.debugLog: CliPlugin get() = "DebugLog" { meta( namedFunction(this, {

    validateFunction() }) { c "-> Transform.replace( replacing = c, newDeclaration = replace(c).function ) } ) }
  105. class DebugLogMetaPlugin : Meta { override fun intercept(ctx: CompilerContext): List<CliPlugin>

    = listOf( debugLog ) } Register Debug Log Plugin
  106. 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()
  107. 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]
  108. val Meta.debugLog: CliPlugin get() = "DebugLog" { meta( namedFunction(this, {

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

    2 days ago ide-plugin gradle-plugin prelude docs meta-testing
  110. 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
  111. val source = """ |annotation class DebugLog | |@DebugLog |fun

    calculate(n: Int): Long = listOf(1L,2L,3L).take(n).last() | """.trimMargin().trim().source
  112. 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)
  113. 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
  114. @Test fun `should log function time execution`() { }

  115. @Test fun `should log function time execution`() { CompilerTest( config

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

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

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

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

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

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

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

    = { listOf(addMetaPlugins(DebugLogMetaPlugin())) }, code = { source}, assert = { quoteOutputMatches(expectedOutput) } )) } How does this test run?
  123. 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
  124. 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
  125. 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.
  126. 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
  127. @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
  128. @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
  129. Writing Complier Plugins with Arrow Meta - Simple Hello World

    Plugin - Write & Test Plugin for logging - Combine Transformations - Custom Type Checker
  130. Transformations - Replace - Remove - New Sources - Combine

    Transformations
  131. Quotes - Expressions - Declarations - Class & Object -

    Properties - Type Reference
  132. fun main() { helloWorld() } fun helloWorld() { println("Hello DroidCon

    EMEA!”) } Remove ReplacePluginKt src main kotlin com learn arrow-meta ReplacePluginKt
  133. 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
  134. val Meta.removePlugin: CliPlugin get() = “Remove" { meta( namedFunction(this, {

    name "== “helloWorld" }) { c "-> Transform.remove( removeIn = c, declaration = """println("Hello Droidcon EMEA!")""".expressionIn(c) ) } ) } Quote
  135. 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
  136. val Meta.removePlugin: CliPlugin get() = “Remove" { meta( namedFunction(this, {

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

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

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

    name "== “helloWorld" }) { c "-> Transform.remove( removeIn = c, declaration = """println("Hello Droidcon EMEA!")""".expressionIn(c) ) } ) } Template
  140. 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
  141. val Meta.removePlugin: CliPlugin get() = “Remove" { meta( namedFunction(this, {

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

    EMEA!”) } ReplacePluginKt ReplacePluginKt src main kotlin com learn arrow-meta ReplacePluginKt Greeting is not printed
  143. Transformations - Replace - Remove - New Sources - Combine

    Transformations
  144. NewSourcePluginKt src main kotlin com learn arrow-meta NewSourcePluginKt data class

    User(""...)
  145. data class User(""...) NewSourcePluginKt src main kotlin com learn arrow-meta

    NewSourcePluginKt class UserAdapter_Generated { fun deserialize() = { } fun serialize() = { } } Generate adapter in new file
  146. 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
  147. 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
  148. 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
  149. 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
  150. 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
  151. 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
  152. 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
  153. 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
  154. data class User(""...) NewSourcePluginKt src main kotlin com learn arrow-meta

    NewSourcePluginKt
  155. NewSourcePluginKt src main kotlin com learn arrow-meta UserAdapter_Generated.kt class UserAdapter_Generated

    { fun deserialize() = { } fun serialize() = { } } Generated file
  156. Transformations - Replace - Remove - New Sources - Combine

    Transformations
  157. CombineTransformKt src main kotlin com learn arrow-meta CombineTransformKt public class

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

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

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

    SimpleClass { ""... }
  161. val Meta.composeTransforms: CliPlugin get() = "Compose Transforms" { meta( classDeclaration(this,

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

    { name "== "SimpleClass" }) { c "-> changeClassVisibility("SimpleClass", c, this) + removeGreeting(c, this) } ) } Filter Class
  163. 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
  164. fun CompilerContext.changeClassVisibility( className: String, context: KtClass, declaration: ClassDeclaration ): Transform<KtClass>

    = declaration.run { } SimpleClass
  165. fun CompilerContext.changeClassVisibility( className: String, context: KtClass, declaration: ClassDeclaration ): Transform<KtClass>

    = declaration.run { } Class to transform
  166. fun CompilerContext.changeClassVisibility( className: String, context: KtClass, declaration: ClassDeclaration ): Transform<KtClass>

    = declaration.run { } Class’s Declaration
  167. fun CompilerContext.changeClassVisibility(""...): Transform<KtClass> = declaration.run { Transform.replace( replacing = context,

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

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

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

    { Transform.remove( removeIn = context, declaration = """ fun printFirst() = println("Hello DroidCon EMEA!") """.expressionIn(context) ) }
  171. 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
  172. val Meta.composeTransforms: CliPlugin get() = "Compose Transforms" { meta( classDeclaration(this,

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

    { name "== "SimpleClass" }) { c "-> changeClassVisibility(“SimpleClass", c, this) + removeGreeting(c, this) } ) } Use Plus Operator + +
  174. operator fun Transform<K>.plus(transform: Transform<K>): Transform.Many(arrayListOf(this, transform)) Transform 1 Transform 2

  175. operator fun Transform<K>.plus(transform: Transform<K>): Transform.Many(arrayListOf(this, transform)) New Combine Transform

  176. val Meta.composeTransforms: CliPlugin get() = "Compose Transforms" { meta( classDeclaration(this,

    { name "== "SimpleClass" }) { c "-> changeClassVisibility(“SimpleClass", c, this) + removeGreeting(c, this) } ) } Remove Greeting
  177. CombineTransformKt src main kotlin com learn arrow-meta CombineTransformKt private class

    SimpleClass { ""... }
  178. Transformations - Replace - Remove - New Sources - Combine

    Transformations
  179. Writing Complier Plugins with Arrow Meta - Simple Hello World

    Plugin - Write & Test Plugin for logging - Combine Transformations - Custom Type Checker
  180. Type 1 Type 2

  181. Type 1 Type 2 "== Define equality & Subtyping

  182. val Meta.typeChecker: CliPlugin get() = “Custom Type Checker” { meta(

    typeChecker { ProofTypeChecker(ctx) } }
  183. val Meta.typeChecker: CliPlugin get() = “Custom Type Checker” { meta(

    typeChecker { CustomTypeChecker(ctx) } }
  184. 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
  185. class CustomTypeChecker() : NewKotlinTypeChecker { override fun isSubtypeOf( subtype: KotlinType,

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

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

    supertype: KotlinType ): Boolean override fun equalTypes( subtype: KotlinType, supertype: KotlinType ): Boolean } Define Equality
  188. Custom Type Checking Example - Type Proofs Plugin - Refinement

    Type Plugin
  189. IDE Plugins

  190. class TasksViewModel { fun refreshTasks() { setState { } }

    }
  191. class TasksViewModel { fun refreshTasks() { setState { ""... }

    } } Show icon in IDE
  192. val MetaIde.mviPlugin: IdePlugin Create extension property

  193. val MetaIde.mviPlugin: IdePlugin Create an IDE Plugin

  194. val MetaIde.mviPlugin: IdePlugin typealias IdePlugin = Plugin<IdeContext>

  195. val MetaIde.mviPlugin: IdePlugin get() = “MVI Plugin” { } Create

    Plugin object
  196. val MetaIde.mviPlugin: IdePlugin get() = “MVI Plugin” { } operator

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

    } Add extension phases
  198. 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
  199. 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
  200. 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
  201. 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
  202. 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
  203. 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" } } ) ) }
  204. class IdeMetaPlugin : MetaPlugin(),{ override fun intercept(ctx: IdeContext): List<IdePlugin> =

    listOf( mviPlugin ) } Extend MetaPlugin
  205. class IdeMetaPlugin : MetaPlugin() { override fun intercept(ctx: IdeContext): List<IdePlugin>

    = listOf( mviPlugin ) } Add all IDE plugins
  206. class TasksViewModel { fun refreshTasks() { setState { ""... }

    } } Show icon in IDE
  207. class TasksViewModel { fun refreshTasks() { setState { ""... }

    } } Show message for feature State will be updated by action.
  208. Plugins in Arrow Meta • Monad Comprehensions • Lens •

    Type Proofs
  209. https:"//codingwithmohit.com/arrow-meta/writing-compiler-plugin-with-arrow-meta/ Compiler Plugin

  210. Thank you Arrow Team & All Contributors • Rachel M.

    Carmena • Raúl Raja • Imran Settuba • Simon Veragauwen • and all contributors!
  211. Kotlin Slack Channels • #compiler • #arrow-meta

  212. 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
  213. Thank You! www.twitter.com/heyitsmohit www.codingwithmohit.com