Что нового в Kotlin 1.7.20
Выпущено: 29 сентября 2022 года
Вышел релиз Kotlin 1.7.20! Вот некоторые ключевые моменты этого релиза:
Мы представили предварительную версию оператора
..<для создания открытых диапазоновНовый менеджер памяти Kotlin/Native теперь включён по умолчанию
Мы представили новую экспериментальную функцию для JVM: inline-классы с универсальным базовым типом
Вы также можете найти краткий обзор изменений в этом видео:
Поддержка плагинов компилятора Kotlin K2
Команда Kotlin продолжает стабилизировать компилятор K2. K2 всё ещё находится в стадии Альфа (как объявлено в релизе Kotlin 1.7.0), но теперь поддерживает несколько плагинов компилятора. Вы можете следить за обновлениями от команды Kotlin по новому компилятору в этой задаче YouTrack.
Начиная с этого релиза 1.7.20, компилятор Kotlin K2 поддерживает следующие плагины:
AtomicFU
jvm-abi-gen
Узнайте больше о новом компиляторе и его преимуществах в следующих видео:
Как включить компилятор Kotlin K2
Для включения компилятора Kotlin K2 и его тестирования используйте следующий параметр компилятора:
-Xuse-k2
Вы можете указать его в вашем файле build.gradle(.kts):
tasks.withType<KotlinCompile> {
kotlinOptions.useK2 = true
}
compileKotlin {
kotlinOptions.useK2 = true
}
Проверьте прирост производительности в ваших проектах JVM и сравните его с результатами старого компилятора.
Оставьте свой отзыв о новом компиляторе K2
Мы очень ценим ваш отзыв в любой форме:
Поделитесь своим отзывом напрямую с разработчиками K2 в Kotlin Slack: получите приглашение и присоединитесь к каналу #k2-early-adopters.
Сообщите о любых проблемах, с которыми вы столкнулись, при использовании нового компилятора K2, в нашем трекере задач.
Включите опцию Отправка статистических данных об использовании, чтобы позволить JetBrains собирать анонимные данные об использовании K2.
Язык
Kotlin 1.7.20 вводит предварительные версии новых языковых функций, а также накладывает ограничения на вывод типов билдера:
Предварительный просмотр оператора ..< для создания открытых диапазонов
В этом релизе представлен новый ..< оператор. В Kotlin есть .. оператор для выражения диапазона значений. Новый ..< оператор действует как функция until и помогает определять открытый диапазон.
Наши исследования показывают, что этот новый оператор лучше выражает открытые диапазоны и четко указывает, что верхняя граница не включена.
Вот пример использования оператора ..< в выражении when.
when (value) {
in 0.0..<0.25 -> // First quarter
in 0.25..<0.5 -> // Second quarter
in 0.5..<0.75 -> // Third quarter
in 0.75..1.0 -> // Last quarter <- Note closed range here
}
Изменения в API стандартной библиотеки
В следующих новых типах и операциях будут представлены пакеты kotlin.ranges в общей стандартной библиотеке Kotlin:
Новый интерфейс OpenEndRange
Новый интерфейс для представления открытых диапазонов очень похож на существующий интерфейс ClosedRange<T>.
interface OpenEndRange<T : Comparable<T>> {
// Lower bound
val start: T
// Upper bound, not included in the range
val endExclusive: T
operator fun contains(value: T): Boolean = value >= start && value < endExclusive
fun isEmpty(): Boolean = start >= endExclusive
}
Реализация OpenEndRange в существующих итерируемых диапазонах
Когда разработчикам нужен диапазон с исключенной верхней границей, они сейчас используют функцию until для эффективного создания замкнутого итерируемого диапазона с теми же значениями. Чтобы сделать эти диапазоны приемлемыми в новом API, который принимает OpenEndRange<T>, мы хотим реализовать этот интерфейс в существующих итерируемых диапазонах: IntRange, LongRange, CharRange, UIntRange, и ULongRange. Таким образом, они будут одновременно реализовывать как интерфейс ClosedRange<T>, так и OpenEndRange<T>.
class IntRange : IntProgression(...), ClosedRange<Int>, OpenEndRange<Int> {
override val start: Int
override val endInclusive: Int
override val endExclusive: Int
}
Операторы rangeUntil для стандартных типов
Операторы rangeUntil будут предоставлены для тех же типов и комбинаций, которые в настоящее время определены оператором rangeTo. Мы предоставляем их в качестве расширяющих функций в целях прототипирования, но для согласованности планируем сделать их членами позже, прежде чем стабилизировать API открытых диапазонов.
Как включить оператор ..<
Чтобы использовать оператор ..< или реализовать соглашение об этом операторе для собственных типов, включите опцию компилятора -language-version 1.8.
Новые элементы API, введенные для поддержки открытых диапазонов стандартных типов, требуют опциональной подписки, как обычно для API экспериментальной stdlib: @OptIn(ExperimentalStdlibApi::class). В качестве альтернативы, можно использовать опцию компилятора -opt-in=kotlin.ExperimentalStdlibApi.
Улучшенные строковые представления для одиночных элементов и иерархий sealed классов с data object
В этом релизе представлен новый тип объявления object для использования: data object. Data object концептуально идентичен обычному object объявлению, но поставляется с чистым toString представлением по умолчанию.
package org.example
object MyObject
data object MyDataObject
fun main() {
println(MyObject) // org.example.MyObject@1f32e575
println(MyDataObject) // MyDataObject
}
Это делает объявления data object идеальными для иерархий sealed классов, где их можно использовать вместе с объявлениями data class. В этом примере объявление EndOfFile как data object вместо простого object означает, что оно получит красивое toString представление без необходимости ручного переопределения, сохраняя симметрию с соответствующими определениями data class.
sealed class ReadResult {
data class Number(val value: Int) : ReadResult()
data class Text(val value: String) : ReadResult()
data object EndOfFile : ReadResult()
}
fun main() {
println(ReadResult.Number(1)) // Number(value=1)
println(ReadResult.Text("Foo")) // Text(value=Foo)
println(ReadResult.EndOfFile) // EndOfFile
}
Как включить data object
Чтобы использовать объявления data object в своем коде, включите опцию компилятора -language-version 1.9. В проекте Gradle вы можете сделать это, добавив следующее в build.gradle(.kts).
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
// ...
kotlinOptions.languageVersion = "1.9"
}
compileKotlin {
// ...
kotlinOptions.languageVersion = '1.9'
}
Подробнее о data object и поделитесь своими отзывами об их реализации в соответствующем документе KEEP.
Новые ограничения вывода типов билдера
Kotlin 1.7.20 накладывает некоторые существенные ограничения на использование вывода типов билдера, которые могут повлиять на ваш код. Эти ограничения применяются к коду, содержащему лямбда-функции билдера, где невозможно вывести параметр без анализа самой лямбды. Параметр используется в качестве аргумента. Теперь компилятор всегда будет показывать ошибку для такого кода и попросит указать тип явно.
Это изменение, вносящее разрыв в работе, но наши исследования показывают, что такие случаи встречаются очень редко, и ограничения не должны повлиять на ваш код. Если это произойдет, рассмотрите следующие случаи:
-
Вывод типа билдера с расширением, скрывающим члены.
Если ваш код содержит расширяющую функцию с тем же именем, которая будет использоваться во время вывода типа билдера, компилятор отобразит ошибку:
class Data { fun doSmth() {} // 1 } fun <T> T.doSmth() {} // 2 fun test() { buildList { this.add(Data()) this.get(0).doSmth() // Resolves to 2 and leads to error } }Для исправления кода следует указать тип явно:
class Data { fun doSmth() {} // 1 } fun <T> T.doSmth() {} // 2 fun test() { buildList<Data> { // Type argument! this.add(Data()) this.get(0).doSmth() // Resolves to 1 } } -
Вывод типа билдера с несколькими лямбдами и типы аргументов не указаны явно.
Если в выводе типа билдера есть два или более блоков лямбда-функций, они влияют на тип. Чтобы предотвратить ошибку, компилятор требует указать тип:
fun <T: Any> buildList( first: MutableList<T>.() -> Unit, second: MutableList<T>.() -> Unit ): List<T> { val list = mutableListOf<T>() list.first() list.second() return list } fun main() { buildList( first = { // this: MutableList<String> add("") }, second = { // this: MutableList<Int> val i: Int = get(0) println(i) } ) }Для исправления ошибки необходимо явно указать тип и исправить несовпадение типов:
fun main() { buildList<Int>( first = { // this: MutableList<Int> add(0) }, second = { // this: MutableList<Int> val i: Int = get(0) println(i) } ) }
Если вы не нашли свой случай в вышеупомянутых, отправьте запрос нашей команде.
См. эту задачу YouTrack для получения дополнительной информации об этом обновлении вывода типа билдера.
Kotlin/JVM
Kotlin 1.7.20 добавляет дженерики для инлайн-классов, добавляет больше оптимизаций байткода для делегируемых свойств и поддерживает IR в задаче генерации плагина kapt, что позволяет использовать все новейшие функции Kotlin с kapt:
Дженерические инлайн-классы
Kotlin 1.7.20 позволяет использовать базовый тип JVM инлайн-классов в качестве параметра типа. Компилятор сопоставляет его с Any? или, в общем случае, с верхним пределом параметра типа.
Рассмотрим следующий пример:
@JvmInline
value class UserId<T>(val value: T)
fun compute(s: UserId<String>) {} // Compiler generates fun compute-<hashcode>(s: Any?)
Функция принимает инлайн-класс в качестве параметра. Параметр сопоставляется с верхним пределом, а не с аргументом типа.
Чтобы включить эту функцию, используйте опцию компилятора -language-version 1.8.
Мы будем рады вашим отзывам на эту функцию в YouTrack.
Более оптимизированные случаи делегируемых свойств
В Kotlin 1.6.0 мы оптимизировали случай делегирования к свойству, исключив поле $delegate и сгенерировав прямой доступ к целевому свойству. В 1.7.20 мы реализовали эту оптимизацию для большего количества случаев. Поле $delegate теперь будет опущено, если делегат:
-
имеет имя:
object NamedObject { operator fun getValue(thisRef: Any?, property: KProperty<*>): String = ... } val s: String by NamedObject -
является конечным
valсвойством с поддерживающим полем и стандартным геттером в том же модуле:val impl: ReadOnlyProperty<Any?, String> = ... class A { val s: String by impl } -
является константным выражением, элементом перечисления,
this, илиnull. Вот примерthis:class A { operator fun getValue(thisRef: Any?, property: KProperty<*>) ... val s by this }
Узнайте больше о делегируемых свойствах.
Мы будем рады вашим отзывам на эту функцию в YouTrack.
Поддержка JVM IR бэкенда в задаче генерации плагина kapt
До версии 1.7.20 задача генерации плагина kapt использовала старый бэкенд, и повторяющиеся аннотации не работали с kapt. С Kotlin 1.7.20 мы добавили поддержку JVM IR бэкенда в задачу генерации плагина kapt. Это позволяет использовать все новейшие функции Kotlin с kapt, включая повторяющиеся аннотации.
Чтобы использовать IR бэкенд в kapt, добавьте следующую опцию в свой файл gradle.properties:
kapt.use.jvm.ir=true
Мы будем рады вашим отзывам на эту функцию в YouTrack.
Kotlin/Native
Kotlin 1.7.20 поставляется с новым менеджером памяти Kotlin/Native, включённым по умолчанию, и даёт вам возможность настроить файл Info.plist:
Новый менеджер памяти Kotlin/Native по умолчанию
Этот релиз приносит дальнейшую стабильность и улучшения производительности новому менеджеру памяти, позволяя нам продвинуть его до Бета-версии.
Предыдущий менеджер памяти усложнял написание конкурентного и асинхронного кода, в том числе с проблемами реализации библиотеки kotlinx.coroutines. Это препятствовало внедрению Kotlin Multiplatform Mobile, так как ограничения по конкурентности создавали проблемы с совместным использованием Kotlin-кода между платформами iOS и Android. Новый менеджер памяти наконец прокладывает путь к переходу Kotlin Multiplatform Mobile к Бета-версии.
Новый менеджер памяти также поддерживает кэш компилятора, что делает время компиляции сопоставимым с предыдущими выпусками. Более подробную информацию о преимуществах нового менеджера памяти вы найдете в нашей первоначальной статье блога для предварительной версии. Более подробные технические сведения вы найдете в документации.
Настройка
Начиная с Kotlin 1.7.20, новый менеджер памяти используется по умолчанию. Дополнительной настройки не требуется.
Если вы уже включили его вручную, вы можете удалить опцию kotlin.native.binary.memoryModel=experimental из вашего файла gradle.properties или binaryOptions["memoryModel"] = "experimental" из файла build.gradle(.kts).
При необходимости вы можете переключиться на устаревший менеджер памяти с помощью опции kotlin.native.binary.memoryModel=strict в вашем файле gradle.properties. Однако поддержка кэша компилятора для устаревшего менеджера памяти больше не доступна, поэтому время компиляции может ухудшиться.
Замораживание
В новом менеджере памяти замораживание устарело. Не используйте его, если ваш код не должен работать с устаревшим менеджером (где замораживание всё ещё необходимо). Это может быть полезно для авторов библиотек, которым нужно поддерживать совместимость с устаревшим менеджером памяти, или для разработчиков, которые хотят иметь резервный вариант, если у них возникнут проблемы с новым менеджером памяти.
В таких случаях вы можете временно поддерживать код как для нового, так и для устаревшего менеджеров памяти. Чтобы проигнорировать предупреждения об устаревании, сделайте следующее:
Добавьте аннотацию к использованию устаревшего API с помощью
@OptIn(FreezingIsDeprecated::class).Примените
languageSettings.optIn("kotlin.native.FreezingIsDeprecated")ко всем наборам исходных кодов Kotlin в Gradle.Передайте флаг компилятора
-opt-in=kotlin.native.FreezingIsDeprecated.
Вызов отложенных функций Kotlin из Swift/Objective-C
Новый менеджер памяти всё ещё ограничивает вызов Kotlin suspend функций из Swift и Objective-C с потоков, отличных от основного, но вы можете снять это ограничение с помощью новой опции Gradle.
Это ограничение изначально было введено в устаревшем менеджере памяти из-за случаев, когда код отправлял продолжение, чтобы оно возобновило работу в исходном потоке. Если у этого потока не было поддерживаемой очереди событий, задача никогда не запускалась, и сопрограмма никогда не возобновлялась.
В некоторых случаях это ограничение больше не требуется, но проверить все необходимые условия сложно. По этой причине мы решили сохранить его в новом менеджере памяти, добавив опцию для его отключения. Для этого добавьте следующую опцию в свой gradle.properties:
kotlin.native.binary.objcExportSuspendFunctionLaunchThreadRestriction=none
Команда Kotlin выражает огромную благодарность Ахмеду Эль-Хелви за реализацию этой опции.
Оставьте свой отзыв
Это существенное изменение в нашей экосистеме. Мы ценим ваш отзыв, чтобы сделать его ещё лучше.
Попробуйте новый менеджер памяти в своих проектах и поделитесь отзывами на нашем портале отслеживания задач YouTrack.
Настройка файла Info.plist
При создании фреймворка компилятор Kotlin/Native генерирует файл свойств Info.plist, Info.plist. Раньше настройка его содержимого была сложной задачей. С Kotlin 1.7.20 вы можете напрямую задать следующие свойства:
Свойство |
Двоичный вариант |
|---|---|
|
|
|
|
|
|
Для этого используйте соответствующий двоичный вариант. Передайте флаг компилятора -Xbinary=$option=$value или задайте binaryOption(option, value) Gradle DSL для необходимого фреймворка.
Команда Kotlin выражает огромную благодарность Мадсу Агеру за реализацию этой функции.
Kotlin/JS
Kotlin/JS получил ряд улучшений, которые повышают удобство разработки и производительность:
Генерация Klib стала быстрее как в инкрементных, так и в чистых сборках благодаря оптимизациям для загрузки зависимостей.
Инкрементная компиляция для бинарных файлов разработки была переработана, что привело к значительным улучшениям в сценариях чистых сборок, более быстрой инкрементной сборке и устранению проблем со стабильностью.
Мы улучшили генерацию
.d.tsдля вложенных объектов, sealed-классов и необязательных параметров в конструкторах.
Gradle
Обновления плагина Kotlin Gradle фокусируются на совместимости с новыми функциями Gradle и последними версиями Gradle.
Kotlin 1.7.20 содержит изменения для поддержки Gradle 7.1. Устаревшие методы и свойства были удалены или заменены, что уменьшило количество предупреждений об устаревании, выдаваемых плагином Kotlin Gradle, и открыло путь для будущей поддержки Gradle 8.0.
Однако существуют потенциально критические изменения, которые могут потребовать вашего внимания:
Настройка целевых платформ
org.jetbrains.kotlin.gradle.dsl.SingleTargetExtensionтеперь имеет параметр дженериков,SingleTargetExtension<T : KotlinTarget>.Конвенция
kotlin.targets.fromPreset()устарела. Вместо неё вы по-прежнему можете использоватьkotlin.targets { fromPreset() }, но мы рекомендуем использовать более специализированные способы создания целевых платформ.-
Автоматически сгенерированные Gradle-аксессоры к целевым платформам больше недоступны внутри блока
kotlin.targets { }. Используйте вместо этого методfindByName("targetName").Обратите внимание, что такие аксессоры по-прежнему доступны в случае
kotlin.targets, например,kotlin.targets.linuxX64.
Настройка каталогов исходных кодов
Плагин Kotlin Gradle теперь добавляет Kotlin SourceDirectorySet в качестве kotlin расширения к группе Java SourceSet. Это позволяет настроить каталоги исходных кодов в файле build.gradle.kts аналогично тому, как это делается в Java, Groovy и Scala:
sourceSets {
main {
kotlin {
java.setSrcDirs(listOf("src/java"))
kotlin.setSrcDirs(listOf("src/kotlin"))
}
}
}
Вам больше не нужно использовать устаревшую конвенцию Gradle и указывать каталоги исходных кодов для Kotlin.
Помните, что вы также можете использовать расширение kotlin для доступа к KotlinSourceSet:
kotlin {
sourceSets {
main {
// ...
}
}
}
Новый метод настройки JVM-цепочки инструментов
Этот выпуск предоставляет новый метод jvmToolchain() для включения функции JVM-цепочки инструментов. Если вам не нужны дополнительные параметры конфигурации, такие как implementation или vendor, вы можете использовать этот метод из расширения Kotlin:
kotlin {
jvmToolchain(17)
}
Это упрощает настройку проекта Kotlin без дополнительной конфигурации. До этого выпуска вы могли указать версию JDK только следующим образом:
kotlin {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
Стандартная библиотека
Kotlin 1.7.20 предлагает новые расширяющие функции для класса java.nio.file.Path, позволяющие вам пройтись по дереву файлов:
walk()лениво проходит по дереву файлов, укоренённому в указанном пути.fileVisitor()позволяет создатьFileVisitorотдельно.FileVisitorопределяет действия над директориями и файлами при прохождении по ним.visitFileTree(fileVisitor: FileVisitor, ...)потребляет готовыйFileVisitorи используетjava.nio.file.Files.walkFileTree()под капотом.visitFileTree(..., builderAction: FileVisitorBuilder.() -> Unit)создаётFileVisitorсbuilderActionи вызывает функциюvisitFileTree(fileVisitor, ...).FileVisitResult, тип возвращаемого значения функцииFileVisitor, имеет значение по умолчаниюCONTINUE, которое продолжает обработку файла.
Вот что вы можете сделать с этими новыми расширяющими функциями:
-
Явно создайте
FileVisitorи затем используйте:val cleanVisitor = fileVisitor { onPreVisitDirectory { directory, attributes -> // Some logic on visiting directories FileVisitResult.CONTINUE } onVisitFile { file, attributes -> // Some logic on visiting files FileVisitResult.CONTINUE } } // Some logic may go here projectDirectory.visitFileTree(cleanVisitor) -
Создайте
FileVisitorсbuilderActionи используйте его немедленно:projectDirectory.visitFileTree { // Definition of the builderAction: onPreVisitDirectory { directory, attributes -> // Some logic on visiting directories FileVisitResult.CONTINUE } onVisitFile { file, attributes -> // Some logic on visiting files FileVisitResult.CONTINUE } } -
Пройдитесь по дереву файлов, укоренённому в указанном пути, с помощью функции
walk().@OptIn(kotlin.io.path.ExperimentalPathApi::class) fun traverseFileTree() { val cleanVisitor = fileVisitor { onPreVisitDirectory { directory, _ -> if (directory.name == "build") { directory.toFile().deleteRecursively() FileVisitResult.SKIP_SUBTREE } else { FileVisitResult.CONTINUE } } onVisitFile { file, _ -> if (file.extension == "class") { file.deleteExisting() } FileVisitResult.CONTINUE } } val rootDirectory = createTempDirectory("Project") rootDirectory.resolve("src").let { srcDirectory -> srcDirectory.createDirectory() srcDirectory.resolve("A.kt").createFile() srcDirectory.resolve("A.class").createFile() } rootDirectory.resolve("build").let { buildDirectory -> buildDirectory.createDirectory() buildDirectory.resolve("Project.jar").createFile() } // Use walk function: val directoryStructure = rootDirectory.walk(PathWalkOption.INCLUDE_DIRECTORIES) .map { it.relativeTo(rootDirectory).toString() } .toList().sorted() assertPrints(directoryStructure, "[, build, build/Project.jar, src, src/A.class, src/A.kt]") rootDirectory.visitFileTree(cleanVisitor) val directoryStructureAfterClean = rootDirectory.walk(PathWalkOption.INCLUDE_DIRECTORIES) .map { it.relativeTo(rootDirectory).toString() } .toList().sorted() assertPrints(directoryStructureAfterClean, "[, src, src/A.kt]") //sampleEnd }
Как обычно для экспериментального API, новые расширения требуют включения: @OptIn(kotlin.io.path.ExperimentalPathApi::class) или @kotlin.io.path.ExperimentalPathApi. В качестве альтернативы, вы можете использовать опцию компилятора: -opt-in=kotlin.io.path.ExperimentalPathApi.
Мы будем рады получить ваши отзывы по walk() функции и расширяющим функциям посещения в YouTrack.
Обновления документации
С момента предыдущего выпуска, документация Kotlin претерпела некоторые заметные изменения:
Переработанные и улучшенные страницы
Обзор базовых типов – узнайте о базовых типах, используемых в Kotlin: числа, значения Boolean, символы, строки, массивы и беззнаковые целые числа.
IDE для разработки на Kotlin – посмотрите список IDE с официальной поддержкой Kotlin и инструментов, которые имеют поддерживаемые сообществом плагины.
Новые статьи в журнале Kotlin Multiplatform
Разработка нативных и кроссплатформенных приложений: как выбрать? – ознакомьтесь с нашим обзором и преимуществами кроссплатформенной разработки приложений и нативного подхода.
Шесть лучших кроссплатформенных фреймворков для разработки приложений – прочитайте о ключевых аспектах, которые помогут вам выбрать правильный фреймворк для вашего кроссплатформенного проекта.
Новые и обновлённые учебники
Начало работы с Kotlin Multiplatform Mobile – узнайте о кроссплатформенной разработке мобильных приложений с Kotlin и создайте приложение, которое работает как на Android, так и на iOS.
Построение полнофункционального веб-приложения с Kotlin Multiplatform – создайте приложение, используя Kotlin на всех уровнях, с частью сервера на Kotlin/JVM и веб-клиентом на Kotlin/JS.
Разработка веб-приложений с React и Kotlin/JS – создайте приложение для браузера, изучая DSL Kotlin и особенности типичной программы React.
Изменения в документации выпуска
Мы больше не предоставляем список рекомендуемых библиотек kotlinx для каждого релиза. Этот список содержал только те версии, которые рекомендованы и протестированы с самим Kotlin. Он не учитывал, что некоторые библиотеки зависят друг от друга и требуют специальной версии kotlinx, которая может отличаться от рекомендованной версии Kotlin.
Мы работаем над тем, чтобы предоставить информацию о взаимосвязях и зависимостях библиотек, чтобы было понятно, какую версию kotlinx библиотеки нужно использовать при обновлении версии Kotlin в вашем проекте.
Установка Kotlin 1.7.20
IntelliJ IDEA 2021.3, 2022.1 и 2022.2 автоматически предлагают обновить плагин Kotlin до 1.7.20.
Новый компилятор командной строки доступен для загрузки на странице релиза GitHub.
Руководство по совместимости для Kotlin 1.7.20
Хотя Kotlin 1.7.20 является инкрементным релизом, нам всё же пришлось внести несовместимые изменения, чтобы ограничить распространение проблем, возникших в Kotlin 1.7.0.
Подробный список таких изменений можно найти в руководстве по совместимости для Kotlin 1.7.20.
© 2010–2023 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/whatsnew1720.html