A major release just around the corner — meet Kotlin 1.3-M2

Posted on by

Moving full steam towards Kotlin 1.3, we’re happy to announce the second milestone release, Kotlin 1.3-M2, which unveils new features and improves the stability of those already announced. Some highlights:

Contracts improve smart-casts and other compile-time analyses

New Standard Library functions for unsigned types and collections

Reflection for coroutines

A migration layer to aid migration onto new coroutines being graduated in 1.3

Numerous bugfixes related to inline classes

We’d like to thank our external contributors whose commits were included in this release: Leonardo Colman Lopes and Pap Lőrinc.

The complete list of changes in this release can be found in the changelog.

We appreciate your feedback regarding the new features or any problems that you may run into with this release.

Contracts

In 1.3-M2 we introduce contracts, a new experimental addition to the Kotlin type system. Let’s consider a motivating example:

//sampleStart fun test(x: Any?) { require(x is String) println("'$x' length is ${x.length}") } //sampleEnd fun main(args: Array<String>) { test("A string") }

Here, require() is a function defined in the Standard Library that throws an exception when its argument is false (similar to assert() , but doesn’t depend on the -ea flag). Thus, if the println() gets executed, then x has to be a String, and a smart cast would be very logical in this line. The problem is: the compiler normally only looks at function signatures, so it knows nothing about how require() is implemented, and thus can’t infer that x is now certainly an instance of String.

With contracts, a function can tell the compiler things like “I affect smart casts this way” or “I execute this lambda immediately and exactly once”. Contracts enrich the type information available through the function signature (“I take a nullable list of T and return a Boolean”) with additional meanings useful at the call site, e.g. “I only return false for non-null lists”.

Kotlin standard library already comes with contracts added to several functions. They work regardless of the experimental flags.

Currently, the Kotlin compiler uses contracts for two types of improvements:

Making smart casts even smarter:

//sampleStart fun test(x: List<Int>?) { // If the function returns false, the value is definitely not null: if (!x.isNullOrEmpty()) { println(x.size) // Yay, smart cast to not-null! } } fun test(x: Any?) { // If the function returns (does not throw), then the argument is true: require(x is String) println(x.length) // Smart cast here too! } //sampleEnd fun main(args: Array<String>) { test(List(42) { 0 }) test("Hello, world!") }

Applies for functions: check , checkNotNull , require , requireNotNull , kotlin.test.assertTrue , kotlin.test.assertFalse , kotlin.test.assertNotNull , isNullOrEmpty , isNullOrBlank

Analyzing variable initialization more accurately:

private val lock = Any() fun main(args: Array<String>) { //sampleStart val x: Int synchronized(lock) { x = 42 // The compiler knows that the lambda is invoked exactly once! } println(x) // Allowed now, the compiler knows that 'x' is definitely assigned. //sampleEnd }

Applies for functions: run , let , with , apply , also , takeIf , takeUnless , synchronized

While the contracts of the standard functions listed above are stable, the declaration site syntax and APIs for defining contracts may be changed in the future. To specify contracts for your own functions, you have to opt into the experimental mode by using the @ExperimentalContracts annotation:

import kotlin.contracts.* @ExperimentalContracts fun myRun(block: () -> Unit) { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } block() }

Note that the contracts are not yet verified at the declaration site, so it’s your responsibility to provide accurate information there. See this KEEP for the syntax, as well as for compatibility guarantees and general design overview.

Kotlin 1.3 graduates coroutines to stable changing several things in the APIs and ABIs based on the feedback we gathered during the experimental period. In the previous milestone, 1.3-M1, there was no migration support bridging the old experimental coroutines with new stable ones. Now, in 1.3-M2 we provide such support: you can simply call old suspend functions.

Also, Kotlin reflection now supports introspection for suspend functions:

KCallable.isSuspend

KCallable.callSuspend

KCallable.callSuspendBy

The latter two allow for calling suspend functions via reflection, so they are themselves marked suspend .

Standard Library

Unsigned types

The library support has been improved for the unsigned integer types. Let’s walk through what has been introduced to make the unsigned types and arrays of unsigned types feel more like first-class citizens:

Functions for converting unsigned integers to and from strings in an arbitrary base: UInt.toString(base) , String.toUInt(base) etc.

fun main(args: Array<String>) { //sampleStart val uint = "4275878552".toUInt() println(uint.toString(16)) println("FF".toUByte(16)) //sampleEnd }

Extension functions until , downTo , step and reversed to support more ways of creating ranges and progressions of unsigned integers.

fun main(args: Array<String>) { //sampleStart println(1u..UInt.MAX_VALUE) println(1u until UInt.MAX_VALUE) println(UInt.MAX_VALUE downTo 0u) println(0u..UInt.MAX_VALUE step 10) println((0u..UInt.MAX_VALUE step 10).reversed()) //sampleEnd }

The arrays of unsigned integers do not implement the structural equality contract of List , same as the arrays of signed integers. To make it easy comparing these arrays or showing their contents as string the contentEquals(other) , contentHashCode() , contentToString() functions were introduced.

fun main(args: Array<String>) { //sampleStart val arr1 = UIntArray(5) { it.toUInt() } val arr2 = uintArrayOf(0u, 1u, 2u, 3u, 4u) println(arr1 contentEquals arr2) println(arr1.contentToString()) println(arr1.contentHashCode()) //sampleEnd }

Secondary constructors of unsigned arrays to create a zero-initialized array of unsigned integers of a given size.

fun main(args: Array<String>) { //sampleStart val uintArrayOfZeroes = UIntArray(16) println(uintArrayOfZeroes.contentToString()) //sampleEnd }

Extension functions to copy unsigned integer arrays and their subranges: copyOf() , copyOf(newSize) , copyOfRange(fromIndex, toIndex) .

fun main(args: Array<String>) { //sampleStart val array = ubyteArrayOf(0x01u, 0x02u, 0xFEu, 0xFFu) println(array.copyOf().contentToString()) println(array.copyOf(2).contentToString()) println(array.copyOf(5).contentToString()) println(array.copyOfRange(2, 4).contentToString()) //sampleEnd }

Extensions for reinterpreting an array of unsigned integers as an array of signed ones, and vice versa: ByteArray.as/toUByteArray() , UByteArray.as/toByteArray() etc.

fun main(args: Array<String>) { //sampleStart val ubyteArray = ubyteArrayOf(1u, 2u, 254u, 255u) val byteArray = ubyteArray.asByteArray() println(byteArray.contentToString()) byteArray[0] = -1 println(ubyteArray.contentToString()) //sampleEnd }

Extensions for converting a specialized array of unsigned integers to an object array: UIntArray.toTypedArray() which returns an Array<UInt> , etc

Copying elements between two existing arrays

The array.copyInto(targetArray, targetOffset, startIndex, endIndex) functions for the existing array types, including the unsigned arrays, make it easier to implement array-based containers in pure Kotlin.

fun main(args: Array<String>) { //sampleStart val sourceArr = arrayOf("k", "o", "t", "l", "i", "n") val targetArr = sourceArr.copyInto(arrayOfNulls<String>(6), 3, startIndex = 3, endIndex = 6) println(targetArr.contentToString()) sourceArr.copyInto(targetArr, startIndex = 0, endIndex = 3) println(targetArr.contentToString()) //sampleEnd }

associateWith

It is quite common situation when you have a list of keys and you want to build a map by associating each of these keys with some value. It was possible to do it before with the associate { it to getValue(it) } function, but now we’re introducing a more efficient and easy to explore alternative: keys.associateWith { getValue(it) }

fun main(args: Array<String>) { //sampleStart val keys = 'a'..'f' val map = keys.associateWith { it.toString().repeat(5).capitalize() } map.forEach { println(it) } //sampleEnd }

ifEmpty and ifBlank functions

Collections, maps, object arrays, char sequences and sequences now have ifEmpty function, which allows to specify a substitute value that will be used instead of the receiver if it is empty.

fun main(args: Array<String>) { //sampleStart fun printAllUppercase(data: List<String>) { val result = data .filter { it.all { c -> c.isUpperCase() } } .ifEmpty { listOf("<no uppercase>") } result.forEach { println(it) } } printAllUppercase(listOf("foo", "Bar")) printAllUppercase(listOf("FOO", "BAR")) //sampleEnd }

Char sequences and strings in addition have ifBlank extension that does the same thing as ifEmpty , but checks for the string being all whitespace instead of empty.

fun main(args: Array<String>) { //sampleStart val s = "

" println(s.ifBlank { "<blank>" }) println(s.ifBlank { null }) //sampleEnd }

Random API improvements

The Random class, introduced in the previous 1.3-M1 release, has received extensions for generating unsigned numbers and arrays of unsigned bytes, thanks to our contributor Leonardo Colman Lopes: these are nextUInt , nextULong and nextUBytes extensions:

import kotlin.random.* fun main(args: Array<String>) { //sampleStart val randomUInt = Random.nextUInt(0x8000_0000u..0xFF00_0000u) println(randomUInt.toString(16)) val randomUBytes = Random.nextUBytes(4) println(randomUBytes.contentToString()) //sampleEnd }

Also we’re introducing a helpful extension for collections, arrays and even ranges to get a random element from among all of the elements of that collection/array/range. It’s called just random() :

fun main(args: Array<String>) { //sampleStart val sweets = listOf("nougat", "marshmallow", "oreo", "pie") println("I'm going to taste some ${sweets.random()} today!") val chars = 'a'..'z' val randomChars = String(CharArray(10) { chars.random() }) println(randomChars) //sampleEnd }

Sealed classes reflection

We’ve added a new API to kotlin-reflect that can be used to enumerate all direct subtypes of a sealed class, namely KClass.sealedSubclasses .

Inline classes

In 1.3-M2 we introduce automatic mangling for names of functions that use inline classes in their signatures. We do it to prevent platform signature clashes when there are several overloads which are different only in the inline type, but not in the carrier type:

// All these types are inline classes mapped to String: fun foo(x: UserName) { ... } fun foo(x: Login) { ... } fun foo(x: UserHash) { ... }

Another reason for mangling is to forbid (accidental) usage from Java, which may be undesired as inline classes are a purely Kotlin concept.

Mangled names will have the form of foo-<some string based on the signature>

Another two improvements for inline classes in this release are:

the support for secondary constructors

automatically generated toString / equals / hashCode functions

Language changes

We’re deprecating the support for older source language versions through the -language-version flag with values 1.0 and 1.1 for kotlinc-1.3 and above. Note that this change only affects compilation of source code for old target versions; all binaries ever compiled with stable language versions will be supported by later versions of kotlinc. To compile your sources with Kotlin 1.0 and 1.1 you can use the corresponding old versions of kotlinc.

Also, certain new restrictions were added in this update aimed to ensure stable and well-defined behavior regarding some error-prone constructs:

Misleading labels on statements other than loops, inline lambdas, and inline anonymous functions, such as foo@ val bar = 1 , had no real meaning or use case and are now prohibited (KT-22274)

, had no real meaning or use case and are now prohibited (KT-22274) Getter-targeted annotations on annotation constructor parameters, which were not represented in the binaries and thus never worked, are now written to the annotation class getter methods (KT-25287)

Unresolved reference errors that were missing in data class constructor’s @get: annotations are now properly reported (KT-19628)

Pre-release notes

Note that the backward compatibility guarantees do not cover pre-release versions: the features and API can change in subsequent releases. When we reach a final RC, all binaries produced by pre-release versions will be outlawed by the compiler, and you will be required to recompile everything that was compiled by 1.3‑Mx.

However, all the code compiled by 1.2.x and earlier releases will be perfectly fine then without recompilation.

How to Try It

In Maven/Gradle: Add http://dl.bintray.com/kotlin/kotlin-eap as a repository for the build script and your projects; use 1.3-M2 as the version number for the compiler plugin and the standard library.

In IntelliJ IDEA: Go to Tools → Kotlin → Configure Kotlin Plugin Updates, then select “Early Access Preview 1.3” in the Update channel drop-down list, and then click Check for updates.

The command-line compiler can be downloaded from the Github release page.

On try.kotlinlang.org: Use the drop-down list in the bottom right-hand corner to change the compiler version to 1.3‑M2.

Let’s Kotlin!