Changing kotlin extension function receiver JVM name
Asked Answered
L

2

10

This is a general question. Let's say I have an extension function written in kotlin which converts DP to PX and return a NonNull Int

fun Int.toPx() {  /** implementation */ }

The function in java will look something like this

public int toPx(int $receiver) {  /** implementation */ }

In my opinion the $receiver makes the Java-interop feels generated and uninviting.

I know that you can use the @JvmName annotation with some combinations like @file:JvmName to change the name in java.

When i'm trying to use @JvmName with the receiver site target it says

"This annotation is not applicable to target type usage and use site target @receiver"

Is there a way to overcome that and change the name of the receiver and if not what is the best alternative.

Lola answered 12/12, 2017 at 15:14 Comment(0)
M
11

@JvmName can only be applied to functions, property accessors and top-level package facades for files. Parameter names are not supported.

Basically, you can define two functions, one taking a simple parameter and another one with receiver:

fun toPx(value: Int) { /* implementation */ }

fun Int.toPx() = toPx(this)

But, expectedly enough, this won't compile because the two functions would have the same JVM signatures. So, to disambiguate them, add @JvmName("...") to the extension and (optionally) mark the extension as inline:

fun toPx(value: Int) { /* implementation */ }

@JvmName("toPxExtension") @Suppress("nothing_to_inline")
inline fun Int.toPx() = toPx(this)

To hide the extension function from Java, you can also annotate it with @JvmSynthetic.

The downside of this solution is the top-level function toPx leaking into the IDE completion scope of the files that see the package.

Mafalda answered 12/12, 2017 at 15:19 Comment(3)
It seems like a lot of work and code reuse just to achieve a java-interopLola
inline modifier doesn't hide a function from JavaGapin
@Gil The java interop is there from the beginning with no work. Yes, the parameter name is not intuitive, but it works just fine. The extra work is only required because you don't want to see $receiver as the parameter name when doing completion in Java. Unfortunately, there isn't an annotation to accomplish what you want, so you have a choice to make. Live with the ugly name, add an extra method, or convert the caller to Kotlin ;)Chrystel
S
1

The parameter name is only relevant in Java regarding documentation (as a method signature hint in the IDE, when you call a method). Unlike in Kotlin, it is never part of the calling code.

If you define extension methods on existing types, I've found that a good approach is to name the file in a way that describes the receiver. While this doesn't matter for Kotlin (as extension methods will be called without the file name), it does for other JVM languages. Thus, the receiver type/meaning is not expressed by the parameter name, but by the class name.

You're already aware of @file:JvmName, so use it to your advantage:

@file:JvmName("Ints")

fun Int.toPx() { ... }

In Kotlin:

val value = 328
val px = value.toPx()

In Java, the code is very close to the Kotlin counterpart:

int value = 328;
Pixel px = Ints.toPx(value);

Can of course be a longer name such as IntExtensionsif that helps readability.

Note that in Kotlin, it is explicitly allowed to reuse the same class name in @file:JvmName across multiple files, and the extensions functions will be combined in a single JVM class (see also here). You need the annotation @file:JvmMultifileClass for this.

Thus, you could also have an Int extension completely unrelated to pixels in another file, yet it would still end up in the same Java class Ints.

Shore answered 9/8, 2018 at 8:33 Comment(1)
It's an option but I don't think it's an equivalent conversion/option, I feel like it could be fixed by the Kotlin team and make the Java-interop seem lessLola

© 2022 - 2024 — McMap. All rights reserved.