K2コンパイラー移行ガイド
Kotlin言語とエコシステムが進化し続けるにつれて、Kotlinコンパイラーも進化してきました。最初のステップは、ロジックを共有し、異なるプラットフォーム上のターゲットのコード生成を簡素化する新しいJVMおよびJS IR (中間表現) バックエンドの導入でした。そして、その進化の次の段階として、K2として知られる新しいフロントエンドが導入されます。
K2コンパイラーの登場により、Kotlinのフロントエンドは完全に書き直され、より効率的な新しいアーキテクチャが採用されました。新しいコンパイラーがもたらす根本的な変更は、より多くのセマンティック情報を含む統一されたデータ構造を使用することです。このフロントエンドは、セマンティック解析、呼び出し解決、および型推論を実行する役割を担います。
新しいアーキテクチャと強化されたデータ構造により、K2コンパイラーは以下の利点を提供します。
- 呼び出し解決と型推論の改善。 コンパイラーの動作はより一貫性があり、コードをよりよく理解します。
- 新しい言語機能のシンタックスシュガーの導入が容易に。 今後、新しい機能が導入された際に、より簡潔で読みやすいコードを使用できるようになります。
- コンパイル時間の高速化。 コンパイル時間は大幅に高速化されます。
- IDEパフォーマンスの向上。 2025.1以降、IntelliJ IDEAはK2モードを使用してKotlinコードを解析し、安定性を高め、パフォーマンスを向上させます。詳細については、IDEでのサポートを参照してください。
このガイドでは、以下の内容を説明します。
- 新しいK2コンパイラーの利点。
- 移行中に遭遇する可能性のある変更点と、それに応じてコードを適応させる方法。
- 以前のバージョンにロールバックする方法。
NOTE
K2コンパイラーは2.0.0からデフォルトで有効になっています。Kotlin 2.0.0 で提供される新機能および新しいK2コンパイラーの詳細については、Kotlin 2.0.0 の新機能を参照してください。
パフォーマンスの改善
K2コンパイラーのパフォーマンスを評価するため、Anki-AndroidとExposedという2つのオープンソースプロジェクトでパフォーマンステストを実施しました。以下に、私たちが発見した主なパフォーマンス改善点を示します。
- K2コンパイラーは、最大94%のコンパイル速度向上をもたらします。例えば、Anki-Androidプロジェクトでは、クリーンビルド時間がKotlin 1.9.23での57.7秒からKotlin 2.0.0での29.7秒に短縮されました。
- K2コンパイラーでは、初期化フェーズが最大488%高速化されます。例えば、Anki-Androidプロジェクトでは、増分ビルドの初期化フェーズがKotlin 1.9.23での0.126秒からKotlin 2.0.0でのわずか0.022秒に短縮されました。
- Kotlin K2コンパイラーは、以前のコンパイラーと比較して、解析フェーズで最大376%高速です。例えば、Anki-Androidプロジェクトでは、増分ビルドの解析時間がKotlin 1.9.23での0.581秒からKotlin 2.0.0でのわずか0.122秒に削減されました。
これらの改善の詳細と、K2コンパイラーのパフォーマンスをどのように分析したかについては、ブログ投稿を参照してください。
言語機能の改善
Kotlin K2コンパイラーは、スマートキャストとKotlin Multiplatformに関連する言語機能を改善します。
スマートキャスト
Kotlinコンパイラーは、特定の場合にオブジェクトを型に自動的にキャストできるため、明示的に指定する手間が省けます。これはスマートキャストと呼ばれます。Kotlin K2コンパイラーは、以前よりもさらに多くのシナリオでスマートキャストを実行できるようになりました。
Kotlin 2.0.0では、スマートキャストに関連する以下の領域で改善を行いました。
ローカル変数とそれ以降のスコープ
以前は、if
条件内で変数がnull
でないと評価された場合、その変数はスマートキャストされていました。この変数に関する情報は、if
ブロックのスコープ内でさらに共有されていました。
しかし、変数をif
条件の外側で宣言した場合、その変数に関する情報はif
条件内では利用できないため、スマートキャストできませんでした。この動作は、when
式やwhile
ループでも見られました。
Kotlin 2.0.0以降では、if
、when
、またはwhile
条件で変数を使用する前に宣言した場合、コンパイラーがその変数に関して収集した情報は、スマートキャストのために対応するブロック内でアクセス可能になります。
これは、ブール条件を変数に抽出するなどの場合に役立ちます。そうすることで、変数に意味のある名前を付けられ、コードの可読性が向上し、後でコード内で変数を再利用できるようになります。例:
class Cat {
fun purr() {
println("Purr purr")
}
}
fun petAnimal(animal: Any) {
val isCat = animal is Cat
if (isCat) {
// In Kotlin 2.0.0, the compiler can access
// information about isCat, so it knows that
// animal was smart-cast to the type Cat.
// Therefore, the purr() function can be called.
// In Kotlin 1.9.20, the compiler doesn't know
// about the smart cast, so calling the purr()
// function triggers an error.
animal.purr()
}
}
fun main(){
val kitty = Cat()
petAnimal(kitty)
// Purr purr
}
論理OR演算子による型チェック
Kotlin 2.0.0では、オブジェクトの型チェックをor
演算子 (||
) と組み合わせた場合、最も近い共通のスーパータイプにスマートキャストされます。この変更以前は、スマートキャストは常にAny
型に対して行われていました。
この場合、そのオブジェクトのプロパティにアクセスしたり、関数を呼び出したりする前に、手動でオブジェクトの型をチェックする必要がありました。例:
interface Status {
fun signal() {}
}
interface Ok : Status
interface Postponed : Status
interface Declined : Status
fun signalCheck(signalStatus: Any) {
if (signalStatus is Postponed || signalStatus is Declined) {
// signalStatus is smart-cast to a common supertype Status
signalStatus.signal()
// Prior to Kotlin 2.0.0, signalStatus is smart cast
// to type Any, so calling the signal() function triggered an
// Unresolved reference error. The signal() function can only
// be called successfully after another type check:
// check(signalStatus is Status)
// signalStatus.signal()
}
}
NOTE
共通のスーパータイプはユニオン型の近似です。ユニオン型はKotlinでは現在サポートされていません。
インライン関数
Kotlin 2.0.0では、K2コンパイラーはインライン関数を異なる方法で扱い、他のコンパイラー解析と組み合わせてスマートキャストが安全であるかどうかを判断できます。
具体的には、インライン関数は暗黙的なcallsInPlace
コントラクトを持つものとして扱われるようになりました。これは、インライン関数に渡されるラムダ関数がインプレースで呼び出されることを意味します。ラムダ関数がインプレースで呼び出されるため、コンパイラーはラムダ関数がその関数本体内に含まれる変数への参照をリークできないことを認識します。
コンパイラーは、この知識を他のコンパイラー解析とともに使用して、キャプチャされた変数にスマートキャストすることが安全であるかどうかを判断します。例:
interface Processor {
fun process()
}
inline fun inlineAction(f: () -> Unit) = f()
fun nextProcessor(): Processor? = null
fun runProcessor(): Processor? {
var processor: Processor? = null
inlineAction {
// In Kotlin 2.0.0, the compiler knows that processor
// is a local variable and inlineAction() is an inline function, so
// references to processor can't be leaked. Therefore, it's safe
// to smart-cast processor.
// If processor isn't null, processor is smart-cast
if (processor != null) {
// The compiler knows that processor isn't null, so no safe call
// is needed
processor.process()
// In Kotlin 1.9.20, you have to perform a safe call:
// processor?.process()
}
processor = nextProcessor()
}
return processor
}
関数型を持つプロパティ
以前のバージョンのKotlinでは、関数型を持つクラスプロパティがスマートキャストされないというバグがありました。この動作はKotlin 2.0.0とK2コンパイラーで修正されました。例:
class Holder(val provider: (() -> Unit)?) {
fun process() {
// In Kotlin 2.0.0, if provider isn't null,
// it is smart-cast
if (provider != null) {
// The compiler knows that provider isn't null
provider()
// In 1.9.20, the compiler doesn't know that provider isn't
// null, so it triggers an error:
// Reference has a nullable type '(() -> Unit)?', use explicit '?.invoke()' to make a function-like call instead
}
}
}
この変更は、invoke
演算子をオーバーロードした場合にも適用されます。例:
interface Provider {
operator fun invoke()
}
interface Processor : () -> String
class Holder(val provider: Provider?, val processor: Processor?) {
fun process() {
if (provider != null) {
provider()
// In 1.9.20, the compiler triggers an error:
// Reference has a nullable type 'Provider?', use explicit '?.invoke()' to make a function-like call instead
}
}
}
例外処理
Kotlin 2.0.0では、例外処理を改善し、スマートキャスト情報をcatch
ブロックとfinally
ブロックに渡せるようにしました。この変更により、コンパイラーがオブジェクトがnull許容型であるかどうかを追跡するため、コードがより安全になります。例:
fun testString() {
var stringInput: String? = null
// stringInput is smart-cast to String type
stringInput = ""
try {
// The compiler knows that stringInput isn't null
println(stringInput.length)
// 0
// The compiler rejects previous smart cast information for
// stringInput. Now stringInput has the String? type.
stringInput = null
// Trigger an exception
if (2 > 1) throw Exception()
stringInput = ""
} catch (exception: Exception) {
// In Kotlin 2.0.0, the compiler knows stringInput
// can be null, so stringInput stays nullable.
println(stringInput?.length)
// null
// In Kotlin 1.9.20, the compiler says that a safe call isn't
// needed, but this is incorrect.
}
}
fun main() {
testString()
}
インクリメントおよびデクリメント演算子
Kotlin 2.0.0以前は、コンパイラーはインクリメントまたはデクリメント演算子を使用した後にオブジェクトの型が変更される可能性があることを理解していませんでした。コンパイラーがオブジェクトの型を正確に追跡できなかったため、コードが未解決参照エラーにつながる可能性がありました。Kotlin 2.0.0で、これは修正されました。
interface Rho {
operator fun inc(): Sigma = TODO()
}
interface Sigma : Rho {
fun sigma() = Unit
}
interface Tau {
fun tau() = Unit
}
fun main(input: Rho) {
var unknownObject: Rho = input
// Check if unknownObject inherits from the Tau interface
// Note, it's possible that unknownObject inherits from both
// Rho and Tau interfaces.
if (unknownObject is Tau) {
// Use the overloaded inc() operator from interface Rho.
// In Kotlin 2.0.0, the type of unknownObject is smart-cast to
// Sigma.
++unknownObject
// In Kotlin 2.0.0, the compiler knows unknownObject has type
// Sigma, so the sigma() function can be called successfully.
unknownObject.sigma()
// In Kotlin 1.9.20, the compiler doesn't perform a smart cast
// when inc() is called so the compiler still thinks that
// unknownObject has type Tau. Calling the sigma() function
// throws a compile-time error.
// In Kotlin 2.0.0, the compiler knows unknownObject has type
// Sigma, so calling the tau() function throws a compile-time
// error.
unknownObject.tau()
// Unresolved reference 'tau'
// In Kotlin 1.9.20, since the compiler mistakenly thinks that
// unknownObject has type Tau, the tau() function can be called,
// but it throws a ClassCastException.
}
}
Kotlin Multiplatform
K2コンパイラーには、Kotlin Multiplatformに関連して以下の領域で改善点があります。
コンパイル時の共通ソースとプラットフォームソースの分離
以前は、Kotlinコンパイラーの設計により、コンパイル時に共通ソースセットとプラットフォームソースセットを分離することができませんでした。その結果、共通コードがプラットフォームコードにアクセスでき、プラットフォーム間で異なる動作を引き起こしていました。さらに、共通コードからのいくつかのコンパイラー設定や依存関係がプラットフォームコードに漏洩していました。
Kotlin 2.0.0では、新しいKotlin K2コンパイラーの実装に、共通ソースセットとプラットフォームソースセット間の厳密な分離を確実にするためのコンパイルスキームの再設計が含まれました。この変更は、expected/actual関数を使用する際に最も顕著です。以前は、共通コード内の関数呼び出しがプラットフォームコード内の関数に解決されることが可能でした。例:
共通コード | プラットフォームコード |
kotlin
| kotlin
|
この例では、共通コードは実行されるプラットフォームに応じて異なる動作をします。
- JVMプラットフォームでは、共通コード内の
foo()
関数を呼び出すと、プラットフォームコードのfoo()
関数がplatform foo
として呼び出されます。 - JavaScriptプラットフォームでは、プラットフォームコードにそのような関数がないため、共通コード内の
foo()
関数を呼び出すと、共通コードのfoo()
関数がcommon foo
として呼び出されます。
Kotlin 2.0.0では、共通コードはプラットフォームコードにアクセスできないため、両方のプラットフォームでfoo()
関数は共通コードのfoo()
関数 (common foo
) に正常に解決されます。
プラットフォーム間の動作の一貫性が向上したことに加え、IntelliJ IDEAまたはAndroid Studioとコンパイラーの間で動作の競合があったケースの修正にも尽力しました。例えば、expected/actualクラスを使用した場合、以下のようになります。
共通コード | プラットフォームコード |
kotlin
| kotlin
|
この例では、expectedクラスIdentity
にはデフォルトコンストラクターがないため、共通コードでは正常に呼び出せません。以前は、エラーはIDEによってのみ報告されていましたが、JVM上ではコードは正常にコンパイルされていました。しかし、現在はコンパイラーが正しくエラーを報告します。
Expected class 'expect class Identity : Any' does not have default constructor
解決動作が変化しない場合
私たちはまだ新しいコンパイルスキームへの移行の過程にあるため、同じソースセット内にない関数を呼び出す際の解決動作は以前と同じです。この違いは、主に共通コードでマルチプラットフォームライブラリからのオーバーロードを使用する場合に顕著になります。
異なるシグネチャを持つ2つのwhichFun()
関数があるライブラリがあるとします。
// Example library
// MODULE: common
fun whichFun(x: Any) = println("common function")
// MODULE: JVM
fun whichFun(x: Int) = println("platform function")
共通コードでwhichFun()
関数を呼び出すと、ライブラリ内で最も関連性の高い引数型を持つ関数が解決されます。
// A project that uses the example library for the JVM target
// MODULE: common
fun main(){
whichFun(2)
// platform function
}
比較として、同じソースセット内でwhichFun()
のオーバーロードを宣言した場合、コードがプラットフォーム固有のバージョンにアクセスできないため、共通コードの関数が解決されます。
// Example library isn't used
// MODULE: common
fun whichFun(x: Any) = println("common function")
fun main(){
whichFun(2)
// common function
}
// MODULE: JVM
fun whichFun(x: Int) = println("platform function")
マルチプラットフォームライブラリと同様に、commonTest
モジュールは別のソースセットにあるため、プラットフォーム固有のコードにも引き続きアクセスできます。したがって、commonTest
モジュール内の関数呼び出しの解決は、古いコンパイルスキームと同じ動作を示します。
将来的に、これらの残りのケースも新しいコンパイルスキームとより一貫性を持つようになるでしょう。
expected/actual宣言の異なる可視性レベル
Kotlin 2.0.0より前は、Kotlin Multiplatformプロジェクトでexpected/actual宣言を使用する場合、同じ可視性レベルである必要がありました。Kotlin 2.0.0では、異なる可視性レベルもサポートされるようになりましたが、それは実際の宣言がexpected宣言よりも_許容度が高い_場合に限られます。例:
expect internal class Attribute // Visibility is internal
actual class Attribute // Visibility is public by default,
// which is more permissive
同様に、実際の宣言で型エイリアスを使用している場合、基になる型の可視性は、expected宣言と同じか、より許容度が高い必要があります。例:
expect internal class Attribute // Visibility is internal
internal actual typealias Attribute = Expanded
class Expanded // Visibility is public by default,
// which is more permissive
Kotlin K2コンパイラーを有効にする方法
Kotlin 2.0.0以降、Kotlin K2コンパイラーはデフォルトで有効になっています。
Kotlinバージョンをアップグレードするには、GradleおよびMavenのビルドスクリプトで、バージョンを2.0.0以降のリリースに変更します。
IntelliJ IDEAまたはAndroid Studioで最高の体験を得るには、IDEでK2モードを有効化することを検討してください。
GradleでKotlinビルドレポートを使用する
Kotlinのビルドレポートは、Kotlinコンパイラータスクの異なるコンパイルフェーズで費やされた時間、使用されたコンパイラーとKotlinのバージョン、およびコンパイルが増分であったかどうかに関する情報を提供します。これらのビルドレポートは、ビルドパフォーマンスの評価に役立ちます。これらはすべてのGradleタスクのパフォーマンスの概要を提供するため、GradleビルドスキャンよりもKotlinコンパイルパイプラインに関するより多くの洞察を提供します。
ビルドレポートを有効にする方法
ビルドレポートを有効にするには、gradle.properties
ファイルでビルドレポートの出力先を宣言します。
kotlin.build.report.output=file
出力には以下の値とその組み合わせが利用可能です。
| オプション | 説明 ---
[//]: # (title: K2コンパイラー移行ガイド)
Kotlin言語とエコシステムが進化し続けるにつれて、Kotlinコンパイラーも進化してきました。最初のステップは、ロジックを共有し、異なるプラットフォーム上のターゲットのコード生成を簡素化する新しいJVMおよびJS IR (中間表現) バックエンドの導入でした。そして、その進化の次の段階として、K2として知られる新しいフロントエンドが導入されます。
{width=700}
K2コンパイラーの登場により、Kotlinのフロントエンドは完全に書き直され、より効率的な新しいアーキテクチャが採用されました。新しいコンパイラーがもたらす根本的な変更は、より多くのセマンティック情報を含む統一されたデータ構造を使用することです。このフロントエンドは、セマンティック解析、呼び出し解決、および型推論を実行する役割を担います。
新しいアーキテクチャと強化されたデータ構造により、K2コンパイラーは以下の利点を提供します。
* **呼び出し解決と型推論の改善。** コンパイラーの動作はより一貫性があり、コードをよりよく理解します。
* **新しい言語機能のシンタックスシュガーの導入が容易に。** 今後、新しい機能が導入された際に、より簡潔で読みやすいコードを使用できるようになります。
* **コンパイル時間の高速化。** コンパイル時間は[大幅に高速化](#performance-improvements)されます。
* **IDEパフォーマンスの向上。** 2025.1以降、IntelliJ IDEAはK2モードを使用してKotlinコードを解析し、安定性を高め、パフォーマンスを向上させます。詳細については、[IDEでのサポート](#support-in-ides)を参照してください。
このガイドでは、以下の内容を説明します。
* 新しいK2コンパイラーの利点。
* 移行中に遭遇する可能性のある変更点と、それに応じてコードを適応させる方法。
* 以前のバージョンにロールバックする方法。
::: note
K2コンパイラーは2.0.0からデフォルトで有効になっています。Kotlin 2.0.0 で提供される新機能および新しいK2コンパイラーの詳細については、[Kotlin 2.0.0 の新機能](whatsnew20.md)を参照してください。
:::
## パフォーマンスの改善
K2コンパイラーのパフォーマンスを評価するため、[Anki-Android](https://github.com/ankidroid/Anki-Android)と[Exposed](https://github.com/JetBrains/Exposed)という2つのオープンソースプロジェクトでパフォーマンステストを実施しました。以下に、私たちが発見した主なパフォーマンス改善点を示します。
* K2コンパイラーは、最大94%のコンパイル速度向上をもたらします。例えば、Anki-Androidプロジェクトでは、クリーンビルド時間がKotlin 1.9.23での57.7秒からKotlin 2.0.0での29.7秒に短縮されました。
* K2コンパイラーでは、初期化フェーズが最大488%高速化されます。例えば、Anki-Androidプロジェクトでは、増分ビルドの初期化フェーズがKotlin 1.9.23での0.126秒からKotlin 2.0.0でのわずか0.022秒に短縮されました。
* Kotlin K2コンパイラーは、以前のコンパイラーと比較して、解析フェーズで最大376%高速です。例えば、Anki-Androidプロジェクトでは、増分ビルドの解析時間がKotlin 1.9.23での0.581秒からKotlin 2.0.0でのわずか0.122秒に削減されました。
これらの改善の詳細と、K2コンパイラーのパフォーマンスをどのように分析したかについては、[ブログ投稿](https://blog.jetbrains.com/kotlin/2024/04/k2-compiler-performance-benchmarks-and-how-to-measure-them-on-your-projects/)を参照してください。
## 言語機能の改善
Kotlin K2コンパイラーは、[スマートキャスト](#smart-casts)と[Kotlin Multiplatform](#kotlin-multiplatform)に関連する言語機能を改善します。
### スマートキャスト
Kotlinコンパイラーは、特定の場合にオブジェクトを型に自動的にキャストできるため、明示的に指定する手間が省けます。これは[スマートキャスト](typecasts.md#smart-casts)と呼ばれます。Kotlin K2コンパイラーは、以前よりもさらに多くのシナリオでスマートキャストを実行できるようになりました。
Kotlin 2.0.0では、スマートキャストに関連する以下の領域で改善を行いました。
* [ローカル変数とそれ以降のスコープ](#local-variables-and-further-scopes)
* [論理OR演算子による型チェック](#type-checks-with-the-logical-or-operator)
* [インライン関数](#inline-functions)
* [関数型を持つプロパティ](#properties-with-function-types)
* [例外処理](#exception-handling)
* [インクリメントおよびデクリメント演算子](#increment-and-decrement-operators)
#### ローカル変数とそれ以降のスコープ
以前は、`if`条件内で変数が`null`でないと評価された場合、その変数はスマートキャストされていました。この変数に関する情報は、`if`ブロックのスコープ内でさらに共有されていました。
しかし、変数を`if`条件の**外側**で宣言した場合、その変数に関する情報は`if`条件内では利用できないため、スマートキャストできませんでした。この動作は、`when`式や`while`ループでも見られました。
Kotlin 2.0.0以降では、`if`、`when`、または`while`条件で変数を使用する前に宣言した場合、コンパイラーがその変数に関して収集した情報は、スマートキャストのために対応するブロック内でアクセス可能になります。
これは、ブール条件を変数に抽出するなどの場合に役立ちます。そうすることで、変数に意味のある名前を付けられ、コードの可読性が向上し、後でコード内で変数を再利用できるようになります。例:
```kotlin
class Cat {
fun purr() {
println("Purr purr")
}
}
fun petAnimal(animal: Any) {
val isCat = animal is Cat
if (isCat) {
// In Kotlin 2.0.0, the compiler can access
// information about isCat, so it knows that
// animal was smart-cast to the type Cat.
// Therefore, the purr() function can be called.
// In Kotlin 1.9.20, the compiler doesn't know
// about the smart cast, so calling the purr()
// function triggers an error.
animal.purr()
}
}
fun main(){
val kitty = Cat()
petAnimal(kitty)
// Purr purr
}
論理OR演算子による型チェック
Kotlin 2.0.0では、オブジェクトの型チェックをor
演算子 (||
) と組み合わせた場合、最も近い共通のスーパータイプにスマートキャストされます。この変更以前は、スマートキャストは常にAny
型に対して行われていました。
この場合、そのオブジェクトのプロパティにアクセスしたり、関数を呼び出したりする前に、手動でオブジェクトの型をチェックする必要がありました。例:
interface Status {
fun signal() {}
}
interface Ok : Status
interface Postponed : Status
interface Declined : Status
fun signalCheck(signalStatus: Any) {
if (signalStatus is Postponed || signalStatus is Declined) {
// signalStatus is smart-cast to a common supertype Status
signalStatus.signal()
// Prior to Kotlin 2.0.0, signalStatus is smart cast
// to type Any, so calling the signal() function triggered an
// Unresolved reference error. The signal() function can only
// be called successfully after another type check:
// check(signalStatus is Status)
// signalStatus.signal()
}
}
NOTE
共通のスーパータイプはユニオン型の近似です。ユニオン型はKotlinでは現在サポートされていません。
インライン関数
Kotlin 2.0.0では、K2コンパイラーはインライン関数を異なる方法で扱い、他のコンパイラー解析と組み合わせてスマートキャストが安全であるかどうかを判断できます。
具体的には、インライン関数は暗黙的なcallsInPlace
コントラクトを持つものとして扱われるようになりました。これは、インライン関数に渡されるラムダ関数がインプレースで呼び出されることを意味します。ラムダ関数がインプレースで呼び出されるため、コンパイラーはラムダ関数がその関数本体内に含まれる変数への参照をリークできないことを認識します。
コンパイラーは、この知識を他のコンパイラー解析とともに使用して、キャプチャされた変数にスマートキャストすることが安全であるかどうかを判断します。例:
interface Processor {
fun process()
}
inline fun inlineAction(f: () -> Unit) = f()
fun nextProcessor(): Processor? = null
fun runProcessor(): Processor? {
var processor: Processor? = null
inlineAction {
// In Kotlin 2.0.0, the compiler knows that processor
// is a local variable and inlineAction() is an inline function, so
// references to processor can't be leaked. Therefore, it's safe
// to smart-cast processor.
// If processor isn't null, processor is smart-cast
if (processor != null) {
// The compiler knows that processor isn't null, so no safe call
// is needed
processor.process()
// In Kotlin 1.9.20, you have to perform a safe call:
// processor?.process()
}
processor = nextProcessor()
}
return processor
}
関数型を持つプロパティ
以前のバージョンのKotlinでは、関数型を持つクラスプロパティがスマートキャストされないというバグがありました。この動作はKotlin 2.0.0とK2コンパイラーで修正されました。例:
class Holder(val provider: (() -> Unit)?) {
fun process() {
// In Kotlin 2.0.0, if provider isn't null,
// it is smart-cast
if (provider != null) {
// The compiler knows that provider isn't null
provider()
// In 1.9.20, the compiler doesn't know that provider isn't
// null, so it triggers an error:
// Reference has a nullable type '(() -> Unit)?', use explicit '?.invoke()' to make a function-like call instead
}
}
}
この変更は、invoke
演算子をオーバーロードした場合にも適用されます。例:
interface Provider {
operator fun invoke()
}
interface Processor : () -> String
class Holder(val provider: Provider?, val processor: Processor?) {
fun process() {
if (provider != null) {
provider()
// In 1.9.20, the compiler triggers an error:
// Reference has a nullable type 'Provider?', use explicit '?.invoke()' to make a function-like call instead
}
}
}
例外処理
Kotlin 2.0.0では、例外処理を改善し、スマートキャスト情報をcatch
ブロックとfinally
ブロックに渡せるようにしました。この変更により、コンパイラーがオブジェクトがnull許容型であるかどうかを追跡するため、コードがより安全になります。例:
fun testString() {
var stringInput: String? = null
// stringInput is smart-cast to String type
stringInput = ""
try {
// The compiler knows that stringInput isn't null
println(stringInput.length)
// 0
// The compiler rejects previous smart cast information for
// stringInput. Now stringInput has the String? type.
stringInput = null
// Trigger an exception
if (2 > 1) throw Exception()
stringInput = ""
} catch (exception: Exception) {
// In Kotlin 2.0.0, the compiler knows stringInput
// can be null, so stringInput stays nullable.
println(stringInput?.length)
// null
// In Kotlin 1.9.20, the compiler says that a safe call isn't
// needed, but this is incorrect.
}
}
fun main() {
testString()
}
インクリメントおよびデクリメント演算子
Kotlin 2.0.0以前は、コンパイラーはインクリメントまたはデクリメント演算子を使用した後にオブジェクトの型が変更される可能性があることを理解していませんでした。コンパイラーがオブジェクトの型を正確に追跡できなかったため、コードが未解決参照エラーにつながる可能性がありました。Kotlin 2.0.0で、これは修正されました。
interface Rho {
operator fun inc(): Sigma = TODO()
}
interface Sigma : Rho {
fun sigma() = Unit
}
interface Tau {
fun tau() = Unit
}
fun main(input: Rho) {
var unknownObject: Rho = input
// Check if unknownObject inherits from the Tau interface
// Note, it's possible that unknownObject inherits from both
// Rho and Tau interfaces.
if (unknownObject is Tau) {
// Use the overloaded inc() operator from interface Rho.
// In Kotlin 2.0.0, the type of unknownObject is smart-cast to
// Sigma.
++unknownObject
// In Kotlin 2.0.0, the compiler knows unknownObject has type
// Sigma, so the sigma() function can be called successfully.
unknownObject.sigma()
// In Kotlin 1.9.20, the compiler doesn't perform a smart cast
// when inc() is called so the compiler still thinks that
// unknownObject has type Tau. Calling the sigma() function
// throws a compile-time error.
// In Kotlin 2.0.0, the compiler knows unknownObject has type
// Sigma, so calling the tau() function throws a compile-time
// error.
unknownObject.tau()
// Unresolved reference 'tau'
// In Kotlin 1.9.20, since the compiler mistakenly thinks that
// unknownObject has type Tau, the tau() function can be called,
// but it throws a ClassCastException.
}
}
Kotlin Multiplatform
K2コンパイラーには、Kotlin Multiplatformに関連して以下の領域で改善点があります。
コンパイル時の共通ソースとプラットフォームソースの分離
以前は、Kotlinコンパイラーの設計により、コンパイル時に共通ソースセットとプラットフォームソースセットを分離することができませんでした。その結果、共通コードがプラットフォームコードにアクセスでき、プラットフォーム間で異なる動作を引き起こしていました。さらに、共通コードからのいくつかのコンパイラー設定や依存関係がプラットフォームコードに漏洩していました。
Kotlin 2.0.0では、新しいKotlin K2コンパイラーの実装に、共通ソースセットとプラットフォームソースセット間の厳密な分離を確実にするためのコンパイルスキームの再設計が含まれました。この変更は、expected/actual関数を使用する際に最も顕著です。以前は、共通コード内の関数呼び出しがプラットフォームコード内の関数に解決されることが可能でした。例:
共通コード | プラットフォームコード |
kotlin
| kotlin
|
この例では、共通コードは実行されるプラットフォームに応じて異なる動作をします。
- JVMプラットフォームでは、共通コード内の
foo()
関数を呼び出すと、プラットフォームコードのfoo()
関数がplatform foo
として呼び出されます。 - JavaScriptプラットフォームでは、プラットフォームコードにそのような関数がないため、共通コード内の
foo()
関数を呼び出すと、共通コードのfoo()
関数がcommon foo
として呼び出されます。
Kotlin 2.0.0では、共通コードはプラットフォームコードにアクセスできないため、両方のプラットフォームでfoo()
関数は共通コードのfoo()
関数 (common foo
) に正常に解決されます。
プラットフォーム間の動作の一貫性が向上したことに加え、IntelliJ IDEAまたはAndroid Studioとコンパイラーの間で動作の競合があったケースの修正にも尽力しました。例えば、expected/actualクラスを使用した場合、以下のようになります。
共通コード | プラットフォームコード |
kotlin
| kotlin
|
この例では、expectedクラスIdentity
にはデフォルトコンストラクターがないため、共通コードでは正常に呼び出せません。以前は、エラーはIDEによってのみ報告されていましたが、JVM上ではコードは正常にコンパイルされていました。しかし、現在はコンパイラーが正しくエラーを報告します。
Expected class 'expect class Identity : Any' does not have default constructor
解決動作が変化しない場合
私たちはまだ新しいコンパイルスキームへの移行の過程にあるため、同じソースセット内にない関数を呼び出す際の解決動作は以前と同じです。この違いは、主に共通コードでマルチプラットフォームライブラリからのオーバーロードを使用する場合に顕著になります。
異なるシグネチャを持つ2つのwhichFun()
関数があるライブラリがあるとします。
// Example library
// MODULE: common
fun whichFun(x: Any) = println("common function")
// MODULE: JVM
fun whichFun(x: Int) = println("platform function")
共通コードでwhichFun()
関数を呼び出すと、ライブラリ内で最も関連性の高い引数型を持つ関数が解決されます。
// A project that uses the example library for the JVM target
// MODULE: common
fun main(){
whichFun(2)
// platform function
}
比較として、同じソースセット内でwhichFun()
のオーバーロードを宣言した場合、コードがプラットフォーム固有のバージョンにアクセスできないため、共通コードの関数が解決されます。
// Example library isn't used
// MODULE: common
fun whichFun(x: Any) = println("common function")
fun main(){
whichFun(2)
// common function
}
// MODULE: JVM
fun whichFun(x: Int) = println("platform function")
マルチプラットフォームライブラリと同様に、commonTest
モジュールは別のソースセットにあるため、プラットフォーム固有のコードにも引き続きアクセスできます。したがって、commonTest
モジュール内の関数呼び出しの解決は、古いコンパイルスキームと同じ動作を示します。
将来的に、これらの残りのケースも新しいコンパイルスキームとより一貫性を持つようになるでしょう。
expected/actual宣言の異なる可視性レベル
Kotlin 2.0.0より前は、Kotlin Multiplatformプロジェクトでexpected/actual宣言を使用する場合、同じ可視性レベルである必要がありました。Kotlin 2.0.0では、異なる可視性レベルもサポートされるようになりましたが、それは実際の宣言がexpected宣言よりも_許容度が高い_場合に限られます。例:
expect internal class Attribute // Visibility is internal
actual class Attribute // Visibility is public by default,
// which is more permissive
同様に、実際の宣言で型エイリアスを使用している場合、基になる型の可視性は、expected宣言と同じか、より許容度が高い必要があります。例:
expect internal class Attribute // Visibility is internal
internal actual typealias Attribute = Expanded
class Expanded // Visibility is public by default,
// which is more permissive
Kotlin K2コンパイラーを有効にする方法
Kotlin 2.0.0以降、Kotlin K2コンパイラーはデフォルトで有効になっています。
Kotlinバージョンをアップグレードするには、GradleおよびMavenのビルドスクリプトで、バージョンを2.0.0以降のリリースに変更します。
IntelliJ IDEAまたはAndroid Studioで最高の体験を得るには、IDEでK2モードを有効化することを検討してください。
GradleでKotlinビルドレポートを使用する
Kotlinのビルドレポートは、Kotlinコンパイラータスクの異なるコンパイルフェーズで費やされた時間、使用されたコンパイラーとKotlinのバージョン、およびコンパイルが増分であったかどうかに関する情報を提供します。これらのビルドレポートは、ビルドパフォーマンスの評価に役立ちます。これらはすべてのGradleタスクのパフォーマンスの概要を提供するため、GradleビルドスキャンよりもKotlinコンパイルパイプラインに関するより多くの洞察を提供します。
ビルドレポートを有効にする方法
ビルドレポートを有効にするには、gradle.properties
ファイルでビルドレポートの出力先を宣言します。
kotlin.build.report.output=file
出力には以下の値とその組み合わせが利用可能です。
| オプション | 説明 + Translation in natural Japanese for a developer audience.
As the Kotlin language and ecosystem have continued to evolve, so has the Kotlin compiler. The first step was the
introduction of the new JVM and JS IR (Intermediate Representation) backends that share logic, simplifying code generation
for targets on different platforms. Now, the next stage of its evolution brings a new frontend known as K2.
{width=700}
With the arrival of the K2 compiler, the Kotlin frontend has been completely rewritten and features a new,
more efficient architecture. The fundamental change the new compiler brings is the use of one unified data structure that
contains more semantic information. This frontend is responsible for performing semantic analysis, call resolution,
and type inference.
The new architecture and enriched data structure enables the K2 compiler to provide the following benefits:
* **Improved call resolution and type inference**. The compiler behaves more consistently and understands your code better.
* **Easier introduction of syntactic sugar for new language features**. In the future, you'll be able to use more concise,
readable code when new features are introduced.
* **Faster compilation times**. Compilation times can be [significantly faster](#performance-improvements).
* **Enhanced IDE performance**. Starting with 2025.1, IntelliJ IDEA uses K2 mode to analyze your Kotlin code, increasing
stability and providing performance improvements. For more information, see [Support in IDEs](#support-in-ides).
This guide:
* Explains the benefits of the new K2 compiler.
* Highlights changes you might encounter during migration and how to adapt your code accordingly.
* Describes how you can roll back to the previous version.
::: note
The new K2 compiler is enabled by default starting with 2.0.0. For more information on the new features provided
in Kotlin 2.0.0, as well as the new K2 compiler, see [What's new in Kotlin 2.0.0](whatsnew20.md).
:::
## Performance improvements
To evaluate the performance of the K2 compiler, we ran performance tests on two open-source projects: [Anki-Android](https://github.com/ankidroid/Anki-Android)
and [Exposed](https://github.com/JetBrains/Exposed). Here are the key performance improvements that we found:
* The K2 compiler brings up to 94% compilation speed gains. For example, in the Anki-Android project, clean build times
were reduced from 57.7 seconds in Kotlin 1.9.23 to 29.7 seconds in Kotlin 2.0.0.
* The initialization phase is up to 488% faster with the K2 compiler. For example, in the Anki-Android project, the
initialization phase for incremental builds was cut from 0.126 seconds in Kotlin 1.9.23 to just 0.022 seconds in Kotlin 2.0.0.
* The Kotlin K2 compiler is up to 376% quicker in the analysis phase compared to the previous compiler. For example,
in the Anki-Android project, analysis times for incremental builds were slashed from 0.581 seconds in Kotlin 1.9.23 to
only 0.122 seconds in Kotlin 2.0.0.
For more details on these improvements and to learn more about how we analyzed the performance of the K2 compiler, see our
[blog post](https://blog.jetbrains.com/kotlin/2024/04/k2-compiler-performance-benchmarks-and-how-to-measure-them-on-your-projects/).
## Language feature improvements
The Kotlin K2 compiler improves language features related to [smart-casting](#smart-casts) and [Kotlin Multiplatform](#kotlin-multiplatform).
### Smart casts
The Kotlin compiler can automatically cast an object to a type in specific cases,
saving you the trouble of having to explicitly specify it yourself. This is called [smart-casting](typecasts.md#smart-casts).
The Kotlin K2 compiler now performs smart casts in even more scenarios than before.
In Kotlin 2.0.0, we've made improvements related to smart casts in the following areas:
* [Local variables and further scopes](#local-variables-and-further-scopes)
* [Type checks with the logical `or` operator](#type-checks-with-the-logical-or-operator)
* [Inline functions](#inline-functions)
* [Properties with function types](#properties-with-function-types)
* [Exception handling](#exception-handling)
* [Increment and decrement operators](#increment-and-decrement-operators)
#### Local variables and further scopes
Previously, if a variable was evaluated as not `null` within an `if` condition, the variable would be smart-cast.
Information about this variable would then be shared further within the scope of the `if` block.
However, if you declared the variable **outside** the `if` condition, no information about the variable would be available
within the `if` condition, so it couldn't be smart-cast. This behavior was also seen with `when` expressions and `while` loops.
From Kotlin 2.0.0, if you declare a variable before using it in your `if`, `when`, or `while` condition, then any
information collected by the compiler about the variable will be accessible in the corresponding block for
smart-casting.
This can be useful when you want to do things like extract boolean conditions into variables. Then, you can give the
variable a meaningful name, which will improve your code readability and make it possible to reuse the variable later
in your code. For example:
```kotlin
class Cat {
fun purr() {
println("Purr purr")
}
}
fun petAnimal(animal: Any) {
val isCat = animal is Cat
if (isCat) {
// In Kotlin 2.0.0, the compiler can access
// information about isCat, so it knows that
// animal was smart-cast to the type Cat.
// Therefore, the purr() function can be called.
// In Kotlin 1.9.20, the compiler doesn't know
// about the smart cast, so calling the purr()
// function triggers an error.
animal.purr()
}
}
fun main(){
val kitty = Cat()
petAnimal(kitty)
// Purr purr
}
Type checks with the logical or operator
In Kotlin 2.0.0, if you combine type checks for objects with an or
operator (||
), a smart cast is made to their closest common supertype. Before this change, a smart cast was always made to the Any
type.
In this case, you still had to manually check the object type afterward before you could access any of its properties or call its functions. For example:
interface Status {
fun signal() {}
}
interface Ok : Status
interface Postponed : Status
interface Declined : Status
fun signalCheck(signalStatus: Any) {
if (signalStatus is Postponed || signalStatus is Declined) {
// signalStatus is smart-cast to a common supertype Status
signalStatus.signal()
// Prior to Kotlin 2.0.0, signalStatus is smart cast
// to type Any, so calling the signal() function triggered an
// Unresolved reference error. The signal() function can only
// be called successfully after another type check:
// check(signalStatus is Status)
// signalStatus.signal()
}
}
NOTE
The common supertype is an approximation of a union type. Union types
Inline functions
In Kotlin 2.0.0, the K2 compiler treats inline functions differently, allowing it to determine in combination with other compiler analyses whether it's safe to smart-cast.
Specifically, inline functions are now treated as having an implicit callsInPlace
contract. This means that any lambda functions passed to an inline function are called in place. Since lambda functions are called in place, the compiler knows that a lambda function can't leak references to any variables contained within its function body.
The compiler uses this knowledge along with other compiler analyses to decide whether it's safe to smart-cast any of the captured variables. For example:
interface Processor {
fun process()
}
inline fun inlineAction(f: () -> Unit) = f()
fun nextProcessor(): Processor? = null
fun runProcessor(): Processor? {
var processor: Processor? = null
inlineAction {
// In Kotlin 2.0.0, the compiler knows that processor
// is a local variable and inlineAction() is an inline function, so
// references to processor can't be leaked. Therefore, it's safe
// to smart-cast processor.
// If processor isn't null, processor is smart-cast
if (processor != null) {
// The compiler knows that processor isn't null, so no safe call
// is needed
processor.process()
// In Kotlin 1.9.20, you have to perform a safe call:
// processor?.process()
}
processor = nextProcessor()
}
return processor
}
Properties with function types
In previous versions of Kotlin, there was a bug that meant that class properties with a function type weren't smart-cast. We fixed this behavior in Kotlin 2.0.0 and the K2 compiler. For example:
class Holder(val provider: (() -> Unit)?) {
fun process() {
// In Kotlin 2.0.0, if provider isn't null,
// it is smart-cast
if (provider != null) {
// The compiler knows that provider isn't null
provider()
// In 1.9.20, the compiler doesn't know that provider isn't
// null, so it triggers an error:
// Reference has a nullable type '(() -> Unit)?', use explicit '?.invoke()' to make a function-like call instead
}
}
}
This change also applies if you overload your invoke
operator. For example:
interface Provider {
operator fun invoke()
}
interface Processor : () -> String
class Holder(val provider: Provider?, val processor: Processor?) {
fun process() {
if (provider != null) {
provider()
// In 1.9.20, the compiler triggers an error:
// Reference has a nullable type 'Provider?', use explicit '?.invoke()' to make a function-like call instead
}
}
}
Exception handling
In Kotlin 2.0.0, we've made improvements to exception handling so that smart cast information can be passed on to catch
and finally
blocks. This change makes your code safer as the compiler keeps track of whether your object has a nullable type. For example:
fun testString() {
var stringInput: String? = null
// stringInput is smart-cast to String type
stringInput = ""
try {
// The compiler knows that stringInput isn't null
println(stringInput.length)
// 0
// The compiler rejects previous smart cast information for
// stringInput. Now stringInput has the String? type.
stringInput = null
// Trigger an exception
if (2 > 1) throw Exception()
stringInput = ""
} catch (exception: Exception) {
// In Kotlin 2.0.0, the compiler knows stringInput
// can be null, so stringInput stays nullable.
println(stringInput?.length)
// null
// In Kotlin 1.9.20, the compiler says that a safe call isn't
// needed, but this is incorrect.
}
}
fun main() {
testString()
}
Increment and decrement operators
Prior to Kotlin 2.0.0, the compiler didn't understand that the type of an object can change after using an increment or decrement operator. As the compiler couldn't accurately track the object type, your code could lead to unresolved reference errors. In Kotlin 2.0.0, this has been fixed:
interface Rho {
operator fun inc(): Sigma = TODO()
}
interface Sigma : Rho {
fun sigma() = Unit
}
interface Tau {
fun tau() = Unit
}
fun main(input: Rho) {
var unknownObject: Rho = input
// Check if unknownObject inherits from the Tau interface
// Note, it's possible that unknownObject inherits from both
// Rho and Tau interfaces.
if (unknownObject is Tau) {
// Use the overloaded inc() operator from interface Rho.
// In Kotlin 2.0.0, the type of unknownObject is smart-cast to
// Sigma.
++unknownObject
// In Kotlin 2.0.0, the compiler knows unknownObject has type
// Sigma, so the sigma() function can be called successfully.
unknownObject.sigma()
// In Kotlin 1.9.20, the compiler doesn't perform a smart cast
// when inc() is called so the compiler still thinks that
// unknownObject has type Tau. Calling the sigma() function
// throws a compile-time error.
// In Kotlin 2.0.0, the compiler knows unknownObject has type
// Sigma, so calling the tau() function throws a compile-time
// error.
unknownObject.tau()
// Unresolved reference 'tau'
// In Kotlin 1.9.20, since the compiler mistakenly thinks that
// unknownObject has type Tau, the tau() function can be called,
// but it throws a ClassCastException.
}
}
Kotlin Multiplatform
K2コンパイラーには、Kotlin Multiplatformに関連して以下の領域で改善点があります。
コンパイル時の共通ソースとプラットフォームソースの分離
以前は、Kotlinコンパイラーの設計により、コンパイル時に共通ソースセットとプラットフォームソースセットを分離することができませんでした。その結果、共通コードがプラットフォームコードにアクセスでき、プラットフォーム間で異なる動作を引き起こしていました。さらに、共通コードからのいくつかのコンパイラー設定や依存関係がプラットフォームコードに漏洩していました。
Kotlin 2.0.0では、新しいKotlin K2コンパイラーの実装に、共通ソースセットとプラットフォームソースセット間の厳密な分離を確実にするためのコンパイルスキームの再設計が含まれました。この変更は、expected/actual関数を使用する際に最も顕著です。以前は、共通コード内の関数呼び出しがプラットフォームコード内の関数に解決されることが可能でした。例:
共通コード | プラットフォームコード |
kotlin
| kotlin
|
この例では、共通コードは実行されるプラットフォームに応じて異なる動作をします。
- JVMプラットフォームでは、共通コード内の
foo()
関数を呼び出すと、プラットフォームコードのfoo()
関数がplatform foo
として呼び出されます。 - JavaScriptプラットフォームでは、プラットフォームコードにそのような関数がないため、共通コード内の
foo()
関数を呼び出すと、共通コードのfoo()
関数がcommon foo
として呼び出されます。
Kotlin 2.0.0では、共通コードはプラットフォームコードにアクセスできないため、両方のプラットフォームでfoo()
関数は共通コードのfoo()
関数 (common foo
) に正常に解決されます。
プラットフォーム間の動作の一貫性が向上したことに加え、IntelliJ IDEAまたはAndroid Studioとコンパイラーの間で動作の競合があったケースの修正にも尽力しました。例えば、expected/actualクラスを使用した場合、以下のようになります。
共通コード | プラットフォームコード |
kotlin
| kotlin
|
この例では、expectedクラスIdentity
にはデフォルトコンストラクターがないため、共通コードでは正常に呼び出せません。以前は、エラーはIDEによってのみ報告されていましたが、JVM上ではコードは正常にコンパイルされていました。しかし、現在はコンパイラーが正しくエラーを報告します。
Expected class 'expect class Identity : Any' does not have default constructor
解決動作が変化しない場合
私たちはまだ新しいコンパイルスキームへの移行の過程にあるため、同じソースセット内にない関数を呼び出す際の解決動作は以前と同じです。この違いは、主に共通コードでマルチプラットフォームライブラリからのオーバーロードを使用する場合に顕著になります。
異なるシグネチャを持つ2つのwhichFun()
関数があるライブラリがあるとします。
// Example library
// MODULE: common
fun whichFun(x: Any) = println("common function")
// MODULE: JVM
fun whichFun(x: Int) = println("platform function")
共通コードでwhichFun()
関数を呼び出すと、ライブラリ内で最も関連性の高い引数型を持つ関数が解決されます。
// A project that uses the example library for the JVM target
// MODULE: common
fun main(){
whichFun(2)
// platform function
}
比較として、同じソースセット内でwhichFun()
のオーバーロードを宣言した場合、コードがプラットフォーム固有のバージョンにアクセスできないため、共通コードの関数が解決されます。
// Example library isn't used
// MODULE: common
fun whichFun(x: Any) = println("common function")
fun main(){
whichFun(2)
// common function
}
// MODULE: JVM
fun whichFun(x: Int) = println("platform function")
マルチプラットフォームライブラリと同様に、commonTest
モジュールは別のソースセットにあるため、プラットフォーム固有のコードにも引き続きアクセスできます。したがって、commonTest
モジュール内の関数呼び出しの解決は、古いコンパイルスキームと同じ動作を示します。
将来的に、これらの残りのケースも新しいコンパイルスキームとより一貫性を持つようになるでしょう。
expected/actual宣言の異なる可視性レベル
Kotlin 2.0.0より前は、Kotlin Multiplatformプロジェクトでexpected/actual宣言を使用する場合、同じ可視性レベルである必要がありました。Kotlin 2.0.0では、異なる可視性レベルもサポートされるようになりましたが、それは実際の宣言がexpected宣言よりも_許容度が高い_場合に限られます。例:
expect internal class Attribute // Visibility is internal
actual class Attribute // Visibility is public by default,
// which is more permissive
同様に、実際の宣言で型エイリアスを使用している場合、基になる型の可視性は、expected宣言と同じか、より許容度が高い必要があります。例:
expect internal class Attribute // Visibility is internal
internal actual typealias Attribute = Expanded
class Expanded // Visibility is public by default,
// which is more permissive
Kotlin K2コンパイラーを有効にする方法
Kotlin 2.0.0以降、Kotlin K2コンパイラーはデフォルトで有効になっています。
Kotlinバージョンをアップグレードするには、GradleおよびMavenのビルドスクリプトで、バージョンを2.0.0以降のリリースに変更します。
IntelliJ IDEAまたはAndroid Studioで最高の体験を得るには、IDEでK2モードを有効化することを検討してください。
GradleでKotlinビルドレポートを使用する
Kotlinのビルドレポートは、Kotlinコンパイラータスクの異なるコンパイルフェーズで費やされた時間、使用されたコンパイラーとKotlinのバージョン、およびコンパイルが増分であったかどうかに関する情報を提供します。これらのビルドレポートは、ビルドパフォーマンスの評価に役立ちます。これらはすべてのGradleタスクのパフォーマンスの概要を提供するため、GradleビルドスキャンよりもKotlinコンパイルパイプラインに関するより多くの洞察を提供します。
ビルドレポートを有効にする方法
ビルドレポートを有効にするには、gradle.properties
ファイルでビルドレポートの出力先を宣言します。
kotlin.build.report.output=file
出力には以下の値とその組み合わせが利用可能です。
| オプション | 説明 + Translation in natural Japanese for a developer audience.
[//]: # (title: K2コンパイラー移行ガイド)
Kotlin言語とエコシステムが進化し続けるにつれて、Kotlinコンパイラーも進化してきました。最初のステップは、ロジックを共有し、異なるプラットフォーム上のターゲットのコード生成を簡素化する新しいJVMおよびJS IR (中間表現) バックエンドの導入でした。そして、その進化の次の段階として、K2として知られる新しいフロントエンドが導入されます。
{width=700}
K2コンパイラーの登場により、Kotlinのフロントエンドは完全に書き直され、より効率的な新しいアーキテクチャが採用されました。新しいコンパイラーがもたらす根本的な変更は、より多くのセマンティック情報を含む統一されたデータ構造を使用することです。このフロントエンドは、セマンティック解析、呼び出し解決、および型推論を実行する役割を担います。
新しいアーキテクチャと強化されたデータ構造により、K2コンパイラーは以下の利点を提供します。
* **呼び出し解決と型推論の改善。** コンパイラーの動作はより一貫性があり、コードをよりよく理解します。
* **新しい言語機能のシンタックスシュガーの導入が容易に。** 今後、新しい機能が導入された際に、より簡潔で読みやすいコードを使用できるようになります。
* **コンパイル時間の高速化。** コンパイル時間は[大幅に高速化](#performance-improvements)されます。
* **IDEパフォーマンスの向上。** 2025.1以降、IntelliJ IDEAはK2モードを使用してKotlinコードを解析し、安定性を高め、パフォーマンスを向上させます。詳細については、[IDEでのサポート](#support-in-ides)を参照してください。
このガイドでは、以下の内容を説明します。
* 新しいK2コンパイラーの利点。
* 移行中に遭遇する可能性のある変更点と、それに応じてコードを適応させる方法。
* 以前のバージョンにロールバックする方法。
::: note
K2コンパイラーは2.0.0からデフォルトで有効になっています。Kotlin 2.0.0 で提供される新機能および新しいK2コンパイラーの詳細については、[Kotlin 2.0.0 の新機能](whatsnew20.md)を参照してください。
:::
## パフォーマンスの改善
K2コンパイラーのパフォーマンスを評価するため、[Anki-Android](https://github.com/ankidroid/Anki-Android)と[Exposed](https://github.com/JetBrains/Exposed)という2つのオープンソースプロジェクトでパフォーマンステストを実施しました。以下に、私たちが発見した主なパフォーマンス改善点を示します。
* K2コンパイラーは、最大94%のコンパイル速度向上をもたらします。例えば、Anki-Androidプロジェクトでは、クリーンビルド時間がKotlin 1.9.23での57.7秒からKotlin 2.0.0での29.7秒に短縮されました。
* K2コンパイラーでは、初期化フェーズが最大488%高速化されます。例えば、Anki-Androidプロジェクトでは、増分ビルドの初期化フェーズがKotlin 1.9.23での0.126秒からKotlin 2.0.0でのわずか0.022秒に短縮されました。
* Kotlin K2コンパイラーは、以前のコンパイラーと比較して、解析フェーズで最大376%高速です。例えば、Anki-Androidプロジェクトでは、増分ビルドの解析時間がKotlin 1.9.23での0.581秒からKotlin 2.0.0でのわずか0.122秒に削減されました。
これらの改善の詳細と、K2コンパイラーのパフォーマンスをどのように分析したかについては、[ブログ投稿](https://blog.jetbrains.com/kotlin/2024/04/k2-compiler-performance-benchmarks-and-how-to-measure-them-on-your-projects/)を参照してください。
## 言語機能の改善
Kotlin K2コンパイラーは、[スマートキャスト](#smart-casts)と[Kotlin Multiplatform](#kotlin-multiplatform)に関連する言語機能を改善します。
### スマートキャスト
Kotlinコンパイラーは、特定の場合にオブジェクトを型に自動的にキャストできるため、明示的に指定する手間が省けます。これは[スマートキャスト](typecasts.md#smart-casts)と呼ばれます。Kotlin K2コンパイラーは、以前よりもさらに多くのシナリオでスマートキャストを実行できるようになりました。
Kotlin 2.0.0では、スマートキャストに関連する以下の領域で改善を行いました。
* [ローカル変数とそれ以降のスコープ](#local-variables-and-further-scopes)
* [論理OR演算子による型チェック](#type-checks-with-the-logical-or-operator)
* [インライン関数](#inline-functions)
* [関数型を持つプロパティ](#properties-with-function-types)
* [例外処理](#exception-handling)
* [インクリメントおよびデクリメント演算子](#increment-and-decrement-operators)
#### ローカル変数とそれ以降のスコープ
以前は、`if`条件内で変数が`null`でないと評価された場合、その変数はスマートキャストされていました。この変数に関する情報は、`if`ブロックのスコープ内でさらに共有されていました。
しかし、変数を`if`条件の**外側**で宣言した場合、その変数に関する情報は`if`条件内では利用できないため、スマートキャストできませんでした。この動作は、`when`式や`while`ループでも見られました。
Kotlin 2.0.0以降では、`if`、`when`、または`while`条件で変数を使用する前に宣言した場合、コンパイラーがその変数に関して収集した情報は、スマートキャストのために対応するブロック内でアクセス可能になります。
これは、ブール条件を変数に抽出するなどの場合に役立ちます。そうすることで、変数に意味のある名前を付けられ、コードの可読性が向上し、後でコード内で変数を再利用できるようになります。例:
```kotlin
class Cat {
fun purr() {
println("Purr purr")
}
}
fun petAnimal(animal: Any) {
val isCat = animal is Cat
if (isCat) {
// In Kotlin 2.0.0, the compiler can access
// information about isCat, so it knows that
// animal was smart-cast to the type Cat.
// Therefore, the purr() function can be called.
// In Kotlin 1.9.20, the compiler doesn't know
// about the smart cast, so calling the purr()
// function triggers an error.
animal.purr()
}
}
fun main(){
val kitty = Cat()
petAnimal(kitty)
// Purr purr
}
論理OR演算子による型チェック
Kotlin 2.0.0では、オブジェクトの型チェックをor
演算子 (||
) と組み合わせた場合、最も近い共通のスーパータイプにスマートキャストされます。この変更以前は、スマートキャストは常にAny
型に対して行われていました。
この場合、そのオブジェクトのプロパティにアクセスしたり、関数を呼び出したりする前に、手動でオブジェクトの型をチェックする必要がありました。例:
interface Status {
fun signal() {}
}
interface Ok : Status
interface Postponed : Status
interface Declined : Status
fun signalCheck(signalStatus: Any) {
if (signalStatus is Postponed || signalStatus is Declined) {
// signalStatus is smart-cast to a common supertype Status
signalStatus.signal()
// Prior to Kotlin 2.0.0, signalStatus is smart cast
// to type Any, so calling the signal() function triggered an
// Unresolved reference error. The signal() function can only
// be called successfully after another type check:
// check(signalStatus is Status)
// signalStatus.signal()
}
}
NOTE
共通のスーパータイプはユニオン型の近似です。ユニオン型はKotlinでは現在サポートされていません。
インライン関数
Kotlin 2.0.0では、K2コンパイラーはインライン関数を異なる方法で扱い、他のコンパイラー解析と組み合わせてスマートキャストが安全であるかどうかを判断できます。
具体的には、インライン関数は暗黙的なcallsInPlace
コントラクトを持つものとして扱われるようになりました。これは、インライン関数に渡されるラムダ関数がインプレースで呼び出されることを意味します。ラムダ関数がインプレースで呼び出されるため、コンパイラーはラムダ関数がその関数本体内に含まれる変数への参照をリークできないことを認識します。
コンパイラーは、この知識を他のコンパイラー解析とともに使用して、キャプチャされた変数にスマートキャストすることが安全であるかどうかを判断します。例:
interface Processor {
fun process()
}
inline fun inlineAction(f: () -> Unit) = f()
fun nextProcessor(): Processor? = null
fun runProcessor(): Processor? {
var processor: Processor? = null
inlineAction {
// In Kotlin 2.0.0, the compiler knows that processor
// is a local variable and inlineAction() is an inline function, so
// references to processor can't be leaked. Therefore, it's safe
// to smart-cast processor.
// If processor isn't null, processor is smart-cast
if (processor != null) {
// The compiler knows that processor isn't null, so no safe call
// is needed
processor.process()
// In Kotlin 1.9.20, you have to perform a safe call:
// processor?.process()
}
processor = nextProcessor()
}
return processor
}
関数型を持つプロパティ
以前のバージョンのKotlinでは、関数型を持つクラスプロパティがスマートキャストされないというバグがありました。この動作はKotlin 2.0.0とK2コンパイラーで修正されました。例:
class Holder(val provider: (() -> Unit)?) {
fun process() {
// In Kotlin 2.0.0, if provider isn't null,
// it is smart-cast
if (provider != null) {
// The compiler knows that provider isn't null
provider()
// In 1.9.20, the compiler doesn't know that provider isn't
// null, so it triggers an error:
// Reference has a nullable type '(() -> Unit)?', use explicit '?.invoke()' to make a function-like call instead
}
}
}
この変更は、invoke
演算子をオーバーロードした場合にも適用されます。例:
interface Provider {
operator fun invoke()
}
interface Processor : () -> String
class Holder(val provider: Provider?, val processor: Processor?) {
fun process() {
if (provider != null) {
provider()
// In 1.9.20, the compiler triggers an error:
// Reference has a nullable type 'Provider?', use explicit '?.invoke()' to make a function-like call instead
}
}