【Kotlin】JvmNameを使ったsame JVM signatureエラー回避テクニックまとめ

# TL;DR
- 引数の型パラメータのみ異なるような同名の関数は定義できない
  - `same JVM signature`エラーが発生する
- `JvmName`を利用することで、`Kotlin`での見た目上のオーバーロードを実現できる
- ただし、幾つかのパターンでは単純に`JvmName`を付与しても問題が出るため、状況に応じた対処が必要
  - 継承可能な関数の場合
  - 引数にラムダが絡む場合

# オーバーロードとジェネリクス
`Kotlin`(`Java`)では、同名で引数の型が異なる関数を定義できます。

一方、`List<Int>`と`List<Long>`のように、ジェネリクスの型引数だけ異なるような関数は定義できません。
`The following declarations have the same JVM signature...`としてコンパイルエラーとなります。

```kotlin:単純にオーバーロードできない例
fun foo(intList: List<Int>)
fun foo(longList: List<Long>)
```

# JvmNameを使った対処法
`Kotlin`では、このようなシグネチャ競合エラーに対応するための機能として、`JvmName`アノテーションが提供されています。
このアノテーションで名前を指定した場合、`JVM`上での名前が別途付けられるため、シグネチャ競合エラーを回避しつつ`Kotlin`上の見た目はオーバーロードされた状況にすることができます。

https://kotlinlang.org/docs/java-to-kotlin-interop.html#handling-signature-clashes-with-jvmname

これを使うと、先ほどのサンプルコードは以下のようにできます。

```kotlin:List&lt; Int&gt;を引数に取る方に名前を付けた例
@JvmName("fooInt")
fun foo(intList: List<Int>)
fun foo(longList: List<Long>)
```

# 単にJvmNameを付けるだけでは問題が出る場合について
`JvmName`は便利な機能ですが、そのまま付けるだけだと問題が出る場合が有ります。
以下、それらの問題が出る場面と回避方法をまとめます[^haaku]。

[^haaku]: ここにまとめるのは筆者が把握している内容だけで、完全に網羅的とは限らないことをご容赦ください。

## 継承可能な関数の場合
インターフェースや抽象関数など、継承可能な関数に`JvmName`アノテーションを付与した場合、`'@JvmName' annotation is not applicable to this declaration`としてコンパイルエラーになります。
例えば以下のような定義全てでコンパイルエラーになります。

```kotlin:単純なJvmNameで回避できない例（List&lt; Long&gt;分の定義は省略）
open class Foo {
    @JvmName("fooInt")
    open fun foo(intList: List<Int>) { /* ... */ }
}

abstract class Bar {
    @JvmName("fooInt")
    abstract fun foo(intList: List<Int>)
}

interface Baz {
    // デフォルト実装が有ってもエラーになる
    @JvmName("fooInt")
    fun foo(intList: List<Int>) { /* ... */ }
}
```

このエラーは`INAPPLICABLE_JVM_NAME`を抑制することで回避できます。

```kotlin:エラーを抑制して回避した例（List&lt; Long&gt;分の定義は省略）
open class Foo {
    @Suppress("INAPPLICABLE_JVM_NAME")
    @JvmName("fooInt")
    open fun foo(intList: List<Int>) { /* ... */ }
}

abstract class Bar {
    @Suppress("INAPPLICABLE_JVM_NAME")
    @JvmName("fooInt")
    abstract fun foo(intList: List<Int>)
}

interface Baz {
    @Suppress("INAPPLICABLE_JVM_NAME")
    @JvmName("fooInt")
    fun foo(intList: List<Int>) { /* ... */ }
}
```

### 補足
`INAPPLICABLE_JVM_NAME`を抑制した場合、`Java`との相互運用時に問題が生じる可能性があります（`Kotlin`のみで運用する場合には気にしなくても大丈夫です）。

また、このような場合に関しては、今後新しく`BinarySignatureName`アノテーションが導入され、抑制無しで扱えるようにする計画が有るそうです。

https://youtrack.jetbrains.com/issue/KT-31420/Support-JvmName-on-interface-or-provide-other-interface-evolution-mechanism#focus=Comments-27-6137680.0-0

## 引数にラムダが絡む場合
引数にラムダが絡む場合も、`JvmName`アノテーションでコンパイルを通すことはできます。

```kotlin
@JvmName("intFoo")
fun foo(intSupplier: () -> Int) {}
fun foo(longSupplier: () -> Long) {}
```

ただし、この関数を素直に呼び出すと、例え戻り値の型を指定したとしても`Overload resolution ambiguity. All these functions match.`としてコンパイルエラーになります。

```kotlin
fun bar() {
    // -> Overload resolution ambiguity. All these functions match.としてエラーになる
    foo { 1 }
}
```

この問題は`OverloadResolutionByLambdaReturnType`アノテーションを用いることで抑制できます。

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-overload-resolution-by-lambda-return-type/

先ほどの定義を以下のようにすることで、呼び出すコードはコンパイルエラーにならなくなります。

```kotlin:OverloadResolutionByLambdaReturnTypeで抑制した例
@OptIn(ExperimentalTypeInference::class) // 実験的機能なためOptInが必要
@OverloadResolutionByLambdaReturnType
@JvmName("intFoo")
fun foo(intSupplier: () -> Int) {}
fun foo(longSupplier: () -> Long) {}
```

### 補足
引数をキャストすることでもこの問題は回避できますが、これでは不細工なので、問題が起きない間は`OverloadResolutionByLambdaReturnType`を使っておく方がいいような気がしています。

```kotlin:ブロックをキャストしてエラーを回避した例
fun bar() {
    foo({ 1 } as () -> Int)
}
```