Code Monkey home page Code Monkey logo

go-sumtype's Introduction

go-sumtype

A simple utility for running exhaustiveness checks on type switch statements. Exhaustiveness checks are only run on interfaces that are declared to be "sum types."

Linux build status

Dual-licensed under MIT or the UNLICENSE.

This work was inspired by our code at Diffeo.

Installation

$ go get github.com/BurntSushi/go-sumtype

For usage info, just run the command:

$ go-sumtype

Typical usage might look like this:

$ go-sumtype $(go list ./... | grep -v vendor)

Usage

go-sumtype takes a list of Go package paths or files and looks for sum type declarations in each package/file provided. Exhaustiveness checks are then performed for each use of a declared sum type in a type switch statement. Namely, go-sumtype will report an error for any type switch statement that either lacks a default clause or does not account for all possible variants.

Declarations are provided in comments like so:

//go-sumtype:decl MySumType

MySumType must satisfy the following:

  1. It is a type defined in the same package.
  2. It is an interface.
  3. It is sealed. That is, part of its interface definition contains an unexported method.

go-sumtype will produce an error if any of the above is not true.

For valid declarations, go-sumtype will look for all occurrences in which a value of type MySumType participates in a type switch statement. In those occurrences, it will attempt to detect whether the type switch is exhaustive or not. If it's not, go-sumtype will report an error. For example, running go-sumtype on this source file:

package main

//go-sumtype:decl MySumType

type MySumType interface {
        sealed()
}

type VariantA struct{}

func (*VariantA) sealed() {}

type VariantB struct{}

func (*VariantB) sealed() {}

func main() {
        switch MySumType(nil).(type) {
        case *VariantA:
        }
}

produces the following:

$ go-sumtype mysumtype.go
mysumtype.go:18:2: exhaustiveness check failed for sum type 'MySumType': missing cases for VariantB

Adding either a default clause or a clause to handle *VariantB will cause exhaustive checks to pass.

As a special case, if the type switch statement contains a default clause that always panics, then exhaustiveness checks are still performed.

Details and motivation

Sum types are otherwise known as discriminated unions. That is, a sum type is a finite set of disjoint values. In type systems that support sum types, the language will guarantee that if one has a sum type T, then its value must be one of its variants.

Go's type system does not support sum types. A typical proxy for representing sum types in Go is to use an interface with an unexported method and define each variant of the sum type in the same package to satisfy said interface. This guarantees that the set of types that satisfy the interface is closed at compile time. Performing case analysis on these types is then done with a type switch statement, e.g., switch x.(type) { ... }. Each clause of the type switch corresponds to a variant of the sum type. The downside of this approach is that Go's type system is not aware of the set of variants, so it cannot tell you whether case analysis over a sum type is complete or not.

The go-sumtype command recognizes this pattern, but it needs a small amount of help to recognize which interfaces should be treated as sum types, which is why the //go-sumtype:decl ... annotation is required. go-sumtype will figure out all of the variants of a sum type by finding the set of types defined in the same package that satisfy the interface specified by the declaration.

The go-sumtype command will prove its worth when you need to add a variant to an existing sum type. Running go-sumtype will tell you immediately which case analyses need to be updated to account for the new variant.

go-sumtype's People

Contributors

burntsushi avatar frou avatar jmquigs avatar kujenga avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

go-sumtype's Issues

on a large codebase, go-sumtype blows the file descriptor limit

This happens when the file descriptor limit is 256, but is fixed by increasing that limit to 1024. Still, 256 open files seems excessive.

go-sumtype itself never opens any file descriptors, instead delegating that work to the loader package. We should confirm the bug is there and then either try to fix it or file a bug with loader.

Update to support Go 1.11 modules

go-sumtype seems to fail with "could not import" errors, even (surprisingly) after go mod vendor. From what I can tell, it needs to be updated to understand Go modules.

add checks for simple enums

In Go, creating a simple enumeration with integer values is a common idiom. We should try to apply exhaustiveness checks to those as well.

I think there are a few variations on this pattern, and we should try to enumerate them here.

The first is to just assign names to integers:

const (
    Variant1 = iota
    Variant2
    Variant3
)

The problem with this pattern is that the variants are just integers, so it might be hard to reliably do exhaustiveness checks here. I think the problem is, "How do we know which switch statements to apply exhaustiveness checks to?"

Another is to create a specific type:

type MySum int

const (
    Variant1 MySum = iota
    Variant2
    Variant3
)

This case is easier to detect, since the switch is typically written as switch value { ... } where value has type MySum. The problem here (and is also present with the above case) is that MySum isn't actually sealed. Anyone could create a new variant in an ad hoc way.

Finally, another pattern which is less common is to create a sealed enum by making it impossible for clients to create ad hoc variants:

type MySum struct {
    v int
}

var (
    Variant1 = MySum{0}
    Variant2 = MySum{1}
    Variant3 = MySum{3}
)

In this case, we have the same level of safety as we do with the interface-based sum types because we can find all possible inhabitants of MySum by only looking at the package in which the type is defined.

More in-depth checking?

(as noted on reddit) - this is great, I've been hankering for something like this. i have a couple sum types (Constraint, Version, PairedVersion, UnpairedVersion) in https://github.com/sdboyer/gps, and...well, I've got a number of false positives so far, ostensibly because there's a series of composed interfaces involved, and some of the switches operate on composed interfaces, rather than the underlying dynamic types.

i don't really have time to dig further right now on exactly how this works, unfortunately, but I thought I'd present it as a case to see what your thoughts are about doing a bit more in-depth analysis of type relations - e.g., plainVersion implements UnpairedVersion, so if a switch contains a case for UnpairedVersion, then plainVersion should be considered covered in exhaustiveness checks.

Check fails if interface is extended by another interface

Test case:

package main

//go-sumtype:decl Animal

type Animal interface {
	MakeSound()
	sealed()
}

type Dog struct{}

func (*Dog) sealed() {}

func (*Dog) MakeSound() {
	println("WOOF")
}

type Canine interface {
	Animal
	Breed() string
}

func test(animal Animal) {
	switch animal.(type) {
	case *Dog:
		println("is dog")
	}
}

func main() {
	test(&Dog{})
}

Fails:

$ go-sumtype main.go
main.go:24:2: exhaustiveness check failed for sum type 'Animal': missing cases for Canine

I did not expect this to fail, as the switch is exhaustive on all concrete types of the interface and there aren't even any structs implementing the second one. The checks fail even there are structs which implement both or one of them.

Package as library so gometalinter can use it

I've started using this tool, but it's annoying that I can't get inline errors in my editor. Integrating it into gometalinter would be an obvious step, I think. For that to happen, I suspect some changes will be needed to make the API publicly importable.

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.