Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlin 2.1: Language Updates

Kotlin 2.1: Language Updates

Anton Arhipov

November 28, 2024
Tweet

More Decks by Anton Arhipov

Other Decks in Programming

Transcript

  1. Language Features •Suppor t for requiring opt-in to extend APIs

    •Non-local break and continue •Multi-dollar interpolation •Guard conditions in when-with-subject expressions •Improved exhaustiveness checks for when expressions with sealed classes
  2. Opt-in requirements for subclasses @RequiresOptIn( level = RequiresOptIn.Level.ERROR, message =

    "Interfaces in this library are experimental" ) annotation class UnstableApi() @SubclassOptInRequired(UnstableApi :: class) interface CoreLibraryApi interface MyImplementation : CoreLibraryApi Library User code
  3. Opt-in requirements for subclasses @RequiresOptIn( level = RequiresOptIn.Level.ERROR, message =

    "Interfaces in this library are experimental" ) annotation class UnstableApi() @SubclassOptInRequired(UnstableApi :: class) interface CoreLibraryApi interface MyImplementation : CoreLibraryApi Library User code
  4. Opt-in requirements for subclasses @RequiresOptIn( level = RequiresOptIn.Level.ERROR, message =

    "Interfaces in this library are experimental" ) annotation class UnstableApi() @SubclassOptInRequired(UnstableApi :: class) interface CoreLibraryApi @OptIn(UnstableApi :: class) interface MyImplementation : CoreLibraryApi Library User code
  5. Local break and continue in lambdas fun List<File>.readFirstLines(): List<String> =

    buildList { for (file in this@readFirstLines) { val reader = file.bufferedReader(Charsets.UTF_8) val line = reader.readLine() if (line.isNullOrEmpty()) continue else add(line) } } "Read the first line from each file in the list"
  6. Local break and continue in lambdas fun List<File>.readFirstLines(): List<String> =

    buildList { for (file in this@readFirstLines) { val reader = file.bufferedReader(Charsets.UTF_8) val line = reader.readLine() if (line.isNullOrEmpty()) continue else add(line) } }
  7. Local break and continue in lambdas fun List<File>.readFirstLines(): List<String> =

    buildList { for (file in this@readFirstLines) { val reader = file.bufferedReader(Charsets.UTF_8) val line = reader.readLine() if (line.isNullOrEmpty()) continue else add(line) } }
  8. Local break and continue in lambdas fun List<File>.readFirstLines(): List<String> =

    buildList { for (file in this@readFirstLines) { val reader = file.bufferedReader(Charsets.UTF_8) val line = reader.readLine() if (line.isNullOrEmpty()) continue else add(line) } }
  9. Local break and continue in lambdas fun List<File>.readFirstLines(): List<String> =

    buildList { for (file in this@readFirstLines) { val reader = file.bufferedReader(Charsets.UTF_8) val line = reader.readLine() if (line.isNullOrEmpty()) continue else add(line) } } Realize, that we need to use the resources properly
  10. Local break and continue in lambdas fun List<File>.readFirstLines(): List<String> =

    buildList { for (file in this@readFirstLines) { file.bufferedReader(Charsets.UTF_8).use { reader -> val line = reader.readLine() // this is now a 'continue' inside a lambda if (line.isNullOrEmpty()) continue else add(line) } } }
  11. Local break and continue in lambdas fun List<File>.readFirstLines(): List<String> =

    buildList { for (file in this@readFirstLines) { file.bufferedReader(Charsets.UTF_8).use { reader -> val line = reader.readLine() // this is now a 'continue' inside a lambda if (line.isNullOrEmpty()) continue else add(line) } } }
  12. Local break and continue in lambdas fun List<File>.readFirstLines(): List<String> =

    buildList { for (file in this@readFirstLines) { file.bufferedReader(Charsets.UTF_8).use { reader -> val line = reader.readLine() // this is now a 'continue' inside a lambda if (line.isNullOrEmpty()) continue else add(line) } } } -Xnon-local-break-continue
  13. Multi-dollar interpolation val jsonSchema: String = """ { "$id": "https:

    // example.com/product.schema.json", }""" Interpolation fails for "id"
  14. Multi-dollar interpolation val jsonSchema: String = """ { "${'$'}id": "https:

    // example.com/product.schema.json", }""" A workaround
  15. Multi-dollar interpolation val jsonSchema: String = $$""" { "$id": "https:

    // example.com/product.schema.json", }""" -Xmulti-dollar-interpolation Use $$ as a symbol sequence for interpolation
  16. Multi-dollar interpolation val jsonSchema: String = $$""" { "$id": "https:

    // example.com/product.schema.json", "parameter": $$parameter" }""" val parameter = "value" Use $$ as a symbol sequence for interpolation
  17. Multi-dollar interpolation val jsonSchema: String = $$$$$$$$$$$$$$$$$$$$$$""" { "$id": "https:

    // example.com/product.schema.json", "parameter": $$$$$$$$$$$$$$$$$$$$$$parameter" }""" val parameter = "value" :)
  18. when { order is YearlySubscription && order.amount > 100 ->

    applyDiscount(order) order is MonthlySubscription -> startSubscription(order) order is OneTimeOrder -> processOrder(order) } val order = getOrder() Guard conditions in when-with-subject expressions
  19. when { order is YearlySubscription && order.amount > 100 ->

    applyDiscount(order) order is MonthlySubscription -> startSubscription(order) order is OneTimeOrder -> processOrder(order) } val order = getOrder() Potentially a logical error Repetition is not nice Guard conditions in when-with-subject expressions
  20. when { order is YearlySubscription && order.amount > 100 ->

    applyDiscount(order) order is YearlySubscription -> processSubscription(order) order is MonthlySubscription -> startSubscription(order) order is OneTimeOrder -> processOrder(order) } val order = getOrder() Guard conditions in when-with-subject expressions
  21. when(order) { is YearlySubscription && order.amount > 100 -> applyDiscount(order)

    is YearlySubscription -> processSubscription(order) is MonthlySubscription -> startSubscription(order) is OneTimeOrder -> processOrder(order) } val order = getOrder() Guard conditions in when-with-subject expressions
  22. when(order) { is YearlySubscription && order.amount > 100 -> applyDiscount(order)

    is YearlySubscription -> processSubscription(order) is MonthlySubscription -> startSubscription(order) is OneTimeOrder -> processOrder(order) } val order = getOrder() Error: expecting ' -> ' && Guard conditions in when-with-subject expressions
  23. when(order) { is YearlySubscription && order.amount > 100 -> applyDiscount(order)

    is YearlySubscription -> processSubscription(order) is MonthlySubscription -> startSubscription(order) is OneTimeOrder -> processOrder(order) } val order = getOrder() if Guard conditions in when-with-subject expressions Guarded conditions: KEEP - 371
  24. when(order) { is YearlySubscription if order.amount > 100 -> applyDiscount(order)

    is YearlySubscription -> processSubscription(order) is MonthlySubscription -> startSubscription(order) is OneTimeOrder -> processOrder(order) } val order = getOrder() Guard conditions in when-with-subject expressions
  25. when(order) { is YearlySubscription -> processSubscription(order) is YearlySubscription if order.amount

    > 100 -> applyDiscount(order) is MonthlySubscription -> startSubscription(order) is OneTimeOrder -> processOrder(order) } val order = getOrder() The 'when' branch is never reachable Guard conditions in when-with-subject expressions
  26. when(order) { is YearlySubscription if order.amount > 100 -> applyDiscount(order)

    is YearlySubscription -> processSubscription(order) is MonthlySubscription -> startSubscription(order) is OneTimeOrder -> processOrder(order) } val order = getOrder() Guard conditions in when-with-subject expressions
  27. Improved exhaustiveness checks for when expressions with sealed classes sealed

    class Result object Error: Result() class Success(val value: String): Result() fun <T : Result> render(result: T) = when (result) { Error -> "Error!" is Success -> result.value // Requires no else branch } Required else branch before 2.1
  28. Change of JSpecify nullability mismatch diagnostics severity to 'strict' import

    org.jspecify.annotations.*; public class SomeJavaClass { @NonNull public String foo() { ... } @Nullable public String bar() { ... } } Java
  29. Change of JSpecify nullability mismatch diagnostics severity to 'strict' import

    org.jspecify.annotations.*; public class SomeJavaClass { @NonNull public String foo() { ... } @Nullable public String bar() { ... } } Java Kotlin val sjc = SomeJavaClass() sjc.foo().length sjc.bar().length
  30. Change of JSpecify nullability mismatch diagnostics severity to 'strict' import

    org.jspecify.annotations.*; public class SomeJavaClass { @NonNull public String foo() { ... } @Nullable public String bar() { ... } } Java Kotlin val sjc = SomeJavaClass() sjc.foo().length sjc.bar().length
  31. Change of JSpecify nullability mismatch diagnostics severity to 'strict' import

    org.jspecify.annotations.*; public class SomeJavaClass { @NonNull public String foo() { ... } @Nullable public String bar() { ... } } Java Kotlin val sjc = SomeJavaClass() sjc.foo().length sjc.bar() ?. length Raises an error in the default strict mode because the result is nullable. To avoid the error, use ?.length instead
  32. Change of JSpecify nullability mismatch diagnostics severity to 'strict' import

    org.jspecify.annotations.*; public class SomeJavaClass { @NonNull public String foo() { ... } @Nullable public String bar() { ... } } Java Kotlin val sjc = SomeJavaClass() sjc.foo().length sjc.bar() ?. length kotlin { compilerOptions { freeCompilerArgs.add(" -- Xnullability-annotations=strict") } } // gradle.build.kts / / ignore | warning | strict