GithubHelp home page GithubHelp logo

isabella232 / exhaustive Goto Github PK

View Code? Open in Web Editor NEW

This project forked from cashapp/exhaustive

0.0 0.0 0.0 160 KB

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

License: Apache License 2.0

Kotlin 100.00%

exhaustive's Introduction

Exhaustive

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

Note: This plugin is reaching its end-of-life and is soft deprecated. Learn more.

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

Soft Deprecated

We did it! Kotlin 1.7 will make all when statements exhaustive by default (where possible) and this plugin will no longer be required.

In order to change the language behavior, Kotlin 1.6 will first warn when a when statement is not exhaustive. Kotlin 1.5.30 ships with this warning as opt-in behavior by setting your languageVersion to 1.6.

kotlin {
  sourceSets.all {
    languageSettings {
      languageVersion = '1.6'
    }
  }
}

If you want to opt-in to errors instead of warnings, enable progressive mode.

kotlin {
  sourceSets.all {
    languageSettings {
      languageVersion = '1.6'
      progressiveMode = true
    }
  }
}

This plugin will continue to be maintained until Kotlin 1.7.0 is released for those who cannot enable these in their builds.

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.

Kotlin Exhaustive
1.4.10 - 1.5.10 0.1.1
1.5.20 - 1.5.31 0.2.0

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

Snapshots of the development version are available in Sonatype's snapshots repository.

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

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:

  • Works everywhere without library or plugin
  • No overhead or impact on compiled code

Neutral:

  • Somewhat self-describing as to the intent, assuming good local property names
  • Good locality as the exhaustiveness forcing is very close to the when keyword, although somewhat overshadowed by the warning suppression

Cons:

  • Requires suppression of warning which need to be put into a shared template or requires alt+enter,enter-ing to create the final form
  • Requires the use of unique local property names (_ is not allowed here)

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:

  • Works everywhere without library or plugin

Cons:

  • Not self-describing as to the effect on the when and the developer intent behind adding it
  • Poor locality as the property is far away from the when keyword it modifies
  • Impact on compiled code in the form of a property call, function call, and/or additional instructions at the call-site

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:

  • Self-describing effect on the when
  • No impact on compiled code

Cons:

  • Requires a library
  • Poor locality as the property is far away from the when keyword it modifies
  • Pollutes the extension namespace by showing up for everything, not just when

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:

  • Great locality as the syntactical trick appears almost like a soft keyword modifying the when

Neutral:

  • Slight impact on compiled code (which could be mitigated on Android with an embedded R8 rule)

Cons:

  • Requires a library
  • Feels too clever
  • Code formatting will insert a space before and after the minus sign breaking the effect

Use soft keyword in compiler

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

Pros:

  • Great locality as a soft keyword directly modifying the when
  • No impact on compiled code
  • Part of the actual language

Cons:

  • Requires forking the compiler and IDE plugin which is an overwhelming long-term commitment!!!

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.

exhaustive's People

Contributors

autonomousapps avatar goooler avatar jakewharton avatar jrodbx avatar shaishavgandhi avatar stefma avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.