---
title: Компилятор K2
description: Фазы компиляции, что изменилось по сравнению с K1, умные приведения, экспериментальные флаги
tags:
- kotlin
- tooling
- compiler
---
K2 - фронтенд компилятора Kotlin, стабильный с версии 2.0 и единственный в линии 2.x. Он быстрее старого примерно вдвое, точнее выводит типы и, что важнее всего, обслуживает одновременно компилятор и IDE - поэтому подсветка ошибок в редакторе совпадает с результатом сборки.
```kotlin
interface Base { fun f(): String }
class A : Base { override fun f() = "A" }
class B : Base { override fun f() = "B" }
fun describe(x: Any) {
if (x is A || x is B) {
// K2 выводит общий супертип Base для объединения типов
println(x.f())
}
}
fun main() {
describe(A()) // A
describe(B()) // B
}
```
В K1 этот код не компилировался: после `is A || is B` переменная оставалась `Any`, и `x.f()` требовал явного приведения.
## Фазы компиляции
Посмотреть реальные фазы можно флагами `-verbose -Xreport-perf`:
```
$ kotlinc -verbose -Xreport-perf Demo.kt -d out
PERF: INIT 271 ms
PERF: ANALYZE 530 ms 11.311 loc/s
PERF: TRANSLATION to IR 144 ms 41.453 loc/s
PERF: KLIB WRITING 0 ms
PERF: IR LOWERING 122 ms 48.799 loc/s
PERF: BACKEND 75 ms 79.456 loc/s
```
```mermaid
graph LR
A[".kt"] --> B["INIT
загрузка classpath"]
B --> C["ANALYZE
фронтенд K2: FIR"]
C --> D["TRANSLATION
FIR → IR"]
D --> E["IR LOWERING
развёртка inline,
suspend, лямбды"]
E --> F["BACKEND
IR → байт-код"]
F --> G[".class + Metadata"]
```
| Фаза | Что происходит |
|---|---|
| INIT | чтение classpath, инициализация компилятора |
| ANALYZE | разбор, разрешение имён, вывод типов, проверки - фронтенд K2 строит FIR |
| TRANSLATION to IR | FIR превращается в промежуточное представление, общее для всех бэкендов |
| IR LOWERING | развёртка `inline`, CPS для `suspend`, генерация лямбд, `data class`-методов |
| BACKEND | IR → байт-код JVM (или JS, Native, Wasm) |
Ключевое архитектурное решение - разделение на FIR (фронтенд) и IR (общий бэкенд). Фронтенд один для всех платформ, бэкенды разные. Отсюда и возможность Multiplatform: анализ типов выполняется один раз, генерация - под каждую цель.
## Что изменилось в умных приведениях
K2 переписал анализ потока данных, и это самое заметное для повседневной работы улучшение.
**Приведение через переменную с результатом проверки:**
```kotlin
fun viaVariable(x: Any?) {
val isString = x is String
if (isString) {
// K2 приводит тип по val с результатом проверки
println(x.length)
}
}
fun main() {
viaVariable("шесть!") // 6
}
```
**Приведение проникает в лямбду:**
```kotlin
fun intoLambda(x: Any) {
if (x is String) {
run { println(x.length) } // приведение видно внутри лямбды
}
}
fun main() {
intoLambda("семь!!!") // 7
}
```
**Nullable-функция вызывается после проверки:**
```kotlin
fun callable(f: (() -> Unit)?) {
if (f != null) {
f() // без ?.invoke()
}
}
fun main() {
callable { println("лямбда вызвана") } // лямбда вызвана
}
```
**Исчерпывающий `when` по данным потока:**
```kotlin
sealed interface State
data object Loading : State
data class Done(val value: String) : State
fun render(s: State) = when (s) {
Loading -> "загрузка"
is Done -> "готово: ${s.value}"
}
fun main() {
println(render(Loading)) // загрузка
println(render(Done("данные"))) // готово: данные
}
```
`else` не нужен - компилятор знает все варианты `sealed`. Подробнее: [Умные приведения](../Синтаксис%20и%20типы/Null-безопасность/Умные%20приведения.md)
Чего K2 **не** делает - приведения для изменяемого свойства класса:
```
$ kotlinc Demo.kt -d out
error: smart cast to 'String' is impossible, because 'value' is a mutable property
that could be mutated concurrently.
```
Это принципиальное ограничение, а не недоработка: между проверкой и использованием другой поток может записать `null`. Решение - локальная копия:
```kotlin
class Holder { var value: String? = "есть" }
fun main() {
val h = Holder()
val snapshot = h.value
if (snapshot != null) {
println(snapshot.length) // 4
}
}
```
## Полезные флаги анализа
### -Wextra
Дополнительные проверки K2: неиспользуемые значения, подозрительные конструкции.
```
kotlinc -Wextra Demo.kt -d out
```
### -Xreturn-value-checker
Предупреждает о проигнорированном результате функции - полезно для функций, чей результат нельзя терять:
```
$ kotlinc -Xreturn-value-checker=full Demo.kt -d out
warning: unused return value of 'compute'.
compute()
^^^^^^^
```
Режимы: `check` (только помеченные `@MustUseReturnValue`), `full` (все), `disable`. Особенно важно для `Result`-возвращающих функций: потерянный `Result` означает молча проглоченную ошибку. Подробнее: [Result и runCatching](../Исключения/Result%20и%20runCatching.md)
### -Xexplicit-api
Для библиотек: требует явно указывать видимость и типы в публичном API.
```
$ kotlinc -Xexplicit-api=strict Lib.kt -d out
error: visibility must be specified in explicit API mode.
class Public {
^^^^^^^^^^^^
error: visibility must be specified in explicit API mode.
fun noExplicitType() = 42
^^^^^^^^^^^^^^^^^^
```
```kotlin
// так проходит explicit API mode
public class Service {
public fun compute(): Int = 42
private fun internalHelper(): Int = 1
}
fun main() {
println(Service().compute()) // 42
}
```
В Gradle - `kotlin { explicitApi() }`. Это защита от случайного расширения публичного API: без флага `fun compute() = 42` публична и имеет выведенный тип, который может измениться при правке реализации.
### Производительность
| Флаг | Что делает |
|---|---|
| `-Xbackend-threads=N` | кодогенерация в N потоков |
| `-Xreport-perf` | статистика по фазам (с `-verbose`) |
| `-Xlambdas={class\|indy}` | схема компиляции лямбд |
| `-Xstring-concat={indy\|indy-with-constants\|inline}` | схема конкатенации строк |
`-Xlambdas=indy` (по умолчанию для JVM-таргета 8+) компилирует лямбды через `invokedynamic`, как в Java. `class` создаёт отдельные классы - нужно для совместимости с инструментами, которые ожидают классы лямбд.
## Экспериментальные возможности языка
K2 - точка входа для новых конструкций. Полный список - `kotlinc -X`.
| Флаг | Возможность |
|---|---|
| `-Xcontext-parameters` | context parameters (в 2.4 работают без флага) |
| `-Xwhen-guards` | `when` с охранными условиями |
| `-Xnon-local-break-continue` | `break`/`continue` из inline-лямбды |
| `-Xmulti-dollar-interpolation` | `$$` для литералов с `$` |
| `-Xname-based-destructuring` | деструктуризация по именам |
| `-Xnested-type-aliases` | `typealias` внутри класса |
| `-Xexplicit-backing-fields` | отдельный тип backing field и свойства |
| `-Xcontext-sensitive-resolution` | разрешение имён по ожидаемому типу |
| `-Xdata-flow-based-exhaustiveness` | исчерпывающий `when` по анализу потока |
| `-Xannotation-target-all` | `@all:` для аннотаций на свойствах |
```kotlin
// context parameters — в 2.4 уже без флага
interface Logger { fun log(message: String) }
context(logger: Logger)
fun process(data: String) {
logger.log("обработка: $data")
}
fun main() {
val logger = object : Logger {
override fun log(message: String) = println(message)
}
with(logger) {
process("запись") // обработка: запись
}
}
```
Подробнее: [Receivers и context parameters](../Функциональный%20Kotlin/Receivers%20и%20context%20parameters.md)
## Три версии, которые путают
```kotlin
// build.gradle.kts
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
kotlin {
jvmToolchain(21)
compilerOptions {
jvmTarget.set(JvmTarget.JVM_21) // версия байт-кода
languageVersion.set(KotlinVersion.KOTLIN_2_4) // возможности языка
apiVersion.set(KotlinVersion.KOTLIN_2_2) // доступный API stdlib
}
}
```
| Опция | Отвечает за | Типичная ошибка |
|---|---|---|
| `jvmTarget` | формат `.class` | оставить дефолтный 1.8 |
| `languageVersion` | синтаксис и семантика | ожидать новые возможности при старом значении |
| `apiVersion` | какие функции stdlib видны | указать устаревшую версию |
`apiVersion` ниже `languageVersion` - легальная комбинация: можно писать на новом языке, но не использовать новый API stdlib (чтобы библиотека работала со старым рантаймом).
Важно: устаревшая `apiVersion` сама даёт предупреждение. В Kotlin 2.4 значение 2.1 выдаёт «API version 2.1 is deprecated», и с `allWarningsAsErrors` это ломает сборку. Подробнее: [Gradle и Kotlin DSL](Gradle%20и%20Kotlin%20DSL.md)
## Progressive mode
```kotlin
// build.gradle.kts
kotlin {
compilerOptions {
progressiveMode.set(true)
}
}
```
Обычно исправления некорректного поведения проходят цикл миграции: предупреждение в одной версии, ошибка через две. Progressive mode применяет их сразу. Код, написанный в этом режиме, совместим с обычным, но не наоборот. Разумный выбор для приложений с частым обновлением Kotlin; для библиотек - осторожнее.
## Плагины компилятора
K2 поддерживает плагины, работающие на уровне FIR/IR - они видят типы и могут генерировать код:
| Плагин | Что делает |
|---|---|
| `kotlinx-serialization` | генерирует сериализаторы при компиляции |
| `all-open` | делает классы `open` (для Spring, Mockito) |
| `no-arg` | добавляет конструктор без аргументов (JPA) |
| `kotlin-spring` | предустановленный `all-open` для Spring-аннотаций |
| `kotlin-jpa` | предустановленный `no-arg` для JPA |
| `kotlin-lombok` | понимание Lombok-геттеров в смешанном коде |
| `power-assert` | подробные сообщения в `assert` |
| `compose` | оптимизация composable-функций |
```kotlin
// build.gradle.kts
plugins {
kotlin("jvm") version "2.4.0"
kotlin("plugin.serialization") version "2.4.0"
kotlin("plugin.spring") version "2.4.0"
kotlin("plugin.jpa") version "2.4.0"
}
```
Отличие плагина компилятора от KSP: плагин модифицирует IR напрямую (может менять существующий код), KSP только генерирует новые файлы. Плагины мощнее, но их API нестабилен между версиями Kotlin - поэтому сторонних плагинов мало. Подробнее: [KSP](KSP.md)
## Инкрементальная компиляция
```
# gradle.properties
kotlin.incremental=true
kotlin.incremental.useClasspathSnapshot=true
kotlin.daemon.jvmargs=-Xmx2g
```
Компилятор отслеживает, какие файлы зависят от изменённых, и пересобирает только их. Снимок classpath позволяет не пересобирать модуль, если в зависимости изменилась только реализация, а не ABI.
Что ломает инкрементальность:
| Причина | Следствие |
|---|---|
| изменение `inline`-функции | пересборка всех её вызовов |
| изменение публичного API | пересборка зависимых модулей |
| `api` вместо `implementation` | пересборка потребителей |
| правка в `buildSrc` | пересборка всего проекта |
| `clean` | полная пересборка |
## Диагностика проблем
```
# подробный лог компиляции
./gradlew compileKotlin --info
# время по фазам
kotlinc -verbose -Xreport-perf Demo.kt -d out
# что попало в байт-код
kotlinc Demo.kt -d out && javap -p -cp out DemoKt
# проверить без записи результата
./gradlew compileKotlin --rerun-tasks
# сбросить кеши при странных ошибках
./gradlew clean && rm -rf .gradle .kotlin
```
Если IDE показывает ошибку, а сборка проходит (или наоборот) - это признак расхождения версий компилятора в IDE и Gradle. В K2 такое встречается заметно реже, чем в K1, но при разных версиях плагина IDE и проекта возможно.
> [!info] Отличия от Java
> | Java | Kotlin |
> |---|---|
> | `javac` - один фронтенд и бэкенд | FIR (фронтенд) + IR (бэкенды) |
> | компилятор только для JVM | JVM, JS, Native, Wasm |
> | нет анализа потока для приведений | умные приведения |
> | `instanceof` с pattern matching (16+) | `is` + автоматическое приведение |
> | аннотационные процессоры (APT) | плагины компилятора + KSP |
> | `-Xlint` | `-Wextra`, `-Xreturn-value-checker` |
> | IDE имеет свой анализатор | K2 общий для IDE и сборки |
>
> **Общий фронтенд для IDE и компилятора - главное инфраструктурное отличие.** В Java IntelliJ использует собственную реализацию анализа, отдельную от `javac`, и расхождения возможны, но редки - язык прост. Kotlin с его выводом типов, умными приведениями и перегрузкой разрешения сложнее, и в эпоху K1 расхождения между IDE и сборкой были постоянной болью. K2 устранил причину: это один и тот же код.
>
> **Умные приведения заменяют то, что в Java решается pattern matching.** Java 16 добавил `if (o instanceof String s)` - явное связывание переменной. Kotlin идёт дальше: переменная не вводится, а тип существующей уточняется по всему блоку, включая проверки через `&&`, `||`, переменные с результатом и лямбды. Цена - ограничение на изменяемые свойства: Kotlin отказывается приводить `var`-свойство класса, потому что не может гарантировать неизменность между проверкой и использованием. Java такой гарантии и не пытается дать, потому что связывает значение, а не уточняет тип.
>
> **Плагины компилятора - то, чего в Java осознанно нет.** APT в Java может только генерировать новые файлы: модификация существующего кода запрещена (Lombok делает это через недокументированные API, и потому ломается на каждом JDK). Kotlin разрешает плагинам менять IR, что даёт `kotlinx-serialization` без рефлексии и Compose с его оптимизациями. Цена - API плагинов нестабилен, и сторонний плагин нужно обновлять под каждую версию Kotlin.
>
> **Многоплатформенность определила архитектуру.** `javac` компилирует в байт-код JVM - это его единственная задача. Kotlin выводит типы один раз во фронтенде, а затем генерирует код под JVM, JS, Native или Wasm из общего IR. Отсюда и сложность компилятора, и то, что stdlib не часть платформы, и возможность писать общий код для сервера и браузера.
>
> Сравнить: [javac](../../Программирование%20на%20Java/JDK/javac.md), [JIT](../../Программирование%20на%20Java/JVM/JIT.md)
> [!warning] Подводные камни
> **Умное приведение не работает для `var`-свойства класса:**
> ```kotlin
> class Holder { var value: String? = "есть" }
>
> fun main() {
> val h = Holder()
> // if (h.value != null) println(h.value.length) // ошибка компиляции
> val snapshot = h.value
> println(if (snapshot != null) snapshot.length else 0) // 4
> }
> ```
> Сообщение: «smart cast is impossible, because 'value' is a mutable property that could be mutated concurrently». Это не обойти флагом - нужна локальная копия.
>
> **`-Xexplicit-api` обнаруживает проблему поздно.** Включение на существующей библиотеке даёт сотни ошибок сразу. Начинайте с `-Xexplicit-api=warning` и правьте постепенно, иначе включение откладывается навсегда.
>
> **Экспериментальные флаги `-X` не гарантируют совместимость.** Возможность может измениться или исчезнуть в следующей версии. Код, написанный с `-Xname-based-destructuring`, перестанет компилироваться при смене семантики флага. Для production-кода используйте только стабильные возможности.
>
> **`progressiveMode` - односторонний переход.** Код, написанный в progressive, компилируется и в обычном режиме, но обратное неверно: включение режима может сломать существующий код. Для библиотек, собираемых пользователями, это риск.
>
> **Устаревшая `apiVersion` вместе с `-Werror` роняет сборку.** В Kotlin 2.4 `apiVersion = 2.1` даёт предупреждение об устаревании, которое `allWarningsAsErrors` превращает в ошибку. Поддерживается около трёх предыдущих версий.
>
> **Изменение `inline`-функции обнуляет инкрементальность её вызовов.** Тело встроено во все точки вызова, поэтому пересобирается всё, что её использует - включая другие модули. `inline` в публичном API библиотеки фиксирует реализацию в ABI.
>
> **Нехватка памяти демона маскируется под медленную сборку.** Без `kotlin.daemon.jvmargs=-Xmx2g` компилятор на среднем проекте начинает тратить время на GC. Ошибки нет - просто сборка идёт в разы дольше.
>
> **`-Xbackend-threads` не всегда ускоряет.** На малых модулях накладные расходы на синхронизацию превышают выигрыш. Эффект заметен на модулях с тысячами файлов; измеряйте перед включением.
>
> **Расхождение IDE и сборки означает разные версии компилятора.** Плагин Kotlin в IDE и версия в `build.gradle.kts` должны соответствовать. Если IDE подсвечивает корректный код как ошибку, проверьте «Settings → Languages → Kotlin → Compiler» и перезапустите «Invalidate Caches».
>
> **`-Xreport-perf` без `-verbose` ничего не печатает.** Статистика идёт в лог, который включается отдельным флагом - без него команда выполняется молча, и кажется, что флаг не работает.
>
> **Плагин компилятора фиксирует версию Kotlin.** Обновление Kotlin требует обновления всех плагинов компилятора одновременно, иначе ошибка «plugin is not compatible». Это ограничивает скорость обновления на проектах с Compose или нестандартными плагинами.
Далее: [KSP](KSP.md)