Kotlin 1.6 will be released on November 16. What are the new syntax features?

  1. This is by no means an exact when statement.
  2. Suspending Functions as Supertypes
  3. Suspend conversion of normal functions
  4. The Builder function is easier to use
  5. Type derivation of recursive generics
  6. Some optimizations related to annotations

1. Safer when statement

Kotlin’s when keyword allows us to write expressions or statements in case branches. 1.6 There were security risks when writing statements in the CASE branch before:

// Define enumeration
enum class Mode  { ON, OFF }
val x: Mode = Mode.ON

// When expression
val result = when(x) {
    Mode.ON -> 1 // case is an expression
    Mode.OFF -> 2

/ / when statement
when(x) { 
    Mode.ON -> println("ON") // case is a statement
    Mode.OFF -> println("OFF")}Copy the code

The following table shows what the compiler checks for the when keyword

The type of x Enumerations, sealed classes/interfaces, Bool types, etc. (exhaustive types) Do not enumerate types
When the expression Case must either exhaust all branches, or add else, otherwise the compilation will fail The Case branch must contain else, otherwise the compilation will fail
When statement Case can enumerate all branches without error Same as above

When x is an enumerable type, the compiler checks the when expression carefully. If the case cannot enumerate all branches or else is missing, the compiler will report the following error:

ERROR: 'when' expression must be exhaustive, add necessary 'is TextMessage' branch or 'else' branch instead 
Copy the code

However, the compiler does not carefully check when statements, and does not report errors even if all branches are not exhausted, which makes it difficult for developers to write safe code:

/ / when statement
when(x) { // WARNING: [NON_EXHAUSTIVE_WHEN] 'when' expression on enum is recommended to be exhaustive, add 'OFF' branch or 'else' branch instead
    Mode.ON -> println("ON") // case is a statement
Copy the code

As of Kotlin 1.6, When you are an enumerable type in a When statement, you must handle all branches without missing them. Considering that there may be a lot of historical code, in order to smooth the transition, 1.6 will give Warning first for cases that are not exhausted in the WHEN statement. Starting from 1.7, Warning will change to Error, requiring developers to force resolution.

2. Suspend function types can be parent classes

A function type in Kotlin can be inherited as a parent class.

class MyFun<T>(var param: P): () -> Result<T> {
    override fun invoke(a): Result<T> {
        // Custom logic based on member param}}fun <T> handle(handler: () -> Result<T>) {
    / /...
Copy the code

Kotlin code makes extensive use of various function types, and many methods take function types as arguments. When you call these methods, you pass in an instance of the function type. When you want to encapsulate reusable logic in an instance, you can create subclasses using function types as parent classes.

But this does not currently work with suspend functions, where you cannot inherit from a parent class of type suspend function

class C : suspend () - >Unit { // Error: Suspend function type is not allowed as supertypes 

C().startCoroutine(completion = object : Continuation<Unit> {
    override val context: CoroutineContext
        get() = TODO("Not yet implemented")

    override fun resumeWith(result: Result<Unit>) {
        TODO("Not yet implemented")}})Copy the code

However, there are many methods that take suspend functions as arguments or recevier, so Kotlin introduced this feature in Preveiw in 1.5.30. This time, it is Stable in 1.6.

class MyClickAction : suspend () - >Unit {
    override suspend fun invoke(a) { TODO() }

fun launchOnClick(action: suspend() - >Unit) {}
Copy the code

As above, you can now call launchOnClick(MyClickAction()) like this.

Note that ordinary function types can be multiinherited as superclasses

class MyClickAction :  () - >Unit, (View) -> Unit {
    override fun invoke(a) {
        TODO("Not yet implemented")}override fun invoke(p1: View) {
        TODO("Not yet implemented")}}Copy the code

However, suspend functions as parent classes do not support multiple inheritance. There cannot be multiple suspend function types in the parent class list, nor can there be both ordinary and suspend function types.

3. Ordinary functions run suspend functions

This feature is also related to the function type.

It is harmless to add suspend to a normal function in Kotlin, although the compiler will tell you that you don’t need to do so. This is handy in some scenarios when a function signature has a suspend function type parameter but also allows you to pass in a normal function.

// Combine's transform parameter is a suspend function
public fun <T1, T2, R> combine(
    flow: Flow<T1>, flow2: Flow<T2>, 
    transform: suspend (a: T1.b: T2) - >R): Flow<R>
     = flow.combine(flow2, transform)

suspend fun before4_1(a) {
        flowA, flowB
    ) { a, b ->
        a to b
    }.collect { (a: Int, b: Int) ->
        println("$a and $b")}}Copy the code

As shown in the code above, the Flow Combine method takes a transform function of type suspend, and we want to complete the creation of a Pair again. This simple logic should not have used suspend, but could only have been written as above prior to 1.4.

As of Kotlin 1.4, references to normal functions can be passed as arguments to the suspend function.

suspend fun from1_4(a) {
        flowA, flowB, ::Pair
    ).collect { (a: Int, b: Int) ->
        println("$a and $b")}}Copy the code

After 1.4, there are still some scenarios where ordinary functions cannot be directly converted to the suspend function

fun getSuspending(suspending: suspend() - >Unit) {}

fun suspending(a) {}

fun test(regular: () -> Unit) {
    getSuspending { }           // OK
    getSuspending(::suspending) / / OK from 1.4
    getSuspending(regular)      / / NG before 1.6
Copy the code

GetSuspending (regular) returns the following error:

ERROR: The feature "suspend conversion" is disabledCopy the code

As of Kotlin 1.6, normal function types in all scenarios can be automatically converted to the suspend function for arguments, and you will no longer see the above error.

4. The Builder function is easier to use

When we build collections, we use Builder functions like buildList, buildMap, etc.

public inline fun <E> buildList(@BuilderInference builderAction: MutableList<E>. () - >Unit): List<E> {
    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
    return buildListInternal(builderAction)

val list = buildList<String> {
    add("b")}Copy the code

The buildList implementation annotates the builderAction lambda with @builderInterface. This allows buildList to intelligently deduce the types of generic parameters from method calls inside builderAction at call time, thereby reducing template code

Can be omitted / / < String >
val list = buildList {
        cannot be omitted
val list = buildList<String> {
    val x = get(1)}Copy the code

However, BuilderInterface has many restrictions on type derivation. For example, the method called in lambda has strict signature requirements, which must have generic parameters and return values without generics, breaking the rules and failing type derivation. So when lambda has a get() call in the code above, it must clearly mark the generic type. This makes the Builder function of the collection class less flexible to use.

Kotlin 1.6 BuilderInterface does not have similar restrictions, for us the most intuitive benefit is that the Builder function in what call will not be restricted, use more freedom

val list = buildList { 
    set(1.null) //OK
    val x = get(1) //OK
    if(x ! =null) {
        removeAt(1) //OK}}val map = buildMap { 
    put("a".1) //OK
    put("b".1.1) //OK
    put("c".2f) //OK
Copy the code

This feature is also available in 1.5.30 by adding the -xunrestricted-Builder-inference compiler option. 1.6 is already available by default.

5. Type derivation of recursive generics

We usually have few requirements for this feature.

In Java or Kotlin we can define a generic with a recursive relationship like the following, that is, the upper limit of the generic is itself

public class PostgreSQLContainer<SELF extends PostgreSQLContainer<SELF>> extends JdbcDatabaseContainer<SELF> {
    / /...
Copy the code

Type inference in this case is difficult, and Kotlin 1.5.30 allows type inference based only on the upper limits of generics.

/ / Before 1.5.30
val containerA = PostgreSQLContainer<Nothing>(DockerImageName.parse("postgres:13-alpine")).apply {
  withInitScript("sql/schema.sql")}// With compiler option in 1.5.30 or by default starting with 1.6.0
val containerB = PostgreSQLContainer(DockerImageName.parse("postgres:13-alpine"))
Copy the code

1.5.30 Support for this feature requires the addition of -xself-upper-bound-inference compiler option, which is supported by default from 1.6.

6. Some optimizations related to annotations

Kotlin 1.6 has a number of annotations optimizations that will come into play during compiler annotation processing

Support instantiation of annotations

annotation class InfoMarker(val info: String)
fun processInfo(marker: InfoMarker)=...fun main(args: Array<String>) {
    if(args.size ! =0)
        processInfo(InfoMarker("default"))}Copy the code

Java annotations are essentially interfaces that implement annotations and can be used by inheritance

public @interface JavaClassAnno {
  String[] value();

public interface JavaClassAnno extends Annotation{
    / /...

class MyAnnotation implements JavaClassAnno { // <--- works in Java
 / /...
Copy the code

However, there is no inheritance in Kotlin, which results in some apis that accept annotation classes that cannot be called on the Kotlin side.

class MyAnnotationLiteral : JavaClassAnno { // <--- doesn't work in Kotlin (annotation can not be inherited)
  / /...
Copy the code

Once the annotation class can be instantiated, the API that receives the annotation class parameters can be called, making it more compatible with Java code

Generic parameters can be annotated

annotation class BoxContent

class Box<@BoxContent T> {}
Copy the code

After Kotlin 1.6, annotations can be added for generic parameters, which will be convenient for annotation processors such as KAPT/KSP.

Repeatable runtime annotations

Jdk 1.8 introduces @ Java. Lang. The annotation. Repetable yuan notes, allowing the same annotations were added for many times. Kotlin also introduces @ Kotlin accordingly. The annotation. The Repeatable, but only before 1.6 annotations @ Retention annotations (RetentionPolicy. SOURCE), when the SOURCE notes appear multiple times, complains

ERROR: [NON_SOURCE_REPEATED_ANNOTATION] Repeatable annotations with non-SOURCE retention are not yet supported
Copy the code

Also, Kotlin side code cannot be annotated multiple times using Java’s @REPEATable annotation.

Starting with Kotlin1.6, the restriction of using only SOURCE class annotations was removed. Any type of annotation can appear multiple times, and The Kotlin side supports using Java’s @REPEATable annotation

@Retentioin(RetentionPolicy.RUNTIME) // This is a RUNTIME annotation
annotation class Attribute(valName: String)@Attribute("attr1") //OK
@Attribute("attr2") //OK
class MyClass {}
Copy the code

The last

Above are some of the new syntax features in Kotlin1.6, most of which were introduced as preview in 1.5.30 and translated into 1.6. In addition to the new syntax features, there are many new contents in 1.6 platform Compiler, which we will not introduce in this paper until we have access to them in daily development.

More reference: kotlinlang.org/docs/whatsn…