19
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

K2 compiler migration guideを読む

Last updated at Posted at 2024-05-04

K2 compiler migration guideが公開された

K2 compilerになってどうなる

  • K2 compilerになってfrontend compilerがKotlinで書き直された
  • frontend compilerはsemantic analysis(セマンティック解析), call resolution(呼び出し解決), and type inference(型推論)を担当している
  • compilerがコードを解析していざなにかをしようとするまでの時間が早くなるということ
  • 以下ざーっと読んだ内容
  • 解釈が間違っていれば指摘してください

Changes

Immediate initialization of open properties with backing fields

  • openなpropertyの即時初期化が必要になる
  • openなpropertyは基本的に初期値を与えるのが推奨されるが、どうしても初期化したくない場合はfinalを付与したpropertyする
open class Base {
    open val a: Int
    open var b: Int

    init {
        // Kotlin 1.9ではOK、Kotlin 2ではエラー
        this.a = 1
        // Kotln 1.9でもエラー
        this.b = 1
    }
}

class Derived : Base() {
    override val a: Int = 2
    override var b = 2
}

Deprecated synthetic setters on a projected receiver

  • Genericsを取るJavaのsetter関数を異なる型で呼び出すとエラーになるようになる
public class Container<E> {
    public E getFoo() {
        return null;
    }
    public void setFoo(E foo) {}
}
fun exampleFunction(starProjected: Container<*>, inProjected: Container<in Number>, sampleString: String) {
    // Kotlin 1.9以前もエラー
    // Nothing型を要求するのでエラーになる
    starProjected.setFoo(sampleString)
    
    // `foo`は`setFoo()`に解決される, Nothing型を要求するが型不一致でもエラーにならず実行時にエラーとなる
    // Kotlin 2からエラーになる
    starProjected.foo = sampleString

    // Kotlin 1.9以前もエラー
    // 数値型を要求するのでエラーになる
    inProjected.setFoo(sampleString)

    // `foo`は`setFoo()`に解決される, 数値型を要求するが型不一致でもエラーにならず実行時にエラーとなる
    // Kotlin 2からエラーになる
    inProjected.foo = sampleString
}

Forbidden function calls with inaccessible types

  • 解決できない型パラメータを持つラムダ関数をもつ関数の呼び出し、一部のGenericsがコンパイルエラーになるようになった
// module one
class Some(val x: Int)

// module two
// build.gradleでimplementation(module.one)している

// module twoはSome型にアクセスできるため両方の関数はコンパイルできる
fun foo(f: (Some, String) -> Unit) {}
fun bar(f: (Some) -> Unit) {}

class Generic<T>

fun gen() = Generic<Some<String>>()
fun takeString(g: Generic<Some<String>>) {}

// module three
// build.gradleでimplementation(module.two)している
fun test() {
    // unused propertyがmodule threeではアクセスできないSome型のためKotlin 2ではエラーになる
    foo { _, _ -> }

    // module threeではアクセスできないSome型をとるためwarningになる
    foo { some, str -> }

    // Genericsで隠蔽されているためKotlin 2ではwarningになる
    val z = gen()

    // module threeでアクセスできないSome型の引数をとるためKotlin 2ではエラーになる
    takeString(z)
}

Consistent resolution order of Kotlin properties and Java fields with the same name

  • Javaクラスを継承したKotlinクラスで同名のプロパティを持つ際の解決が一貫性を持つようになった
  • 基本的にサブクラスのpropertyが優先される
  • Kotlinクラスを継承したJavaクラスも同様にサブクラスのプロパティが優先される
public class Base {
    public String a = "a";
    public String b = "b";
}
class Derived : Base() {
    val a = "aa"
    val b
      get() = "bb"
}

fun main() {
    val derived = Derived()
    // Derived.aに解決される
    println(derived.a)

    // カスタムゲッターの挙動が変わる
    // Kotlin 1.9以前 Base.bに解決される -> b
    // Kotlin 2 Derived.bに解決される -> bb
    println(derived.b)
}

Improved null safety for Java primitive arrays

  • Kotlin 2からTYPE_USEなannotationが付与されているJava配列(Array)のnullabilityが正しく推論されるようになった
  • TYPE_USEなannotation以外はKotlin 2でも変更はない
interface DataProvider {
    int @Nullable [] fetchData();
}
val dataService: DataProvider = object : DataProvider {}

val data = dataService.fetchData()

// Kotlin 1.9以前
// dataはIntArrayに解決される
// 呼び出し時にNPEが発生する可能性がある
data[0]

// Kotlin 2
// dataはIntArray?に解決される
data?.get(0)

Smart cast improvements

Local variables and further scopes

  • if, when外で判定した条件がblock内で判定されるようになる
  • instanceの型がなんなのかというのを意味にある名前で変数とできる
class Cat {
    fun purr() {
        println("Purr purr")
    }
}

fun petAnimal(animal: Any) {
    val isCat = animal is Cat
    if (isCat) {
        // Kotlin 1.9以前: すでに判定済みでもasやcheckで型を指定しないと呼べない
        (animas as Cat).purr()
        
        // Kotlin 2.0: if外の条件についてもfrontend compilerが考慮してくれる
        animal.purr()
    }
}

Type checks with the logical or operator

  • or(||)の条件について正確に読み取ってくれるようになる
interface Status {
    fun signal() {}
}

interface Ok : Status
interface Postponed : Status
interface Declined : Status

fun signalCheck(signalStatus: Any) {
    if (signalStatus is Postponed || signalStatus is Declined) {
        // Kotlin 1.9以前: asやcheckで型を指定しないと呼べない、かつasやcheckを行ったあとの行はStatusとしか認識できない
        (signalStatus as Status).signal()

        // Kotlin 2.0: Status interfaceであることは明示的なため呼べる
        signalStatus.signal()
    }
}

Inline functions

  • inline関数内の変数観測が正しくなり余計なnullabilityを気にする必要がなくなった
interface Processor {
    fun process()
}

inline fun inlineAction(f: () -> Unit) = f()

fun nextProcessor(): Processor? = null

fun runProcessor(): Processor? {
    var processor: Processor? = null
    inlineAction {
        if (processor != null) {
            // Kotlin 1.9以前:
            processor?.process()
            
            // Kotlin 2.0: 展開された関数中ではすでにnullチェック済みなのでそのまま呼べる
            processor.process()
        }

        processor = nextProcessor()
    }

    return processor
}

Properties with function types

interface Provider {
    operator fun invoke()
}

interface Processor : () -> String

class Holder(val provider: Provider?, val processor: Processor?) {
    fun process() {
        if (provider != null) {
            // Kotlin 1.9以前: invokeを呼ぶ必要がある
            provider.invoke()

            // Kotlin 2.0: nullチェック済みなので直接operator functionを呼べる
            provider()
        }
        if (processor != null) {
            // Kotlin 1.9以前: invokeを呼ぶ必要がある
            processor.invoke()

            // Kotlin 2.0: nullチェック済みなので直接operator functionを呼べる
            processor()
        }
    }
}

Exception handling

  • catchfinallyの型推論が向上した
fun testString() {
    var stringInput: String? = null
    stringInput = ""
    try {
        // 文字列代入後なのでcompilerはnon-null判定
        println(stringInput.length)

        stringInput = null

        // 処理途中でexceptionを投げる
        if (2 > 1) throw Exception()
        stringInput = ""
    } catch (exception: Exception) {
        // Kotlin 1.9以前: try句の前に文字列を代入しているのでStringでcatch句を書く(NullPointExceptionの可能性がある)
        // Kotlin 2.0: stringInputがnullの可能性があるためString?でcatch句を書く
        println(stringInput?.length)
    }
}

Increment and decrement operators

  • inc, decメソッドの型推論バグが修正された
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
    if (unknownObject is Tau) {

        // incrementでSigma型になる
        ++unknownObject

        // Kotlin 1.9以前: unresolved reference
        // Kotlin 2.0.0: callable
        unknownObject.sigma()

        // Kotlin 1.9以前: callableだが呼び出すとエラーになる
        // Kotlin 2.0.0: 既にTau型でなくなっているのでunresolved reference
        unknownObject.tau()
    }
}

Kotlin Multiplatform improvements

Separation of common and platform sources during compilation

  • KMPは共通で使用するcommonコードと各platformで使用するplatformコードをそれぞれ別に記述することができるため同名・同引数の関数を作成できる
  • basic-project-structure-2.png
  • Kotlin 1.9以前ではcompile時にcommonコードとplatformコードをごっちゃにして取り扱っていた
  • Kotlin 2.0ではcommonコードとplatformコードがisolateされる
  • 同名関数をcommonコードとplatformコードを定義し呼び出した場合の挙動が変更される
  • ただし引数の型が異なる関数を定義するなどoverloadを使用する場合は以前の挙動になったりするところもある

表. 同名関数をcommonコードとplatformコードを定義し呼び出した場合の挙動

--- Kotlin 1.9以前 Kotlin 2.0
JVM JVM platformコード commonコード
JVM以外 commonコード commonコード

Different visibility levels of expected and actual declarations

  • expect/actualはplatformごとの実装を分けるための仕組み
  • Kotlin 1.9以前ではexpect/actualでそれぞれ定義したものは同一のvisibilityを設定する必要があった
  • Kotlin 2.0ではstrictでない方向性(見える範囲が広がる方向)への変更は許容されるようになる
expect internal class Attribute
// internal -> publicなのでOK
actual class Attribute

// actualでtypealiasを使うときはvisibility設定はstrictでないようにならなければならない
expect internal class Attribute
internal actual typealias Attribute = Expanded
class Expanded

Compiler plugins support

  • 公式のpluginは大体対応されている
  • kaptも対応するらしい
  • compose, KSPももちろん対応進んでいる

Tasks error in Gradle configuration cache

  • Gradle configuration cacheはプロジェクトの設定値を使い回すことで2回目以降のタスク実行を爆速にする仕組み
  • 2.0.0-Beta4からinvocation of Task.project at execution time is unsupportedというconfiguration cacheのエラーが出ていた
  • configuration cacheに対応していないタスクがあることで発生している
  • Gradleチームが解決に取り組み中

Support in IntelliJ IDEA

  • IntelliJ IDEA 2024.1から使える
  • Android StudioはKoalaから使える
  • Settings | Languages & Frameworks | Kotlin > Enable K2 Kotlin Modeのチェックボックスをいれる

Kotlin/JVM

Generate lambda functions using invokedynamic

  • invokedynamicがラムダ関数のインスタンス化のデフォルトになった
  • invokedynamicはKotlin 1.5でcompiler flag(-Xlambdas=indy)を設定すると使えた
  • 従来のラムダ関数の匿名クラスのインスタンス化よりバイナリサイズが減少する
  • invokedynamicは現状いくつかの制限がある
    • 生成された匿名クラスのインスタンスはシリアライズできない
    • reflect()メソッドが対応していない
    • toString()が読みづらい
  • @JvmSerializableLambdaをつけると個別に以前と同じ挙動にできる
  • -Xlambdas=classを設定すると全体を以前と同じ挙動にできる

The kotlinx-metadata-jvm library is now Stable

  • kotlinx-metadata-jvmが安定版になった
  • kotlinx-metadata-jvmは@Metadataを読み取ることで、kotlin-reflectなしで黒魔術を使いやすくするライブラリ
  • 安定版になったことでkotlin-metadata-jvm(x抜き)でartifactを取得できるようになった

Kotlin/Native

Resolving conflicts with Objective-C methods

  • Obj-Cメソッドの衝突が解消された
  • Obj-Cは同引数異名のメソッドを定義できるがKotlin上ではoverloadエラーが発生していた
  • Kotlin 2.0で@ObjCSignatureOverrideアノテーションが導入された
  • @ObjCSignatureOverrideを付与すると衝突するoverloadを無視するようになる

Changed log level for compiler arguments

  • K/Nのcompile, link, cinterpタスクのデフォルトログレベルがINFOからDEBUGに変更された

Kotlin/Wasm

Unsigned primitive types in functions with @JsExport

  • @JsExportを使ってexportした関数で符号無しのPrimitiveな型が使えるようになった

Binaryen available by default in production builds

  • BinaryenはWASMの最適化ライブラリ
  • プロダクションビルドで設定無しで使えるようになった
  • 開発ビルドでは適用されない

Generation of TypeScript declaration files in Kotlin/Wasm

  • @JsExportを付与した関数のTypeScript定義(.d.tsファイル)を生成できるようになった
  • kotlin {
      wasmJs {
        binaries.executable()
        browser {}
        generateTypeScriptDefinitions() // これを追加する
      }
    }
    

Support for named export

  • @JsExportを付与した関数が名前付きでexportされるようになった
@JsExport
fun add(a: Int, b: Int) = a + b
// Kotlin 1.9以前
import exports from "./index.mjs"

exports.add(10, 10);


// Kotlin 2.0
import { add } from "./index.mjs"

add(10, 10);

withWasm() function is split into JS and WASI variants

  • gradleのSourceSet設定時に使用するwithWasm()関数がDeprecatedになった
kotlin {
    applyHierarchyTemplate {
        // Deprecated
        withWasm()

        // Kotlin 2.0
        withWasmJs()
        withWasmWasi()
    }
}

New functionality to catch JavaScript exceptions

  • try-catchを使ってJavaScript側のExceptionをcatchできるようになった
  • ThrowableもしくはJsExceptionとしてcatchすることができる
  • catchはできるがcall stackなどの情報はまだ取得できない

The new exception handling proposal is now supported

  • Kotlin/WasmのWebAssembly例外処理のproposal実装が試せるようになった
  • compiler flagに-Xwasm-use-new-exception-proposalを設定すると使える
  • -Xwasm-use-traps-instead-of-exceptionsなど既存の機能とも互換性があるので一緒に使える

Kotlin/JS

New compilation target

  • Kotlin/JSのcompile target設定方法が新しくなった
  • この方法で設定するとES2015 classesとES2015 generatorsが有効になる
// Kotlin 1.9
kotlin {
    js(IR) {
        useEsModules() // Enables ES2015 modules
    }
}

tasks.withType<KotlinJsCompile>().configureEach {
    kotlinOptions {
        useEsClasses = true
    }
}


// Kotlin 2.0
kotlin {
    js {
        compilerOptions {
            target.set("es2015")
        }
    }
}

Suspend functions as ES2015 generators

Passing arguments to the main function

  • Main関数に引数を渡せるようになった
kotlin {
    js {
        binary.executable()
        // Deno.argsに引数が格納される
        passAsArgumentToMainFunction("Deno.args")

        // NodeJSの場合は`process.argv`に格納される
        nodejs {
            passProcessArgvToMainFunction()
        }
    }
}

Per-file compilation for Kotlin/JS projects

  • ktファイルごとにjsファイルを生成するオプションが追加された
  • Kotlin 1.9以前は1つのjsファイルにバンドルするかモジュール単位でjsファイルを生成するオプションがあった
  • モジュール単位でも大きいjsファイルになる可能性があるためKotlin 2以降はktファイルごとjsファイルを生成することができるようになった
  • ktファイルにexport宣言が含まれている場合はktファイルごとに2つのjsファイルが生成される
  • compiler optionに-Xir-per-fileを設定するかgradle.propertiesにkotlin.js.ir.output.granularity=per-fileを設定すると使える

Improved collection interoperability

  • KotlinのSet, Map, ListなどのCollectionをexportできるようになった
  • JavaScriptからKotlinのCollectionを作成することはまだできない
@JsExport
data class User(
    val name: String,
    val friends: List<User> = emptyList()
)

@JsExport
val me = User(
    name = "Me",
    firends = listOf(User(name = "Kodee"))
)
import { User, me, KtList } from "my-module"

const allMyFriendNames = me.friends
  .asJsReadonlyArrayView()
  .map(x => x.name)

Support for createInstance()

  • JVMのみで使用できたkotlin-reflectのcreateInstanceがKotlin/JSで使用できるようになった

Support for type-safe plain JavaScript objects

  • 型安全なJavaScriptオブジェクトがサポートされた
  • js()関数でJavaScriptオブジェクトを使用すると実行時エラーもしくは一歳動かないが@JsPlainObjectを付与したinterfaceを使用することでコンパイル時にエラーを検知できるようになる
  • js-plain-objects pluginを使用すると使える
  • js-plain-objects pluginはK2のみをサポートしている
  • js-plain-objectsはinvoke関数とcopy関数を追加する

Support for npm package manager

  • Kotlin 1.9まではYarnのみが使用できた
  • gradle.propertiesを設定するとNPMを使用することができる
kotlin.js.yarn=false

Gradle improvements

New Gradle DSL for compiler options in multiplatform projects

  • Multiplatformプロジェクトでcompiler optionを一括設定できるようになった
  • Kotlin 1.9以前ではTask, Compiler, SourceSetごとにしか設定できなかった
  • Projectレベル、Targetレベル、Taskレベルの順に適用される(そのためProjectレベルで設定したオプションをTargetレベル、Taskレベルで上書きできる)
kotlin {
    // Kotlin 2から使用できるようになった
    compilerOptions {
        allWarningsAsErrors.set(true)
    }
    jvm {
        compilerOptions {
            // `allWarningsAsErrors`と`noJdk`がJVMソースセットに適用される
            noJdk.set(true)
        }
    }

    tasks.named<KotlinJvmCompile>("compileKotlinJvm") {
        compilerOptions {
            // `allWarningsAsErrors`,`noJdk`,`verbose`がcompilerに適用される
            verbose = true
        }
    }
}

New Compose compiler Gradle plugin

  • Jetpack Compose ComiplerがKotlinリポジトリに移管された
  • Jetpack Compose compiler moving to the Kotlin repository
  • 今までは新しいKotlinがリリースされてもcompose comiplerが対応していないためcompose compilerが新しいKotlinに対応するまで待つ必要があったが待たなくてよくなる
before after
androidx.compose.compiler:compiler org.jetbrains.kotlin:kotlin-compose-compiler-plugin-embeddable
androidx.compose.compiler:compiler-hosted org.jetbrains.kotlin:kotlin-compose-compiler-plugin
// Kotlin 1.9
plugin {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
}

// android{}内でcomposeの設定を行う
android {
    buildFeatures {
        compose = true
    }
    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.13"
    }
}

// Kotlin 2以降
plugin {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    id("org.jetbrains.kotlin.plugin.compose")
}

// topレベルのcomposeCompiler{}内でcomposeの設定を行う
composeCompiler {
    enableStrongSkippingMode = true
}

New attribute to distinguish JVM and Android published libraries

  • JVMとandroid向けのライブラリを区別するattributeが全てのvariantで公開されるようになった
    • android, standard-jvm, no-jvmの3種類がある
  • JavaプロジェクトでJVM対応されているKMPライブラリを安全に使用できるようになった
  • gradle.propertiesにkotlin.publishJvmEnvironmentAttribute=falseを設定すると無効化できる

Improved Gradle dependency handling for CInteropProcess in Kotlin/Native

  • Kotlin/Nativeでcinteropを使用する際のdefFile生成タスク周りが整理された
  • Kotlin 1.9以前ではcinteropタスクで使用するdefFileが別のタスクの成果物であった場合、Gradleタスクの依存を設定して解決する必要があった
  • Kotlin 2ではGradleの新しいプロパティであるRegularFilePropertyを使用してdefFileを参照するようになった
  • RegularFilePropertyを使用するとGradleはプロパティを遅延評価するようになるため別タスクの実行を待った上でcinteropタスクが実行されるようになる
kotlin {
    macosArm64("native") {
        compilations.getByName("main") {
            // Kotlin 1.9
            cinterop.create("cinterop-library") {
                defFileProperty.set(createDefFileTask.flatMap { it.defFile.asFile })
                project.tasks.named(interopProcessingTaskName).configure {
                    dependsOn(createDefFileTask)
                }
            }

            // Kotlin 2
            cinterop.create("cinterop-library") {
                definitionFile.set(project.file("def-file.def"))
            }
        }
    }
}

Visibility changes in Gradle

  • Kotlin DSLを使用する際に適切にDSLを使用していない場合Warningログが表示されるようになった
kotlin {
    // ここで`KotlinTargetContainerWithPresetFunctions.jvm()`を呼ぶべき
    jvm()

    sourceSets {
        // `NamedDomainObjectContainer<KotlinSourceSet>.jvm()`は存在しないが`KotlinTargetContainerWithPresetFunctions.jvm()`を呼べてしまう
        // Kotlin 2以降はWarningが表示される
        jvm()
    }
}

New directory for Kotlin data in Gradle projects

  • Kotlin Gradle Pluginが<project-root>/.kotlin配下にcacheを保存するようになった
  • gradle.propertiesを設定することで保存先と保存の有無を設定できる
kotlin.project.persistent.dir=<project-root-directory>/.kotlin # 保存先を変更できる
kotlin.project.persistent.dir.gradle.disableWrite=true # 保存を無効にする

Kotlin/Native compiler downloaded when needed

  • Kotlin 1.9以前ではProjectにKotlin/Nativeターゲットが追加されているとconfigurationフェーズでKotlin Native Compilerがダウンロードされていた
    • ~/.konanなどにダウンロードされる
  • Kotlin/Navitveターゲットが設定されているがKotlin/Nativeコンパイルを行わない場合もダウンロードされていた
    • ex)iOSターゲットが設定されているプロジェクトをLinuxマシンで開いた場合にもLinux向けのKotlin Native Compilerがダウンロードされてしまっていた
    • CI上などでネットワーク、ディスクリソースを浪費していた
  • Kotlin 2では実際にKotlin/Nativeコンパイルを実行するタイミングでダウンロードされるようになる
  • gradle.propertieskotlin.native.toolchain.enabled=falseを設定すると以前の挙動に戻せる

Deprecating old ways of defining compiler options

  • HasCompilerOptionscompilerOptionsがdeprecatedになった
  • HasCompilerOptionsinterfaceを用いて各DSLにcompipler optionを設定する方法が提供されていたがKotlinコンパイルタスクと同一のcompilerOptionsを提供するため混乱を招いていた
  • New Gradle DSL for compiler options in multiplatform projectsもこれの一環
  • 各設定方法がドキュメントにあるのでそちらを読んで改めて設定するとよい

Bumped minimum AGP supported version

  • Kotlin 2.0.0以降はAGP7.1.3以上である必要がある

New Gradle property to try latest language version

  • 新しいKotlinバージョンを試すためのオプションが追加された
  • gradle.propertiesにkotlin.experimental.tryNext=trueを設定すると新しいKotlinバージョンを試せる
  • Kotlin2.0のプロジェクトでオプションを設定するとKotlin2.1が使用できる

New JSON output format for build reports

  • Kotlin 1.7でビルドのレポートをファイル形式で保存することができるようになった
  • txtのみサポートされたいたがjson形式が新たに加わった

Standard library

Stable replacement of the enum class values generic function

  • enumEntries<T>()がstableになった
  • enumEntries<T>()enumValues<T>()を代替する関数
  • Kotlin 1.8.20で導入されたEnum.entriesEnum.values()の関係と同じく, enumEntries<T>()のほうがパフォーマンスが良いため置き換えることが推奨される

Stable AutoCloseable interface

  • AutoCloseablejava.lang.AutoCloseableのKotlin版
  • try-with-resourceをKotlinで実現するinterface
  • 実装しているクラスはuse関数などを用いて適切にresource解放する実装ができる

Common protected property AbstractMutableList.modCount

  • AbstractMutableList.modCountがcommonコードで使用できるようになった
  • Kotlin 1.9以前では各Platformで実装されていたためcommonコードで使用できなかった

Common protected function AbstractMutableList.removeRange

  • AbstractMutableList.removeRangeがcommonコードで使用できるようになった
  • Kotlin 1.9以前では各Platformで実装されていたためcommonコードで使用できなかった

Common String.toCharArray(destination)

  • jvmコードでのみ使用可能だったString.toCharArray(destination)がcommonコードで使用できるようになった
  • String.toCharArray()は新しいCharArrayのインスタンスを生成するが、String.toCharArray(destination)は生成しない
19
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?