Category: Uncategorized

  • Kotlin IR: Unlocking Incredible Possibilities for Code Manipulation

    I’m currently working on a pet project: a Kotlin assertion library designed to handle deep assertions over any object type. The goal is to write code that looks like this:

    Kotlin
    confirmThat { (1..3).toList() } deepMatches { listOf(1, 2, 3) }

    To make this work, the library needs to convert arbitrary code into a structured matcher tree. Under the hood, the goal is to transform that simple list check into something like this:

    Kotlin
    confirmThat { (1..3).toList() } deepMatches {
        ListMatcher(
            ValueMatcher(1),
            ValueMatcher(2),
            ValueMatcher(3)
        )
    }

    To pull this off, I weighed two sophisticated technical paths:

    Option 1: Runtime Bytecode Manipulation

    The first option is to modify the compiled bytecode while the application is running using tools like Byte Buddy.

    • The Pro: It’s a standard way to handle introspection on the JVM without needing a custom compiler setup.
    • The Con: It has very limited capabilities because Kotlin-specific details — like null-safety metadata — are often erased or transformed once the code is compiled. Many things are just impossible — e.g. you cannot convert primitives (int) into objects (Int), which might make it impossible to replace int with IntMatcher.

    Option 2: Kotlin Intermediate Representation (IR)

    The more “hardcore” approach is hijacking the Kotlin compilation process itself. By using a Kotlin Compiler Plugin, I can intercept the IR (Intermediate Representation). This happens after the code is parsed but before it undergoes any “lowering” steps (converting high-level constructs into simpler ones) or gets turned into bytecode.

    • The Pro: This allows me to see the code in its purest form. I can generate highly efficient, type-safe matchers that are baked directly into the program.
    • The Con: It’s significantly more complex to implement since it requires working deep within the compiler’s internal mechanics.

    My Take

    I decided to go the “hardcore” route. I’ve been experimenting with Kotlin IR, and it works perfectly! It handles the primitive-to-object mapping and null-safety metadata with ease.

    Stay tuned—I’ll be sharing more on how I actually implemented the IR transformer in my next post.