Kotlin の companion object の protected メソッドをコールすると実行時例外が発生する対処策


# 概要
表題のとおり、companion object に定義した protected メソッドをコールすると、下記の実行時例外が発生しました。

```
java.lang.IllegalAccessError: tried to access method com.sample.Parent$Companion.parentStaticProtected()Ljava/lang/String; from class com.Child$Companion

	at com.Child$Companion.parentFromChild(Child.kt:10)
	at another.ChildTest.call_test(ChildTest.kt:10)
```

# 詳細
ソースコードの構成は下記のようになっています。

```
.
└── com
    ├── Child.kt
    └── sample
        ├── Parent.kt
```

Child クラスから、Parent クラスをコールします。


```Child.kt
package com

import com.sample.Parent

class Child: Parent() {
    companion object {
        fun parentFromChild(): String {
            return parentStaticProtected()
        }
    }
}
```


```Parent.kt
package com.sample

abstract class Parent {
    companion object {
        @JvmStatic
        protected fun parentStaticProtected(): String {
            return "IllegalAccessError!!"
        }
    }
}
```

Parent の parentStaticProtected についている `@JvmStatic アノテーション` は、companion object の protected メソッドには、このアノテーションをつけるようコンパイラがエラーを出すので、コンパイルエラーを解決するためにつけています。

# 原因
Kotlin には、パッケージプライベートの概念はないのですが、どうやら companion object の protected メソッドは、同一パッケージ内でからのみコールできるようです。
試しに、Child クラスのパッケージを *com* から、*com.sample* に変更したところ、正常終了しました。

```Child.kt
package com.sample
```

# OptIn アノテーションで回避
パッケージの移動で回避できるものの、2 つの課題を感じました。

* 同一パッケージ外に companion object に protecte メソッドをもったサブクラスをおきたい時があるだろう
* 例外の意味がよくわからないので実行時例外前にコンパイルエラーで気付きたい

この挙動を変更してくれればよいのですが、いつになるかがよくわからないので、対処策として、[Opt-In Requirements](https://kotlinlang.org/docs/reference/opt-in-requirements.html) の機能を使って回避してみました。
この方法ですと、後述の `@OptIn` のアノテーションを、利用したくなったら都度メソッドにつける方法でコールできてしまいます。不適切なスコープからコールされる可能性はあるので、せめてそうならないよう、それらしい名前のアノテーションで定義します。


```SubClassCanUse.kt
package com.annotation

/**
 * このアノテーションは、実質 @RequiresOptIn アノテーションですが、
 * 役割を明確にするため別途アノテーション定義しています。
 */
@RequiresOptIn
annotation class SubClassCanUse
```

公式のページによると、[`@RequiresOptIn` アノテーション](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-requires-opt-in/) をつけたメソッドをコールするために、特殊な制限をかけることができます。

```Parent.kt
    companion object {
        @SubClassCanUse
        fun parentStaticProtected(): String {
            return "now we can call this... because scope is public."
        }
    }
```

このメソッドをコールしようとすると、

```
This declaration is experimental and its usage must be marked with '@com.annotation.SubClassCanUse' or '@OptIn(com.annotation.SubClassCanUse::class)'
```
とコンパイルエラーが発生します。コンパイルエラーを解決するためには、メッセージのとおり、呼び出し元に、

* `@SubClassCanUse`
* `@OptIn`

のどちらかのアノテーションを書く必要あります。`@SubClassCanUse アノテーション` でコンパイルエラーを解決する場合、parentStaticProtected をコールしているメソッドだけでなく、間接的にコールするメソッドを含めて、parentStaticProtected メソッドに到達するまでに利用されるすべてのメソッドに `@SubClassCanUse アノテーション` を設定しないといけません。
この方法だと、非常に多くの場所にアノテーションを記述するハメになり、ノイズになってしまうので、[`@OptIn アノテーション`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-opt-in/) を使用してコンパイルエラーを解決します。このアノテーションでコンパイルエラーを解決する場合は、直接 `@SubClassCanUse アノテーション`がついたメソッドをコールしているメソッドにだけ記述すれば良いです。

```Child.kt

package com

import com.annotation.SubClassCanUse
import com.sample.Parent

class Child: Parent() {
    companion object {

        @OptIn(SubClassCanUse::class)
        fun parentFromChild(): String {
            return parentStaticProtected()
        }
    }
}
```