Skip to content

Commit

Permalink
Refactor into a package so that it can be integrated into golangci-lint.
Browse files Browse the repository at this point in the history
  • Loading branch information
alecthomas committed Mar 11, 2023
1 parent b14d3b1 commit 2fe5460
Show file tree
Hide file tree
Showing 10 changed files with 87 additions and 71 deletions.
2 changes: 1 addition & 1 deletion .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ builds:
- "386"
goarm:
- "6"
main: .
main: ./cmd/go-check-sumtype
binary: go-check-sumtype
archives:
-
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ or not. If it's not, `go-check-sumtype` will report an error. For example, runni
```go
package main

//sumtype:decl MySumType

//sumtype:decl
type MySumType interface {
sealed()
}
Expand Down Expand Up @@ -111,7 +110,7 @@ cannot tell you whether case analysis over a sum type is complete or not.

The `go-check-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 `//sumtype:decl ...` annotation is required. `go-check-sumtype` will
is why the `//sumtype:decl` annotation is required. `go-check-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.
Expand Down
2 changes: 1 addition & 1 deletion check.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package gochecksumtype

import (
"fmt"
Expand Down
30 changes: 15 additions & 15 deletions check_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package gochecksumtype

import (
"testing"
Expand All @@ -9,7 +9,7 @@ import (
// TestMissingOne tests that we detect a single missing variant.
func TestMissingOne(t *testing.T) {
code := `
package main
package gochecksumtype
//sumtype:decl
type T interface { sealed() }
Expand All @@ -29,15 +29,15 @@ func main() {
tmpdir, pkgs := setupPackages(t, code)
defer teardownPackage(t, tmpdir)

errs := run(pkgs)
errs := Run(pkgs)
assert.Equal(t, 1, len(errs))
assert.Equal(t, []string{"B"}, missingNames(t, errs[0]))
}

// TestMissingTwo tests that we detect a two missing variants.
func TestMissingTwo(t *testing.T) {
code := `
package main
package gochecksumtype
//sumtype:decl
type T interface { sealed() }
Expand All @@ -60,7 +60,7 @@ func main() {
tmpdir, pkgs := setupPackages(t, code)
defer teardownPackage(t, tmpdir)

errs := run(pkgs)
errs := Run(pkgs)
assert.Equal(t, 1, len(errs))
assert.Equal(t, []string{"B", "C"}, missingNames(t, errs[0]))
}
Expand All @@ -69,7 +69,7 @@ func main() {
// if we have a trivial default case that panics.
func TestMissingOneWithPanic(t *testing.T) {
code := `
package main
package gochecksumtype
//sumtype:decl
type T interface { sealed() }
Expand All @@ -91,15 +91,15 @@ func main() {
tmpdir, pkgs := setupPackages(t, code)
defer teardownPackage(t, tmpdir)

errs := run(pkgs)
errs := Run(pkgs)
assert.Equal(t, 1, len(errs))
assert.Equal(t, []string{"B"}, missingNames(t, errs[0]))
}

// TestNoMissing tests that we correctly detect exhaustive case analysis.
func TestNoMissing(t *testing.T) {
code := `
package main
package gochecksumtype
//sumtype:decl
type T interface { sealed() }
Expand All @@ -122,15 +122,15 @@ func main() {
tmpdir, pkgs := setupPackages(t, code)
defer teardownPackage(t, tmpdir)

errs := run(pkgs)
errs := Run(pkgs)
assert.Equal(t, 0, len(errs))
}

// TestNoMissingDefault tests that even if we have a missing variant, a default
// case should thwart exhaustiveness checking.
func TestNoMissingDefault(t *testing.T) {
code := `
package main
package gochecksumtype
//sumtype:decl
type T interface { sealed() }
Expand All @@ -152,15 +152,15 @@ func main() {
tmpdir, pkgs := setupPackages(t, code)
defer teardownPackage(t, tmpdir)

errs := run(pkgs)
errs := Run(pkgs)
assert.Equal(t, 0, len(errs))
}

// TestNotSealed tests that we report an error if one tries to declare a sum
// type with an unsealed interface.
func TestNotSealed(t *testing.T) {
code := `
package main
package gochecksumtype
//sumtype:decl
type T interface {}
Expand All @@ -170,7 +170,7 @@ func main() {}
tmpdir, pkgs := setupPackages(t, code)
defer teardownPackage(t, tmpdir)

errs := run(pkgs)
errs := Run(pkgs)
assert.Equal(t, 1, len(errs))
assert.Equal(t, "T", errs[0].(unsealedError).Decl.TypeName)
}
Expand All @@ -179,7 +179,7 @@ func main() {}
// type that doesn't correspond to an interface.
func TestNotInterface(t *testing.T) {
code := `
package main
package gochecksumtype
//sumtype:decl
type T struct {}
Expand All @@ -189,7 +189,7 @@ func main() {}
tmpdir, pkgs := setupPackages(t, code)
defer teardownPackage(t, tmpdir)

errs := run(pkgs)
errs := Run(pkgs)
assert.Equal(t, 1, len(errs))
assert.Equal(t, "T", errs[0].(notInterfaceError).Decl.TypeName)
}
Expand Down
46 changes: 9 additions & 37 deletions main.go → cmd/go-check-sumtype/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"
"strings"

gochecksumtype "github.com/alecthomas/go-check-sumtype"
"golang.org/x/tools/go/packages"
)

Expand All @@ -14,42 +15,7 @@ func main() {
log.Fatalf("Usage: sumtype <packages>\n")
}
args := os.Args[1:]
pkgs, err := tycheckAll(args)
if err != nil {
log.Fatal(err)
}
if errs := run(pkgs); len(errs) > 0 {
var list []string
for _, err := range errs {
list = append(list, err.Error())
}
log.Fatal(strings.Join(list, "\n"))
}
}

func run(pkgs []*packages.Package) []error {
var errs []error

decls, err := findSumTypeDecls(pkgs)
if err != nil {
return []error{err}
}

defs, defErrs := findSumTypeDefs(decls)
errs = append(errs, defErrs...)
if len(defs) == 0 {
return errs
}

for _, pkg := range pkgs {
if pkgErrs := check(pkg, defs); pkgErrs != nil {
errs = append(errs, pkgErrs...)
}
}
return errs
}

func tycheckAll(args []string) ([]*packages.Package, error) {
conf := &packages.Config{
Mode: packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedTypes | packages.NeedTypesSizes |
packages.NeedImports | packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles,
Expand All @@ -67,7 +33,13 @@ func tycheckAll(args []string) ([]*packages.Package, error) {
}
pkgs, err := packages.Load(conf, args...)
if err != nil {
return nil, err
log.Fatal(err)
}
if errs := gochecksumtype.Run(pkgs); len(errs) > 0 {
var list []string
for _, err := range errs {
list = append(list, err.Error())
}
log.Fatal(strings.Join(list, "\n"))
}
return pkgs, nil
}
2 changes: 1 addition & 1 deletion decl.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package gochecksumtype

import (
"fmt"
Expand Down
2 changes: 1 addition & 1 deletion def.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package gochecksumtype

import (
"fmt"
Expand Down
18 changes: 7 additions & 11 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,11 @@ either lacks a default clause or does not account for all possible variants.
Declarations are provided in comments like so:
//sumtype:decl MySumType
//sumtype:decl
type MySumType interface { ... }
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.
MySumType must be *sealed*. That is, part of its interface definition contains
an unexported method.
sumtype will produce an error if any of the above is not true.
Expand All @@ -24,10 +21,9 @@ occurrences, it will attempt to detect whether the type switch is exhaustive
or not. If it's not, sumtype will report an error. For example:
$ cat mysumtype.go
package main
//sumtype:decl MySumType
package gochecksumtype
//sumtype:decl
type MySumType interface {
sealed()
}
Expand All @@ -54,4 +50,4 @@ 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.
*/
package main
package gochecksumtype
25 changes: 24 additions & 1 deletion help_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package gochecksumtype

import (
"io/ioutil"
Expand Down Expand Up @@ -30,3 +30,26 @@ func teardownPackage(t *testing.T, dir string) {
t.Fatal(err)
}
}

func tycheckAll(args []string) ([]*packages.Package, error) {
conf := &packages.Config{
Mode: packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedTypes | packages.NeedTypesSizes |
packages.NeedImports | packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles,
// Unfortunately, it appears including the test packages in
// this lint makes it difficult to do exhaustiveness checking.
// Namely, it appears that compiling the test version of a
// package introduces distinct types from the normal version
// of the package, which will always result in inexhaustive
// errors whenever a package both defines a sum type and has
// tests. (Specifically, using `package name`. Using `package
// name_test` is OK.)
//
// It's not clear what the best way to fix this is. :-(
Tests: false,
}
pkgs, err := packages.Load(conf, args...)
if err != nil {
return nil, err
}
return pkgs, nil
}
26 changes: 26 additions & 0 deletions run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package gochecksumtype

import "golang.org/x/tools/go/packages"

// Run sumtype checking on the given packages.
func Run(pkgs []*packages.Package) []error {
var errs []error

decls, err := findSumTypeDecls(pkgs)
if err != nil {
return []error{err}
}

defs, defErrs := findSumTypeDefs(decls)
errs = append(errs, defErrs...)
if len(defs) == 0 {
return errs
}

for _, pkg := range pkgs {
if pkgErrs := check(pkg, defs); pkgErrs != nil {
errs = append(errs, pkgErrs...)
}
}
return errs
}

0 comments on commit 2fe5460

Please sign in to comment.