loading...

Mixins, Kotlin, and Default Methods - why they don't work together

williambl profile image William BL Updated on ・2 min read

A note for people using Mixins which implement interfaces which have default implementations of methods which are written in Kotlin.

Lets say you have an interface in Kotlin:

interface MyKotlinterface {
    fun doMyThing() {
      x.y.z.doThing(this)
    }
}
Enter fullscreen mode Exit fullscreen mode

and a mixin:

@Mixin(PlayerEntity.class)
abstract class MyMixin implements MyKotlinterface {}
Enter fullscreen mode Exit fullscreen mode

you might think that you can then do (player as MyKotlinterface).doMyThing(). However, you will get an AbstractMethodError. Why? because Kotlin doesn't compile your interface how you'd expect it to.

You'd think that it would compile like:

public interface MyKotlinterface {
     public default void doMyThing() {
          x.y.z.doThing(this)
     }
}
Enter fullscreen mode Exit fullscreen mode

but that would only work on JVMs at or above version 8, when default implementations for methods in interfaces were added - so Kotlin compiles it like this instead, which works on all JVMs:

public interface TestInterface {
    /* All calls to the default impl of this method
     * are rewritten by the kotlin compiler to use
     * the static method
     */
    public void doMyThing();

    public static final class DefaultImpls {
        public static void doMyThing(TestInterface obj) {
            x.y.z.doThing(obj)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The side-effect of this is that when you add your interface to a class with that mixin, it bypasses the kotlin compiler's rewriting - and so, attempts to access the default impl of the method on the player will crash with an AbstractMethodError. There are different fixes for this, depending on which Kotlin version you're using:

  • For Kotlin <1.2, you'll have to just write your implementations in your mixin class.
  • For Kotlin 1.2<=version<1.4, annotate your method with @JvmDefault, and put this in your build.gradle:
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    kotlinOptions {
        freeCompilerArgs += '-Xjvm-default'
    }
}
Enter fullscreen mode Exit fullscreen mode
  • For Kotlin >=1.4, just put this in your build.gradle:
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    kotlinOptions {
        freeCompilerArgs += '-Xjvm-default=all'
    }
}
Enter fullscreen mode Exit fullscreen mode

and your default methods will be compiled as they should be!

with thanks to this blog post and this decompiler/bytecode viewer

Discussion

pic
Editor guide