Language Changes in Kotlin 1.7.0

Volkan Toprak
3 min readSep 12, 2022

--

As you probably already know, Kotlin 1.7.0 has been released. This version introduces the Alpha version of the new Kotlin/JVM K2 compiler and brings performance improvements for the JVM, JS, and Native platforms.

But today, I would like to focus more on language changes of this version.

Stable Builder Inference

When calling generic builder functions, builder inference, a specific type of type inference, is helpful. By storing type information about other calls inside a call’s lambda parameter, it enables the compiler to deduce the type arguments of that call.

Before Kotlin 1.7.0, the -Xenable-builder-inference compiler option which was introduced in 1.6.0 was necessary to enable builder inference for a builder function. The option is turned on by default in version 1.7.0.

Implementation by delegation to inlined value of inline class

Now, you can construct memory-free, lightweight wrappers for the majority of cases. What that means for us?

Let’s create an Animal interface with two methods.

interface Animal { 
fun sound() = "sound"
fun sleep() = "sleep"
}

Now, it is time to create a lightweight wrapper with a value class.

@JvmInline
value class AnimalWrapper(val animal: Animal): Animal by animal
fun main() {
val animalWrapper = AnimalWrapper(object: Animal {})
println(animalWrapper.sound())
println(animalWrapper.sleep())
}

To build a lightweight wrapper for a value or class instance, all interface methods must be manually coded. Implementation by delegation solved that issue, but prior to 1.7.0, inline classes were not compatible. Therefore, if you want to try that value class in previous version, you will see an error like ‘Value class cannot implement an interface by delegation if expression is not a parameter’. However, this restriction has been removed with version 1.7.0.

Underscore operator for type arguments

An underscore operator (_) has been introduced with version 1.7.0 for type arguments.

Let’s go through an example and create an abstract class with generic parameter.

abstract class Animal<T> {
abstract fun numberOfLegs(): T
}

Now, create two implementations of this Animal class, Dog and Pig. They specify the generic parameter as String and Integer.

class Dog : Animal<String>() {
override fun numberOfLegs(): String = "4"
}
class Pig : Animal<Int>() {
override fun numberOfLegs(): Int = 4
}

It is time to create a Runner object which basically gets initial class and runs numberOfLegs function for that class.

object Runner {
inline fun <reified S: Animal<T>, T> run(): T {
return S::class.java.getDeclaredConstructor().newInstance().numberOfLegs()
}}

We can now use underscore operator to automatically infer a type argument when other types are specified.

fun main() {
val s = Runner.run<Dog, _>()
assert(s == "4")
val n = Runner.run<Pig, _>()
assert(n == 4)
}

As you can see, for the first example, T is concluded as String since Dog is implemented by Animal<String>. Similarly, T is concluded as Integer since Pig is implemented by Animal<Int>.

Stable opt-in requirements

The opt-in mechanism in Kotlin allows you to mark APIs that should be used carefully. If you mark a declaration as opt-in required and use it, it will produce a warning or error in the code, prompting the user to explicitly opt in to using it. You can find more information about Opt-in requirements here.

Before 1.7.0, the opt-in feature itself required the argument like the one below to avoid a warning.

-opt-in=kotlin.RequiresOptIn

It is no longer required with version 1.7.0. However, opt-in can still be used to opt-in for other annotations for modules.

Thanks for reading! Since Kotlin 1.7.0 is a feature release, it can bring changes that are incompatible with the code written for earlier versions of the language. Find the detailed list of such changes in the compatibility guide.

--

--