Kotlin 1.3.60 Released

Posted on by

We’re happy to present the new release today, Kotlin 1.3.60. In addition to the quality improvements, this version focuses on:

Optimizing the comparison of inline classes.

classes. Tooling improvements for debugging, J2K converter, and Gradle scripts written in Kotlin.

Support for more Kotlin/Native platforms/targets.

Improving the Kotlin/MPP IDE experience.

For Kotlin/JS, adding support for source maps and improving the platform test runner integration.

Preview for some already implemented features of Kotlin 1.4.

You can find the complete list of changes in the change log. As always, we’re really grateful to our external contributors.

Let’s dive into the details!

Language changes

An incremental release doesn’t bring any language changes other than minor improvements (like changing confusing error messages) or updates for experimental features (like inline classes). To take a sneak peek at what is coming in Kotlin 1.4, read the corresponding section below.

Improvements for inline classes

The equality comparison of two instances of inline classes caused unnecessary boxing of their underlying values. Starting with v1.3.60, value comparisons are optimized:

inline class MyClass(val value: Int) { // Generated by the compiler: // public static final boolean equals-impl0(int p1, int p2) { // return p1 == p2; // } } fun main() { val first = MyClass(1) val second = MyClass(1) println(first == second) // Called under the hood: // MyClass.equals-impl0(first, second) }

In the bytecode, a special static method equals-impl0 which compares the underlying values is generated for each inline class. When you use the equality comparison on unboxed instances, it’s called under the hood to avoid extra boxing.

Note that for now, it’s not possible to override equals/hashCode for inline classes. The generated equals-impl0 method simply compares the values. In the future versions, when the custom equals for inline classes is supported, the same equals-imp0 method is intended to be used under the hood.

Note that for compatibility reasons, this optimization will work for the kotlin.Result class starting with Kotlin 1.4 only.

Improved error messages

In rare situations, when you read a compiler error message, it’s not obvious why the error occurs. We try to fix this and to improve any error messages that might cause confusion.

Kotlin supports the trailing lambda convention: the lambda can be moved out of the parentheses. Such a lambda can also begin on the following line. This convention causes confusion sometimes when the compiler assumes that the curly braces on the next line should be the lambda argument of the function, but they’re not:

Now the error message for such cases has been improved, and you can automatically apply an easy fix: insert a semicolon at the end of the preceding line. (Yes, a semicolon is sometimes necessary in Kotlin!)

Another case worth highlighting is trying to make a mutable variable lazy. A lazy variable is by design read-only, but this might cause confusion. Now the error message is improved and an automatic quick-fix is available:

If you have other use-cases in mind that seem confusing or if you’re missing some useful quick-fixes, please log such requests in our issue tracker.

IntelliJ IDEA support

Scratches and Worksheets

We’ve redesigned and improved Scratch files, which let you perform small experiments with your codebase. Now it’s easier to see the results, which are shown in a different window. The multiline output is wrapped, and the output for the given line is highlighted:

Sometimes, however, scratch files don’t play well. There are situations where you would prefer to use a sandbox that’s part of the project, rather than to be defined outside of the project. This can be especially useful for educational purposes, creating demo projects, or during presentations. For all such use-cases, please welcome the brand new Kotlin Worksheets:

Kotlin Worksheets are conceptually and technically very similar to Scratch files: you can play with your codebase and see the results right away. The major difference between the two is that Worksheets are a part of the project, which means they can be stored in a VCS and shared, while Scratches are intended to be used outside of a project.

build.gradle.kts

We’re working on enhancing your user experience with Kotlin Gradle build scripts. We already have completion and highlighting performance improvements, and we will continue working closely with Gradle to improve it further.

Debugging improvements

You can now set function breakpoints in the Kotlin code. The debugger will then stop execution on entering or exiting the corresponding function. You can also set an additional entry condition if needed:

Completion and imports improvements

Several known bugs have been fixed, like completion for cases when the package name matches the local variable name:

Now if you define a typealias for enum, its members are now correctly shown in completion:

If you use an operator function like invoke via the concise operator syntax, IntelliJ IDEA will suggest importing it automatically:

New Java-to-Kotlin converter

We’ve done some good work on the new Java-to-Kotlin converter. Many corner-case issues have been fixed, such as conversion for static imports and proper analysis of usages of a collection, should a collection become mutable or read-only after the conversion, even if this collection itself is a generic argument.

Now, when you convert several files at once, they are analyzed together and the usages from the other files affect the final result. For example, if you pass null as a String argument to a foo function in Java, after converting a function and its usage together, the converted Kotlin function will take a nullable String? as an argument:

Note that the new converter is now used by default.

Eclipse IDE plugin update

We are happy to announce that the kotlin-eclipse plugin now supports experimentally incremental compilation for single modules. To try it, select the “incremental compilation” checkbox in the Kotlin | Building section of Eclipse properties. It’s still an experimental feature, so we will be happy to hear any feedback you may have!

Kotlin/Multiplatform

We’ve devoted a lot of focus to MPP tooling, so if anything didn’t quite work the way you expected in the past, please give it another try!

While future release(s) may bring new superpowers to the multiplatform side of Kotlin, this one comes with a lot of improvements/fixes to the usability within the IDE. In particular, we’ve significantly enhanced some “create expect ” quick-fixes.

Kotlin/Native

The Kotlin/Native compiler has acquired a few new capabilities:

Compatibility with the latest tooling bits: XCode 11 and LLVM 8.0 .

and . A plethora of new platforms/targets: watchOS

watchos_x86

watchos_arm64

watchos_arm32 tvOS

tvos_x64

tvos_arm64 Android (native)

android_x86

android_x64

Experimental symbolication of iOS crash reports for release binaries (including LLVM-inlined code, which is one step further than what XCode is able to decode).

Thread-safe tracking of Objective-C weak/shared references to Kotlin objects.

Support for suspend callable references.

Functions with “big arity” (on par with the JVM limit)

The ability to associate a work queue with any context/thread, not just the ones created ad⁠-⁠hoc through Worker.start .

Generic Kotlin/Multiplatform command-line parser

Some of you may have noticed that the kotlinx.cli project has been dormant for a few months. We’re happy to share that the project’s code has been (mostly) rewritten, and it’s also included in this release of the Kotlin/Native compiler.

We appreciate feedback from early adopters! You can check out how it’s used in some samples (tetris game, CSV parser, ‍video player), and even internally.

Performance

Even though the Kotlin/Native compiler is yet to be deeply optimized for performance, this release brings a few improvements that result in some impressive speedups.

The compilation speed, especially for large projects, has been increased by producing native libraries directly from klibs (instead of sources).

The runtime performance has also been improved: interface calls are now up to 5x faster, and type checks up to 50x faster!

Fixes

Missing debug information for inlined code Some inlining optimizations were forgetting to update the mapping between source line numbers and binary code location, which resulted in misbehaving breakpoints that would “overshoot their destination”. We’ve taught them to be more diligent, and now breakpoints work as expected.

Passing null to variadic arguments Whenever null was passed as an argument to a function with vararg arguments, such as: platform.posix.printf("%p", null) …the compiler wasn’t exactly sure what type to assign to it, and would protest by crashing! Heeding this, now we treat those values as COpaquePointer : the untyped/void pointer, equivalent to void * in C.

Unboxing negative values on iOS/macOS Sometimes, handling negative bytes would result in a nasty crash; it turned out to be an LLVM bug, for which we have suggested a manual workaround. Starting from this release, the workaround will be applied automatically by the compiler.

Kotlin/JS

In the world of Kotlin/JS, new changes are landing that focus on your quality of life and simplifying working with the new org.jetbrains.kotlin.js plugin, most notably support for source maps and improvements for test runners.

Source maps

With Kotlin 1.3.60, source maps are generated automatically for your code that targets JavaScript through the org.jetbrains.kotlin.js Gradle plugin. This makes it a lot more comfortable to debug your own code, as it provides readable stack traces when you run into an error, and equips you with the superpowers provided by the developer tools in the case of targeting the browser – with support for breakpoints, code annotations, information about the local scope, and more. They also simplify working with tests for the JS target, as we will see in the next section.

Test runner improvements

When running your tests on the JS platform, the standard output generated by your tests on the regular channels (ie. log , warn , error ) is included in the generated Gradle reports. This functionality is available for the Node.js and browser targets. Integration with source maps makes the stack traces of your tests readable and easy to relate to your code – with file names and line numbers pointing directly to your Kotlin sources:

Test filtering support for the JS target means that tests can be run selectively instead of having to execute all tasks at once. You can use this functionality via the Gradle command line interface through the --tests flag:

./gradlew browserTest --tests AppTest.testOperationExecution

Alternatively, you can use the gutter icons in IntelliJ IDEA to run individual tests or tests from a specific set:

Upcoming changes in Kotlin 1.4

Kotlin 1.4 is planned to be released some time in 2020. However, you can already try some implemented features by specifying the corresponding language version:

compileKotlin { kotlinOptions { languageVersion = "1.4" } }

Note that so far Kotlin 1.4 is available in the experimental state.

NPE assertions

By also setting the apiVersion to 1.4 , you can observe the changed behavior with null-check optimizations described earlier. The following code now throws a NullPointerException instead of an IllegalStateException with the old message "JavaCode.getNull() must not be null" :

fun main() { duplicate(JavaCode.getNull()) // 1 } fun duplicate(s: String) = s + s

public class JavaCode { public static String getNull() { return null; } }

Break & continue inside when

One of the language changes in Kotlin 1.4 is allowing break and continue inside when . Currently, break and continue expressions without labels are forbidden because these keywords were reserved to possibly be used for fall-through in when . However, using labels turned out to be rather cumbersome, so break and continue get their expected meaning inside outer loops:

fun foo(list: List<Int>) { for (i in list) { when (i) { 42 -> continue else -> println(i) } } }

The fall-through behavior inside when is subject to further design.

Changes for tail-recursive functions

We’re going to fix some “corner-case” behavioral peculiarities for tail-recursive functions.

Initialization order of default values

The change is only noticeable if your tail-recursive function defines default values with side effects. In Kotlin 1.3, the initialization order of default values inside the tailrec function is wrong: the default values are initialized from the last one to the first one, even though they should be initialized vice versa, from the first one to the last one, as it works for regular functions.

The change is apparent with the following example:

var counter = 0 fun inc() = counter++ tailrec fun test(i: Int, x: Int = inc(), y: Int = inc()) { println("x: $x, y: $y") if (i > 0) test(i - 1) } fun main() { test(1) }

The output in Kotlin 1.3 is:

x: 0, y: 1 x: 3, y: 2

In Kotlin 1.4, the output is:

x: 0, y: 1 x: 2, y: 3

We expect that such cases should very rarely occur in practice. (But if for some reason you use this complicated combination of language features, please take note of this upcoming change.)

Prohibiting open tailrec functions

In Kotlin 1.3, combining open and tailrec modifiers is a warning, in Kotlin 1.4 it becomes an error. Note that it’s a “breaking change”: code that used to work doesn’t work anymore, but we don’t expect this case to ever be used in practice.

It’s unclear whether an open tail-recursive function is supposed to behave as a tail-recursive or as an open function primarily. In Kotlin 1.3, the open modifier is “ignored” but that leads to some confusing behavior:

open class A { open tailrec fun foo(count: Int) { // Note: open & tailrec modifiers println("A.foo($count)") if (count > 0) foo(count - 1) } } class B : A() { override fun foo(count: Int) { println("B.foo($count)") } fun callSuperFoo(count: Int) = super.foo(count) } fun main() { B().callSuperFoo(3) }

The output of this code is:

A.foo(3) A.foo(2) A.foo(1) A.foo(0)

The foo function behaves as expected for a tail-recursive function: it calls itself and this call is optimized under the hood.

If we remove the tailrec modifier from the foo function in A , then the output becomes:

A.foo(3) B.foo(2)

Now the foo function behaves as expected for an open function: at first, super.foo(count) calls explicitly a function from the parent A class. Then foo(count - 1) is a virtual call that calls a function from the B subclass.

Because such behavior is confusing, Kotlin 1.4 prohibits using open and tailrec at the same time.

How to update

As always, you can try Kotlin online at play.kotl.in.

In Maven, Gradle, and npm : Use 1.3.60 as the version for the compiler and the standard library. See the docs here.

: Use as the version for the compiler and the standard library. See the docs here. In IntelliJ IDEA and Android Studio : Update the Kotlin plugin to version 1.3.60. Use Tools | Kotlin | Configure Kotlin Plugin Updates and click the “Check for updates now” button.

and : Update the Kotlin plugin to version 1.3.60. Use Tools | Kotlin | Configure Kotlin Plugin Updates and click the “Check for updates now” button. In Eclipse : Install the plugin using the Marketplace.

: Install the plugin using the Marketplace. The command-line compiler can be downloaded from the Github release page.

If you run into any problems with the new release, you’re welcome to ask for help on the forums on Slack (get an invite here), or to report issues in the issue tracker.

Let’s Kotlin!

External Contributions

We want to especially thank Steven Schäfer for implementing the equality comparison optimization for inline classes.

We’d like to thank all our external contributors whose pull requests were included in this release: