Home

Awesome

Exhaustive

WARNING: This plugin is deprecated!

Kotlin 1.7 made all when statements exhaustive by default and this plugin is no longer required.


An annotation and Kotlin compiler plugin for enforcing a when statement is exhaustive.

enum class RouletteColor { Red, Black, Green }

fun printColor(color: RouletteColor) {
  @Exhaustive
  when (color) {
    Red -> println("red")
    Black -> println("black")
  }
}
e: Example.kt:5: @Exhaustive when is not exhaustive!

Missing branches:
- RouletteColor.Green

No more assigning to dummy local properties or referencing pointless functions or properties to force the when to be an expression for exhaustiveness checking. The plugin reuses the same check that is used for a when expression.

In addition to being forced to be exhaustive, an annotated when statement is forbidden from using an else branch.

fun printColor(color: RouletteColor) {
  @Exhaustive
  when (color) {
    Red -> println("red")
    Black -> println("black")
    else -> println("green")
  }
}
e: Example.kt:5: @Exhaustive when must not contain an 'else' branch

The presence of an else block indicates support for a default action. The exhaustive check would otherwise always pass with this branch which is why it is disallowed.

Sealed classes are also supported.

sealed class RouletteColor {
  object Red : RouletteColor()
  object Black : RouletteColor()
  object Green : RouletteColor()
}

fun printColor(color: RouletteColor) {
  @Exhaustive
  when (color) {
    RouletteColor.Red -> println("red")
    RouletteColor.Black -> println("black")
  }
}
e: Example.kt:9: @Exhaustive when is not exhaustive!

Missing branches:
- RouletteColor.Green

Usage

buildscript {
  dependencies {
    classpath 'app.cash.exhaustive:exhaustive-gradle:0.2.0'
  }
  repositories {
    mavenCentral()
  }
}

apply plugin: 'org.jetbrains.kotlin.jvm' // or .android or .multiplatform or .js
apply plugin: 'app.cash.exhaustive'

The @Exhaustive annotation will be made available in your main and test source sets but will not be shipped as a dependency of the module.

Since Kotlin compiler plugins are an unstable API, certain versions of Exhaustive only work with certain versions of Kotlin.

KotlinExhaustive
1.4.10 - 1.5.100.1.1
1.5.20 - 1.5.310.2.0

Versions of Kotlin older than 1.4.10 are not supported. Versions newer than those listed may be supported but are untested.

<details> <summary>Snapshots of the development version are available in Sonatype's snapshots repository.</summary> <p>
buildscript {
  dependencies {
    classpath 'app.cash.exhaustive:exhaustive-gradle:0.3.0-SNAPSHOT'
  }
  repositories {
    maven {
      url 'https://oss.sonatype.org/content/repositories/snapshots/'
    }
  }
}

// 'apply' same as above
</p> </details>

Alternatives Considered

In the weeks prior to building this project a set of alternatives were explored and rejected for various reasons. They are listed below. If you evaluate their merits differently, you are welcome to use them instead. The solution provided by this plugin is not perfect either.

Unused local and warning suppression

fun printColor(color: RouletteColor) {
  @Suppress("UNUSED_VARIABLE")
  val exhaustive = when (color) {
    RouletteColor.Red -> println("red")
    RouletteColor.Black -> println("black")
  }
}

Pros:

Neutral:

Cons:

Built-in trailing property or function call

fun printColor(color: RouletteColor) {
  when (color) {
    RouletteColor.Red -> println("red")
    RouletteColor.Black -> println("black")
  }.javaClass // or .hashCode() or anything else...
}

Pros:

Cons:

Library trailing property

@Suppress("unused") // Receiver reference forces when into expression form.
inline val Any?.exhaustive get() = Unit

fun printColor(color: RouletteColor) {
  when (color) {
    RouletteColor.Red -> println("red")
    RouletteColor.Black -> println("black")
  }.exhaustive
}

Pros:

Cons:

Library leading expression

@Suppress("NOTHING_TO_INLINE", "ClassName", "UNUSED_PARAMETER") // Faking a soft keyword.
object exhaustive {
  inline operator fun minus(other: Any?) = Unit
}

fun printColor(color: RouletteColor) {
  exhaustive-when (color) {
    RouletteColor.Red -> println("red")
    RouletteColor.Black -> println("black")
  }
}

Pro:

Neutral:

Cons:

Use soft keyword in compiler

fun printColor(color: RouletteColor) {
  sealed when (color) {
    RouletteColor.Red -> println("red")
    RouletteColor.Black -> println("black")
  }
}

Pros:

Cons:

License

Copyright 2020 Square, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.