DEV Community

Will BL
Will BL

Posted on • Edited on

1

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

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

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay