Spec-Zone.ru › Kotlin 2

Руководство по миграции на компилятор K2

По мере развития языка Kotlin и его экосистемы развивался и компилятор Kotlin. Первым шагом стало появление новых бэкендов JVM и JS IR (промежуточное представление), которые используют общую логику, упрощая генерацию кода для целевых платформ. Теперь следующий этап развития — новый фронтенд под названием K2.

Kotlin K2 compiler architecture

С появлением компилятора K2 фронтенд Kotlin был полностью переписан и получил новую, более эффективную архитектуру. Основное изменение, которое привносит новый компилятор, — использование единой структуры данных, содержащей больше семантической информации. Этот фронтенд отвечает за семантический анализ, разрешение вызовов и вывод типов.

Новая архитектура и обогащенная структура данных позволяют компилятору K2 предоставить следующие преимущества:

  • Улучшенное разрешение вызовов и вывод типов. Компилятор работает более последовательно и лучше понимает ваш код.

  • Упрощение добавления синтаксического сахара для новых возможностей языка. В будущем вы сможете писать более лаконичный и понятный код при появлении новых возможностей.

  • Ускорение компиляции. Компиляция может выполняться значительно быстрее.

  • Повышение производительности IDE. IntelliJ IDEA и Android Studio используют компилятор K2 для анализа кода Kotlin, повышая стабильность и производительность. Дополнительную информацию см. в разделе Поддержка в IDE.

В этом руководстве:

  • Объясняются преимущества нового компилятора K2.

  • Рассматриваются изменения, с которыми вы можете столкнуться при миграции, и способы адаптации кода.

  • Описывается, как вернуться к предыдущей версии.

Новый компилятор K2 включен по умолчанию начиная с версии 2.0.0. Дополнительную информацию о новых возможностях Kotlin 2.0.0 и новом компиляторе K2 см. в разделе Что нового в Kotlin 2.0.0.

Повышение производительности

Чтобы оценить производительность компилятора K2, мы провели тесты производительности на двух проектах с открытым исходным кодом: Anki-Android и Exposed. Вот основные улучшения производительности, которые мы обнаружили:

  • Компилятор K2 обеспечивает ускорение компиляции до 94%. Например, в проекте Anki-Android время полной сборки сократилось с 57,7 секунды в Kotlin 1.9.23 до 29,7 секунды в Kotlin 2.0.0.

  • Фаза инициализации с компилятором K2 выполняется до 488% быстрее. Например, в проекте Anki-Android фаза инициализации инкрементальной сборки сократилась с 0,126 секунды в Kotlin 1.9.23 до всего 0,022 секунды в Kotlin 2.0.0.

  • Компилятор Kotlin K2 выполняет фазу анализа до 376% быстрее по сравнению с предыдущим компилятором. Например, в проекте Anki-Android время анализа при инкрементальной сборке сократилось с 0,581 секунды в Kotlin 1.9.23 до всего 0,122 секунды в Kotlin 2.0.0.

Подробнее об этих улучшениях и о том, как мы анализировали производительность компилятора K2, читайте в нашей публикации в блоге.

Улучшения возможностей языка

Компилятор Kotlin K2 улучшает возможности языка, связанные со смарт-кастами и Kotlin Multiplatform.

Смарт-касты

В определенных случаях компилятор Kotlin может автоматически привести объект к типу, избавляя вас от необходимости указывать это явно. Это называется смарт-кастом. Теперь компилятор Kotlin K2 выполняет смарт-касты в еще большем количестве сценариев.

В Kotlin 2.0.0 мы улучшили работу смарт-кастов в следующих областях:

  • Локальные переменные и последующие области видимости

  • Проверки типов с оператором логического or

  • Встроенные функции

  • Свойства с функциональными типами

  • Обработка исключений

  • Операторы инкремента и декремента

Локальные переменные и последующие области видимости

Ранее, если в условии 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
}

Проверки типов с оператором логического ИЛИ

В 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()
    }
}

Общий супертип — это приближение типа объединения. Типы объединения пока не поддерживаются в 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. Это делает ваш код безопаснее, поскольку компилятор отслеживает, имеет ли объект nullable-тип. Например:

//sampleStart
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.
    }
}
//sampleEnd
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, в следующих областях:

  • Разделение общих и платформенных исходных файлов во время компиляции

  • Разные уровни видимости expect- и actual-объявлений

Разделение общих и платформенных исходных файлов во время компиляции

Ранее устройство компилятора Kotlin не позволяло ему разделять общие и платформенные наборы исходных файлов во время компиляции. В результате общий код мог обращаться к платформенному, что приводило к различиям в поведении на разных платформах. Кроме того, некоторые настройки компилятора и зависимости из общего кода могли попадать в платформенный код.

В Kotlin 2.0.0 при реализации нового компилятора Kotlin K2 схема компиляции была переработана, чтобы обеспечить строгое разделение общих и платформенных наборов исходных файлов. Это изменение наиболее заметно при использовании expect- и actual-функций. Ранее вызов функции в общем коде мог разрешиться в функцию из платформенного кода. Например:

Общий код

Платформенный код

fun foo(x: Any) = println("common foo")

fun exampleFunction() {
    foo(42)
}
// JVM
fun foo(x: Int) = println("platform foo")

// JavaScript
// There is no foo() function overload on the JavaScript platform

В этом примере поведение общего кода зависит от платформы, на которой он выполняется:

  • На платформе JVM вызов функции foo() в общем коде приводит к вызову функции foo() из платформенного кода в качестве platform foo.

  • На платформе JavaScript вызов функции foo() в общем коде приводит к вызову функции foo() из общего кода в качестве common foo, поскольку в платформенном коде такой функции нет.

В Kotlin 2.0.0 общий код не имеет доступа к платформенному, поэтому на обеих платформах функция foo() успешно разрешается в функцию foo() из общего кода: common foo.

Помимо повышения согласованности поведения на разных платформах, мы также приложили немало усилий, чтобы устранить случаи расхождения между IntelliJ IDEA или Android Studio и компилятором. Например, при использовании expect- и actual-классов происходило следующее:

Общий код

Платформенный код

expect class Identity {
    fun confirmIdentity(): String
}

fun common() {
    // Before 2.0.0, it triggers an IDE-only error
    Identity().confirmIdentity()
    // RESOLUTION_TO_CLASSIFIER : Expected class Identity has no default constructor.
}
actual class Identity {
    actual fun confirmIdentity() = "expect class fun: jvm"
}

В этом примере у ожидаемого класса Identity нет конструктора по умолчанию, поэтому его нельзя успешно вызвать в общем коде. Ранее об ошибке сообщала только IDE, но код при этом успешно компилировался для JVM. Теперь же компилятор правильно сообщает об ошибке:

Expected class 'expect class Identity : Any' does not have default constructor
Когда поведение разрешения не меняется

Мы все еще переходим на новую схему компиляции, поэтому поведение разрешения остается прежним при вызове функций, находящихся не в одном наборе исходных файлов. Это различие особенно заметно при использовании в общем коде перегрузок из многоплатформенной библиотеки.

Предположим, у вас есть библиотека с двумя функциями 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 ведет себя так же, как и в старой схеме компиляции.

В будущем эти оставшиеся случаи будут приведены в большее соответствие с новой схемой компиляции.

Разные уровни видимости expect- и actual-объявлений

До Kotlin 2.0.0 при использовании expect- и actual-объявлений в проекте Kotlin Multiplatform они должны были иметь одинаковый уровень видимости. Теперь Kotlin 2.0.0 также поддерживает разные уровни видимости, но только если actual-объявление более доступно, чем expect-объявление. Например:

expect internal class Attribute // Visibility is internal
actual class Attribute          // Visibility is public by default,
                                // which is more permissive

Аналогично, если в actual-объявлении используется псевдоним типа, видимость базового типа должна быть такой же или более высокой, чем у expect-объявления. Например:

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, укажите версию 2.0.0 или более позднюю в сценариях сборки Gradle и Maven.

Использование отчетов о сборке Kotlin с Gradle

Отчеты о сборке Kotlin содержат сведения о времени, затраченном на разные этапы компиляции задач компилятора Kotlin, а также о том, какой компилятор и версия Kotlin использовались и была ли компиляция инкрементальной. Эти отчеты полезны для оценки производительности сборки. Они дают больше информации о конвейере компиляции Kotlin, чем отчеты о сборках Gradle, поскольку содержат обзор производительности всех задач Gradle.

Как включить отчеты о сборке

Чтобы включить отчеты о сборке, укажите, куда сохранять их вывод, в файле gradle.properties:

kotlin.build.report.output=file

Для вывода доступны следующие значения и их комбинации:

Параметр

Описание

file

Сохраняет отчеты о сборке в удобочитаемом формате в локальный файл. По умолчанию это ${project_folder}/build/reports/kotlin-build/${project_name}-timestamp.txt

single_file

Сохраняет отчеты о сборке в формате объекта в указанный локальный файл.

build_scan

Сохраняет отчеты о сборке в разделе custom values отчета о сборке. Обратите внимание, что плагин Gradle Enterprise ограничивает количество пользовательских значений и их длину. В крупных проектах некоторые значения могут быть потеряны.

http

Отправляет отчеты о сборке с помощью HTTP(S). Метод POST отправляет метрики в формате JSON. Актуальную версию отправляемых данных можно посмотреть в репозитории Kotlin. Примеры конечных точек HTTP приведены в этой публикации в блоге

json

Сохраняет отчеты о сборке в формате JSON в локальный файл. Укажите расположение отчетов о сборке в kotlin.build.report.json.directory. По умолчанию файл называется ${project_name}-build-<date-time>-<index>.json.

Дополнительную информацию о возможностях отчетов о сборке см. в разделе Отчеты о сборке.

Поддержка в IDE

IntelliJ IDEA и Android Studio полностью поддерживают компилятор K2 и используют его по умолчанию для улучшения анализа кода, автодополнения и подсветки. Ничего настраивать не нужно. Обновите IDE до последней версии, чтобы воспользоваться преимуществами.

Попробуйте компилятор Kotlin K2 в Kotlin Playground

Kotlin Playground поддерживает Kotlin 2.0.0 и более поздние версии. Попробуйте!

Как вернуться к предыдущему компилятору

Чтобы использовать предыдущий компилятор в Kotlin 2.0.0–2.3.21, выполните одно из следующих действий:

  • В файле build.gradle.kts установите версию языка 1.9.

    ИЛИ

  • Используйте следующий параметр компилятора: -language-version 1.9.

Начиная с Kotlin 2.4.0, вернуться к предыдущему компилятору нельзя.

Изменения

С появлением нового фронтенда компилятор Kotlin претерпел несколько изменений. Сначала рассмотрим наиболее значимые изменения, влияющие на ваш код: объясним, что изменилось, и расскажем о рекомендуемых подходах в дальнейшем. Если вы хотите узнать больше, мы сгруппировали изменения по темам, чтобы вам было проще продолжить чтение.

В этом разделе рассматриваются следующие изменения:

  • Немедленная инициализация открытых свойств с полями для хранения значения

  • Устаревшие синтетические сеттеры для проецируемого получателя

  • Запрет на использование недоступных обобщённых типов

  • Единый порядок разрешения свойств Kotlin и полей Java с одинаковыми именами

  • Улучшенная проверка на null для массивов примитивных типов Java

  • Более строгие правила для абстрактных членов в ожидаемых классах

Немедленная инициализация открытых свойств с полями для хранения значения

Что изменилось?

В Kotlin 2.0 все свойства open с полями для хранения значения должны инициализироваться сразу, иначе возникнет ошибка компиляции. Раньше немедленная инициализация требовалась только для свойств open var, но теперь это правило распространяется и на свойства open val с полями для хранения значения:

open class Base {
    open val a: Int
    open var b: Int
    
    init {
        // Error starting with Kotlin 2.0 that earlier compiled successfully 
        this.a = 1 //Error: open val must have initializer
        // Always an error
        this.b = 1 // Error: open var must have initializer
    }
}

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

Это изменение делает поведение компилятора более предсказуемым. Рассмотрим пример, в котором свойство open val переопределяется свойством var с пользовательским сеттером.

Использование пользовательского сеттера может привести к путанице при отложенной инициализации, поскольку непонятно, нужно ли инициализировать поле для хранения значения или вызвать сеттер. Раньше компилятор не мог гарантировать, что при вызове сеттера поле для хранения значения будет инициализировано.

Как теперь лучше поступать?

Мы рекомендуем всегда инициализировать открытые свойства с полями для хранения значения, поскольку считаем, что такой подход эффективнее и снижает вероятность ошибок.

Однако, если вы не хотите инициализировать свойство сразу, можно:

  • Сделать свойство final.

  • Использовать приватное свойство для хранения значения, которое допускает отложенную инициализацию.

Дополнительную информацию см. в соответствующей задаче в YouTrack.

Устаревший синтетический сеттер для проецируемого получателя

Что изменилось?

Если вы используете синтетический сеттер класса Java, чтобы присвоить значение типа, конфликтующего с проецируемым типом класса, возникает ошибка.

Предположим, что у вас есть класс Java с именем Container, содержащий методы getFoo() и setFoo():

public class Container<E> {
    public E getFoo() {
        return null;
    }
    public void setFoo(E foo) {}
}

Если в следующем коде Kotlin экземпляры класса Container имеют проецируемые типы, использование метода setFoo() всегда приводит к ошибке. Однако синтетическое свойство foo будет вызывать ошибку только начиная с Kotlin 2.0.0:

fun exampleFunction(starProjected: Container<*>, inProjected: Container<in Number>, sampleString: String) {
    starProjected.setFoo(sampleString)
    // Error since Kotlin 1.0

    // Synthetic setter `foo` is resolved to the `setFoo()` method
    starProjected.foo = sampleString
    // Error since Kotlin 2.0.0

    inProjected.setFoo(sampleString)
    // Error since Kotlin 1.0

    // Synthetic setter `foo` is resolved to the `setFoo()` method
    inProjected.foo = sampleString
    // Error since Kotlin 2.0.0
}

Как теперь лучше поступать?

Если из-за этого изменения в коде появились ошибки, возможно, стоит пересмотреть структуру объявлений типов. Может быть, вам не нужно использовать проекции типов или следует удалить из кода присваивания.

Дополнительную информацию см. в соответствующей задаче в YouTrack.

Запрет на использование недоступных обобщённых типов

Что изменилось?

В связи с новой архитектурой компилятора K2 мы изменили способ обработки недоступных обобщённых типов. Как правило, не следует использовать недоступные обобщённые типы в коде, поскольку это указывает на неправильную конфигурацию сборки проекта, из-за которой компилятор не может получить информацию, необходимую для компиляции. В Kotlin 2.0.0 нельзя объявить или вызвать функциональный литерал с недоступным обобщённым типом, а также использовать обобщённый тип с недоступными аргументами типа. Это ограничение помогает избежать ошибок компилятора в дальнейшем.

Например, предположим, что вы объявили обобщённый класс в одном модуле:

// Module one
class Node<V>(val value: V)

Если у вас есть другой модуль (модуль два), настроенный как зависимый от модуля один, ваш код может обращаться к классу Node<V> и использовать его как тип в типах функций:

// Module two
fun execute(func: (Node<Int>) -> Unit) {}
// Function compiles successfully

Однако, если проект настроен неправильно и у вас есть третий модуль (модуль три), зависящий только от модуля два, компилятор Kotlin не сможет получить доступ к классу Node<V> в модуле один при компиляции третьего модуля. Теперь любые лямбды или анонимные функции в модуле три, использующие тип Node<V>, вызывают ошибки в Kotlin 2.0.0. Это помогает предотвратить ошибки компилятора, сбои и исключения во время выполнения, которых можно избежать:

// Module three
fun test() {
    // Triggers an error in Kotlin 2.0.0, as the type of the implicit 
    // lambda parameter (it) resolves to Node, which is inaccessible
    execute {}

    // Triggers an error in Kotlin 2.0.0, as the type of the unused 
    // lambda parameter (_) resolves to Node, which is inaccessible
    execute { _ -> }

    // Triggers an error in Kotlin 2.0.0, as the type of the unused
    // anonymous function parameter (_) resolves to Node, which is inaccessible
    execute(fun (_) {})
}

Ошибки возникают не только при использовании в функциональных литералах параметров-значений с недоступными обобщёнными типами, но и в том случае, если тип содержит недоступный аргумент обобщённого типа.

Например, в модуле один объявлен тот же обобщённый класс. В модуле два объявлен другой обобщённый класс: Container<C>. Кроме того, в модуле два объявлены функции, в которых Container<C> использует обобщённый класс Node<V> в качестве аргумента типа:

Модуль один

Модуль два

// Module one
class Node<V>(val value: V)
// Module two
class Container<C>(vararg val content: C)

// Functions with generic class type that
// also have a generic class type argument
fun produce(): Container<Node<Int>> = Container(Node(42))
fun consume(arg: Container<Node<Int>>) {}

Если вызвать эти функции в модуле три, в Kotlin 2.0.0 возникнет ошибка, поскольку обобщённый класс Node<V> недоступен из модуля три:

// Module three
fun test() {
    // Triggers an error in Kotlin 2.0.0, as generic class Node<V> is 
    // inaccessible
    consume(produce())
}

В будущих выпусках мы продолжим отказываться от использования недоступных типов в целом. В Kotlin 2.0.0 мы уже начали это делать, добавив предупреждения для некоторых случаев с недоступными типами, в том числе необобщёнными.

Например, возьмём ту же конфигурацию модулей, что и в предыдущих примерах, но заменим обобщённый класс Node<V> необобщённым классом IntNode и объявим все функции в модуле два:

Модуль один

Модуль два

// Module one
class IntNode(val value: Int)
// Module two
// A function that contains a lambda 
// parameter with `IntNode` type
fun execute(func: (IntNode) -> Unit) {}

class Container<C>(vararg val content: C)

// Functions with generic class type
// that has `IntNode` as a type argument
fun produce(): Container<IntNode> = Container(IntNode(42))
fun consume(arg: Container<IntNode>) {}

При вызове этих функций в модуле три появляются предупреждения:

// Module three
fun test() {
    // Triggers warnings in Kotlin 2.0.0, as class IntNode is 
    // inaccessible.

    execute {}
    // Class 'IntNode' of the parameter 'it' is inaccessible.

    execute { _ -> }
    execute(fun (_) {})
    // Class 'IntNode' of the parameter '_' is inaccessible.

    // Will trigger a warning in future Kotlin releases, as IntNode is
    // inaccessible.
    consume(produce())
}

Как теперь лучше поступать?

Если вы столкнулись с новыми предупреждениями о недоступных обобщённых типах, скорее всего, проблема связана с конфигурацией системы сборки. Рекомендуем проверить скрипты сборки и конфигурацию.

В крайнем случае можно настроить прямую зависимость модуля три от модуля один. Другой вариант — изменить код так, чтобы типы были доступны в рамках одного модуля.

Дополнительную информацию см. в соответствующей задаче в YouTrack.

Единый порядок разрешения свойств Kotlin и полей Java с одинаковыми именами

Что изменилось?

До Kotlin 2.0.0 при работе с классами Java и Kotlin, наследующими друг от друга и содержащими свойства Kotlin и поля Java с одинаковыми именами, поведение при разрешении совпадающих имён было непоследовательным. Кроме того, поведение IntelliJ IDEA и компилятора различалось. Разрабатывая новый механизм разрешения для Kotlin 2.0.0, мы стремились свести влияние на пользователей к минимуму.

Например, предположим, что есть класс Java Base:

public class Base {
    public String a = "a";

    public String b = "b";
}

Предположим также, что есть класс Kotlin Derived, наследующий ранее упомянутый класс Base:

class Derived : Base() {
    val a = "aa"

    // Declares custom get() function
    val b get() = "bb"
}

fun main() {
    // Resolves Derived.a
    println(a)
    // aa

    // Resolves Base.b
    println(b)
    // b
}

До Kotlin 2.0.0 выражение a разрешалось как свойство Kotlin в классе Kotlin Derived, а выражение b — как поле Java в классе Java Base.

В Kotlin 2.0.0 поведение разрешения в этом примере стало последовательным: свойство Kotlin имеет приоритет над одноимённым полем Java. Теперь выражение b разрешается как: Derived.b.

До Kotlin 2.0.0 при переходе к объявлению или использованию a в IntelliJ IDEA выполнялся ошибочный переход к полю Java вместо свойства Kotlin.

Начиная с Kotlin 2.0.0 IntelliJ IDEA выполняет переход в то же место, что и компилятор.

Общее правило: приоритет имеет подкласс. Это видно на предыдущем примере: разрешается свойство Kotlin a из класса Derived, поскольку Derived — подкласс класса Java Base.

Если наследование организовано наоборот и класс Java наследует класс Kotlin, приоритет имеет поле Java в подклассе, а не одноимённое свойство Kotlin.

Рассмотрим пример:

Kotlin

Java

open class Base {
    val a = "aa"
}
public class Derived extends Base {
    public String a = "a";
}

Теперь рассмотрим следующий код:

fun main() {
    // Resolves Derived.a
    println(a)
    // a
}

Как теперь лучше поступать?

Если это изменение влияет на ваш код, подумайте, действительно ли вам нужны совпадающие имена. Если вы хотите, чтобы классы Java или Kotlin содержали поле или свойство с одинаковым именем и наследовали друг друга, учитывайте, что приоритет будет у поля или свойства в подклассе.

Дополнительную информацию см. в соответствующей задаче в YouTrack.

Улучшенная проверка на null для массивов примитивных типов Java

Что изменилось?

Начиная с Kotlin 2.0.0 компилятор корректно определяет допустимость null для массивов примитивных типов Java, импортированных в Kotlin. Теперь он сохраняет исходную информацию о допустимости null из аннотаций TYPE_USE, используемых с массивами примитивных типов Java, и выдаёт ошибки, если значения используются не в соответствии с аннотациями.

Обычно при вызове из Kotlin типов Java с аннотациями @Nullable и @NotNull им присваивается соответствующая информация о допустимости null:

interface DataService {
    @NotNull ResultContainer<@Nullable String> fetchData();
}
val dataService: DataService = ... 
dataService.fetchData() // -> ResultContainer<String?>

Однако раньше при импорте массивов примитивных типов Java в Kotlin все аннотации TYPE_USE терялись, что приводило к платформенной допустимости null и потенциально небезопасному коду:

interface DataProvider {
    int @Nullable [] fetchData();
}
val dataService: DataProvider = ...
dataService.fetchData() // -> IntArray .. IntArray?
// No error, even though `dataService.fetchData()` might be `null` according to annotations
// This might result in a NullPointerException
dataService.fetchData()[0]

Обратите внимание, что эта проблема никогда не затрагивала аннотации допустимости null непосредственно в объявлении, а только аннотации TYPE_USE.

Как теперь лучше поступать?

В Kotlin 2.0.0 проверка на null для массивов примитивных типов Java стала стандартной. Если вы используете такие массивы, проверьте код на наличие новых предупреждений и ошибок:

  • Любой код, который использует массив примитивного типа Java @Nullable без явной проверки на null или пытается передать null методу Java, ожидающему массив примитивного типа, не допускающий null, теперь не скомпилируется.

  • При использовании массива примитивного типа @NotNull с проверкой на null теперь выдаются предупреждения «Ненужный безопасный вызов» или «Сравнение с null всегда ложно».

Дополнительную информацию см. в соответствующей задаче в YouTrack.

Более строгие правила для абстрактных членов в ожидаемых классах

Ожидаемые и фактические классы находятся в статусе бета-версии. Они почти стабильны, но в будущем может потребоваться выполнить миграцию. Мы постараемся свести к минимуму необходимость дальнейших изменений.

Что изменилось?

Из-за разделения общих и платформенных исходных файлов при компиляции компилятором K2 мы ввели более строгие правила для абстрактных членов в ожидаемых классах.

В предыдущем компиляторе ожидаемый неабстрактный класс мог наследовать абстрактную функцию, не переопределяя эту функцию. Поскольку компилятор одновременно имел доступ к общему и платформенному коду, он мог проверить, есть ли у абстрактной функции соответствующее переопределение и определение в фактическом классе.

Теперь, когда общие и платформенные исходные файлы компилируются отдельно, унаследованную функцию необходимо явно переопределить в ожидаемом классе, чтобы компилятор знал, что она не является абстрактной. В противном случае компилятор сообщает об ошибке ABSTRACT_MEMBER_NOT_IMPLEMENTED.

Например, предположим, что в общем исходном наборе объявлен абстрактный класс с именем FileSystem, содержащий абстрактную функцию listFiles(). Функция listFiles() определена в платформенном исходном наборе в составе фактического объявления.

Если в общем коде у вас есть ожидаемый неабстрактный класс с именем PlatformFileSystem, наследующий класс FileSystem, класс PlatformFileSystem наследует абстрактную функцию listFiles(). Однако в Kotlin неабстрактный класс не может содержать абстрактную функцию. Чтобы сделать функцию listFiles() неабстрактной, нужно объявить её как переопределение без ключевого слова abstract:

Общий код

Платформенный код

abstract class FileSystem {
    abstract fun listFiles()
}
expect open class PlatformFileSystem() : FileSystem {
    // In Kotlin 2.0.0, an explicit override is needed
    expect override fun listFiles()
    // Before Kotlin 2.0.0, an override wasn't needed
}
actual open class PlatformFileSystem : FileSystem {
    actual override fun listFiles() {}
}

Как теперь лучше поступать?

Если ожидаемый неабстрактный класс наследует абстрактные функции, добавьте неабстрактное переопределение.

Дополнительную информацию см. в соответствующей задаче в YouTrack.

По тематическим областям

В этих тематических областях перечислены изменения, которые вряд ли повлияют на ваш код, но содержат ссылки на соответствующие задачи YouTrack для дополнительной информации. Изменения, отмеченные звездочкой (*) рядом с идентификатором задачи, описаны в начале раздела.

Вывод типов

Идентификатор задачи

Название

KT-64189

Неверный тип в скомпилированной сигнатуре функции для ссылки на свойство, если явно указан тип Normal

KT-47986

Запретить неявный вывод переменной типа в верхнюю границу в контексте вывода типов построителя

KT-59275

K2: требовать явные аргументы типа для вызовов обобщённых аннотаций в литералах массивов

KT-53752

Пропущена проверка подтипов для типа-пересечения

KT-59138

Изменить представление типов на основе параметров типа Java по умолчанию в Kotlin

KT-57178

Изменить выведенный тип префиксного инкремента: возвращать тип результата геттера вместо типа результата оператора inc()

KT-57609

K2: не учитывать наличие @UnsafeVariance при использовании контравариантных параметров

KT-57620

K2: запретить разрешение в пользу перекрытых членов для raw-типов

KT-64641

K2: корректно выводить тип ссылки на вызываемый объект, у которого есть параметр-функция расширения

KT-57011

Согласовать фактический тип переменной деструктуризации с явно указанным типом, если он задан

KT-38895

K2: исправить непоследовательное поведение при переполнении целочисленных литералов

KT-54862

Анонимный тип может быть раскрыт из анонимной функции через аргумент типа

KT-22379

Условие цикла while с break может привести к некорректному смарт-касту

KT-62507

K2: запретить смарт-каст в общем коде для свойства верхнего уровня expect/actual

KT-65750

Операторы инкремента и сложения, изменяющие тип результата, должны влиять на смарт-касты

KT-65349

[LC] K2: явное указание типов переменных в некоторых случаях нарушает смарт-касты с ограничениями, работавшие в K1

Обобщения

Идентификатор задачи

Название

KT-54309*

Устаревание использования синтетического сеттера для проецированного получателя

KT-57600

Запретить переопределение метода Java с параметром raw-типа параметром обобщённого типа

KT-54663

Запретить передачу потенциально допускающего null параметра типа в параметр DNN с проекцией `in`

KT-54066

Устаревание нарушения верхней границы в конструкторах typealias

KT-49404

Исправить некорректность типов для захваченного контравариантного типа на основе класса Java

KT-61718

Запретить некорректный код с собственными верхними границами и захваченными типами

KT-61749

Запретить некорректное нарушение границы во внутреннем обобщённом классе обобщённого внешнего класса

KT-62923

K2: добавить PROJECTION_IN_IMMEDIATE_ARGUMENT_TO_SUPERTYPE для проекций внешних суперклассов внутреннего класса

KT-63243

Сообщать MANY_IMPL_MEMBER_NOT_IMPLEMENTED при наследовании от коллекции примитивов с дополнительной специализированной реализацией из другого суперкласса

KT-60305

K2: запретить вызов конструктора и наследование от псевдонима типа, имеющего модификаторы вариативности в раскрытом типе

KT-64965

Исправить дыру в системе типов, вызванную неправильной обработкой захваченных типов с собственными верхними границами

KT-64966

Запретить обобщённые делегирующие вызовы конструкторов с неверным типом параметра типа

KT-65712

Сообщать о пропущенном нарушении верхней границы, если верхняя граница является захваченным типом

Разрешение

Идентификатор задачи

Название

KT-55017*

Выбирать свойство Kotlin из производного класса при разрешении перегрузки с полем Java из базового класса

KT-58260

Обеспечить согласованную работу соглашения invoke с ожидаемым преобразованием синтаксиса

KT-62866

K2: изменить поведение разрешения квалификаторов, когда объект-компаньон имеет приоритет перед статической областью

KT-57750

Сообщать об ошибке неоднозначности при разрешении типов, если импортированы звёздочкой классы с одинаковыми именами

KT-63558

K2: перенести логику разрешения вокруг COMPATIBILITY_WARNING

KT-51194

Ложноотрицательный результат CONFLICTING_INHERITED_MEMBERS, если класс зависимости содержится в двух разных версиях одной зависимости

KT-37592

Свойство invoke функционального типа с получателем имеет приоритет перед функцией расширения invoke

KT-51666

Квалифицированный this: добавить случай this, квалифицированный типом, и повысить его приоритет

KT-54166

Подтвердить неопределённое поведение в случае конфликтов полных имён в classpath

KT-64431

K2: запретить использовать псевдонимы типов в качестве квалификаторов в импортах

KT-56520

K1/K2: некорректная работа башни разрешения для ссылок на типы с неоднозначностью на нижнем уровне

Видимость

Идентификатор задачи

Название

KT-64474*

Объявить использование недоступных типов неопределённым поведением

KT-55179

Ложноотрицательный PRIVATE_CLASS_MEMBER_FROM_INLINE при вызове члена объекта-компаньона приватного класса из внутренней inline-функции

KT-58042

Сделать синтетическое свойство невидимым, если эквивалентный геттер невидим, даже когда переопределённое объявление видно

KT-64255

Запретить доступ к сеттеру internal из производного класса в другом модуле

KT-33917

Запретить раскрытие анонимных типов из приватных inline-функций

KT-54997

Запретить неявный доступ к API, не являющемуся публичным, из inline-функции публичного API

KT-56310

Смарт-касты не должны влиять на видимость защищённых членов

KT-65494

Запретить доступ к пропущенным приватным функциям-операторам из публичной inline-функции

KT-65004

K1: сеттер var, переопределяющего protected val, генерируется как public

KT-64972

Запретить переопределение приватными членами на этапе компоновки для Kotlin/Native

Аннотации

Идентификатор задачи

Название

KT-58723

Запретить аннотировать инструкции аннотацией, если у неё нет цели EXPRESSION

KT-49930

Игнорировать выражение в скобках при проверке `REPEATED_ANNOTATION`

KT-57422

K2: запретить аннотации с целевым элементом 'get' на геттерах свойств

KT-46483

Запретить аннотации параметров типа в предложении where

KT-64299

Область видимости компаньона игнорируется при разрешении аннотаций объекта-компаньона

KT-64654

K2: возникла неоднозначность между пользовательскими и обязательными для компилятора аннотациями

KT-64527

Аннотации значений enum не должны копироваться в классы значений enum

KT-63389

K2: `WRONG_ANNOTATION_TARGET` сообщается для несовместимых аннотаций типа, обёрнутого в `()?`

KT-63388

K2: `WRONG_ANNOTATION_TARGET` сообщается для аннотаций типа параметра catch

Безопасность работы с null

Идентификатор задачи

Название

KT-54521*

Устаревание небезопасного использования типов массивов, аннотированных как Nullable в Java

KT-41034

K2: изменить семантику вычисления для сочетания безопасных вызовов и операторов соглашений

KT-50850

Порядок суперклассов определяет параметры nullable для унаследованных функций

KT-53982

Сохранять nullable при аппроксимации локальных типов в публичных сигнатурах

KT-62998

Запретить присваивание nullable-значения ненулевому полю Java как вариант небезопасного присваивания

KT-63209

Сообщать об отсутствующих ошибках для nullable-аргументов уровня ошибки типов Java уровня предупреждения

Взаимодействие с Java

Идентификатор задачи

Название

KT-53061

Запретить наличие в исходном коде классов Java и Kotlin с одинаковыми полными именами

KT-49882

Поведение классов, унаследованных от коллекций Java, непоследовательно и зависит от порядка суперклассов

KT-66324

K2: неопределённое поведение в случае наследования класса Java от приватного класса Kotlin

KT-66220

Передача метода Java с vararg в inline-функцию приводит во время выполнения к массиву массивов вместо обычного массива

KT-66204

Разрешить переопределение членов internal в иерархии K-J-K

Свойства

Идентификатор задачи

Название

KT-57555*

[LC] Запретить отложенную инициализацию открытых свойств с полем хранения

KT-58589

Устаревание пропущенного MUST_BE_INITIALIZED, если первичный конструктор отсутствует или класс является локальным

KT-64295

Запретить рекурсивное разрешение в случае потенциальных вызовов invoke для свойств

KT-57290

Устаревание смарт-каста свойства базового класса из невидимого производного класса, если базовый класс находится в другом модуле

KT-62661

K2: пропущена OPT_IN_USAGE_ERROR для свойств класса данных

Поток управления

Идентификатор задачи

Название

KT-56408

Непоследовательные правила CFA в блоке инициализации класса в K1 и K2

KT-57871

Несоответствие K1/K2 для условного выражения if без ветви else в скобках

KT-42995

Ложноотрицательный результат "VAL_REASSIGNMENT" в блоке try/catch с инициализацией в функции области видимости

KT-65724

Передавать информацию о потоке данных из блока try в блоки catch и finally

Классы enum

Идентификатор задачи

Название

KT-57608

Запретить доступ к объекту-компаньону класса enum во время инициализации элемента enum

KT-34372

Сообщать о пропущенной ошибке для виртуального inline-метода в классах enum

KT-52802

Сообщать о неоднозначности при разрешении между свойством/полем и элементом enum

KT-47310

Изменить поведение разрешения квалификаторов, когда свойство компаньона имеет приоритет перед элементом enum

Функциональные (SAM) интерфейсы

Идентификатор задачи

Название

KT-52628

Устаревание вызовов SAM-конструкторов, требующих OptIn без аннотации

KT-57014

Запрет возврата значений с неверной nullability из лямбды для SAM-конструктора функциональных интерфейсов JDK

KT-64342

SAM-преобразование типов параметров ссылок на вызываемые объекты приводит к CCE

Объект-компаньон

Идентификатор задачи

Название

KT-54316

Ссылка на член объекта-компаньона вне вызова имеет неверную сигнатуру

KT-47313

Изменить разрешение ссылки (V)::foo, если у V есть объект-компаньон

Разное

Идентификатор задачи

Название

KT-59739*

K2/MPP сообщает [ABSTRACT_MEMBER_NOT_IMPLEMENTED] для наследника в общем коде, если реализация находится в фактическом аналоге

KT-49015

Изменить поведение квалифицированного this при возможных конфликтах меток

KT-56545

Исправить некорректное переименование функций в бэкенде JVM при случайном конфликте перегрузок в подклассе Java

KT-62019

[Проблема LC] Запретить объявления анонимных функций с модификатором suspend в позициях операторов

KT-55111

OptIn: запретить вызовы конструкторов с аргументами по умолчанию (параметрами со значениями по умолчанию) под маркером

KT-61182

Преобразование в Unit случайно разрешено для выражений с переменными и разрешением invoke

KT-55199

Запретить преобразование ссылок на вызываемые объекты с адаптациями в KFunction

KT-65776

[LC] K2 нарушает работу `false && ...` и `false || ...`

KT-65682

[LC] Устаревание ключевых слов `header`/`impl`

KT-45375

Генерировать все лямбды Kotlin по умолчанию с помощью invokedynamic + LambdaMetafactory

Совместимость с выпусками Kotlin

Поддержка нового компилятора K2 реализована в следующих выпусках Kotlin:

Выпуск Kotlin

Уровень стабильности

2.0.0–2.4.20

Стабильный

1.9.20–1.9.25

Бета

1.9.0–1.9.10

JVM — бета

1.7.0–1.8.22

Альфа

Совместимость с библиотеками Kotlin

Если вы работаете с Kotlin/JVM, компилятор K2 работает с библиотеками, скомпилированными с помощью любой версии Kotlin.

Если вы работаете с Kotlin Multiplatform, гарантируется работа компилятора K2 с библиотеками, скомпилированными с помощью Kotlin версии 1.9.20 и новее.

Поддержка плагинов компилятора

В настоящее время компилятор Kotlin K2 поддерживает следующие плагины компилятора Kotlin:

  • all-open

  • AtomicFU

  • jvm-abi-gen

  • js-plain-objects

  • kapt

  • Lombok

  • no-arg

  • Parcelize

  • Power-assert

  • SAM with receiver

  • Serialization

Кроме того, компилятор Kotlin K2 поддерживает:

  • Плагин компилятора Jetpack Compose версии 1.5.0 и новее.

  • Обработка символов Kotlin (KSP) начиная с версии KSP2.

Если вы используете дополнительные плагины компилятора, проверьте в их документации, совместимы ли они с K2.

Обновление собственных плагинов компилятора

Собственные плагины компилятора используют API плагинов, который имеет статус экспериментального. Поэтому API может измениться в любой момент, и мы не можем гарантировать обратную совместимость.

Процесс обновления зависит от типа вашего собственного плагина и может выполняться двумя способами.

Плагины компилятора только для бэкенда

Если ваш плагин реализует только точки расширения IrGenerationExtension, процесс ничем не отличается от обновления для любого другого нового выпуска компилятора. Проверьте, изменился ли используемый вами API, и при необходимости внесите соответствующие изменения.

Плагины компилятора для бэкенда и фронтенда

Если ваш плагин использует точки расширения, связанные с фронтендом, необходимо переписать его с помощью нового API компилятора K2. Введение в новый API см. в разделе API плагинов FIR.

Если у вас возникли вопросы об обновлении собственного плагина компилятора, присоединяйтесь к нашему каналу Slack #compiler — мы постараемся вам помочь.

Поделитесь отзывом о новом компиляторе K2

Мы будем рады любым вашим отзывам!

  • Сообщайте о любых проблемах, с которыми вы столкнулись при переходе на новый компилятор K2, в нашем трекере задач.

  • Включите параметр «Отправлять статистику использования», чтобы разрешить JetBrains собирать анонимные данные об использовании K2.

12 августа 2026 г.
Демон KotlinКомпилятор Kotlin для командной строки

© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/k2-compiler-migration-guide.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API