The Kotlin compiler’s job is to convert source code into byte code. Compiler plugins like Poko
are able to intercept a representation of the source code during compilation and affect the
produced byte code. To this end, Poko’s purpose is fairly straightforward: for any source class with
@Poko annotation, produce byte code that uses that class’s primary constructor properties in
three overridden functions.
To complicate things, Kotlin added a new compiler backend in 1.4, and made that new backend the default in 1.5. So depending on the mode in which the compiler runs, the byte code output is completely different: either classic JVM byte code or IR byte code. Poko supports both, effectively meaning this plugin’s core functions must be written twice.
When the Kotlin compiler starts executing, it loads
ComponentRegistrar services, which is how
plugins can register themselves.
registers both the non-IR
and the IR
here. Depending on the consuming project’s compiler settings, only one of these will actually be
used by the compiler.
IR function generation
PokoIrGenerationExtension executes for every Poko consumer who compiles with
useIR = true, which
is possible from Kotlin 1.4 and is the default in Kotlin 1.5. Poko first added support for this in
0.5.0, which supports Kotlin 1.4.20.
For every function that gets compiled, the extension checks if its owner class has the
annotation and meets the requirements for Poko classes (has a primary constructor with at least one
member property, etc.). If so, and if the function is one of the standard
hashCode functions, the plugin determines the class’s primary constructor properties and uses
those properties to override that function.
This approach fundamentally depends on every class already having these three functions by
default—it only intercepts the byte code generation after the compiler is already creating each
function. This is possible because all JVM classes include each of these three functions, implicitly
inherited from the
Object base class. Code inside
generateToStringMethodBody, is used to generate each function body by calling Kotlin compiler
APIs that represent IR byte code constructs.
Non-IR function generation
PokoCodegenExtension executes for every Poko consumer who compiles with
useIR = false, which is
the default for Kotlin 1.4 and can still be set in Kotlin 1.5 and 1.6.
For every class that gets compiled, the extension checks if that class has the
and meets the requirements for Poko classes. If so, it determines the class’s primary constructor
properties and uses those properties to override the
in that class.
Since every Java class has a default implementation for each of these functions, Poko finds the
existing function declaration and mutates it with the new byte code. Internal
classes, such as
are used to generate each function body by calling Kotlin compiler APIs that represent Java byte code