Skip to content

Commit

Permalink
deprecate -checking-strategy flag (no effect); named type for constan…
Browse files Browse the repository at this point in the history
…t value

also rename hitlist type to checklist
  • Loading branch information
nishanths committed Nov 5, 2021
1 parent faaebac commit 0d093a5
Show file tree
Hide file tree
Showing 12 changed files with 127 additions and 341 deletions.
21 changes: 5 additions & 16 deletions checklist.go
@@ -1,7 +1,6 @@
package exhaustive

import (
"fmt"
"go/ast"
"go/types"
"regexp"
Expand Down Expand Up @@ -45,21 +44,11 @@ func makeChecklist(em *enumMembers, enumPkg *types.Package, includeUnexported bo
}
}

func (c *checklist) found(memberName string, strategy checkingStrategy) {
switch strategy {
case strategyValue:
// delete all of the same-valued names
constVal := c.em.NameToValue[memberName]
for _, n := range c.em.ValueToNames[constVal] {
delete(c.m, n)
}

case strategyName:
// delete the given name alone
delete(c.m, memberName)

default:
panic(fmt.Sprintf("unknown strategy %v", strategy))
func (c *checklist) found(memberName string) {
// delete all of the same-valued names
constVal := c.em.NameToValue[memberName]
for _, n := range c.em.ValueToNames[constVal] {
delete(c.m, n)
}
}

Expand Down
29 changes: 8 additions & 21 deletions checklist_test.go
@@ -1,7 +1,6 @@
package exhaustive

import (
"fmt"
"go/types"
"reflect"
"regexp"
Expand All @@ -13,7 +12,7 @@ func TestChecklist(t *testing.T) {

em := &enumMembers{
Names: []string{"A", "B", "C", "D", "E", "F", "G"},
NameToValue: map[string]string{
NameToValue: map[string]constantValue{
"A": "1",
"B": "2",
"C": "5",
Expand All @@ -22,7 +21,7 @@ func TestChecklist(t *testing.T) {
"F": "2",
"G": "4",
},
ValueToNames: map[string][]string{
ValueToNames: map[constantValue][]string{
"1": {"A"},
"2": {"B", "D", "F"},
"3": {"E"},
Expand All @@ -39,14 +38,6 @@ func TestChecklist(t *testing.T) {
}
}

t.Run("panics on unknown strategy", func(t *testing.T) {
checklist := makeChecklist(em, enumPkg, false, nil)
f := func() {
checklist.found("A", checkingStrategy(8238))
}
assertPanic(t, f, fmt.Sprintf("unknown strategy %v", checkingStrategy(8238)))
})

t.Run("main operations", func(t *testing.T) {
checklist := makeChecklist(em, enumPkg, false, nil)
checkRemaining(t, checklist, map[string]struct{}{
Expand All @@ -59,7 +50,7 @@ func TestChecklist(t *testing.T) {
"G": {},
})

checklist.found("A", strategyValue)
checklist.found("A")
checkRemaining(t, checklist, map[string]struct{}{
"B": {},
"C": {},
Expand All @@ -69,39 +60,35 @@ func TestChecklist(t *testing.T) {
"G": {},
})

checklist.found("B", strategyName)
checklist.found("B")
checkRemaining(t, checklist, map[string]struct{}{
"C": {},
"D": {},
"E": {},
"F": {},
"G": {},
})

// repeated call should be a no-op.
checklist.found("B", strategyName)
checklist.found("B")
checkRemaining(t, checklist, map[string]struct{}{
"C": {},
"D": {},
"E": {},
"F": {},
"G": {},
})

checklist.found("F", strategyValue)
checklist.found("F")
checkRemaining(t, checklist, map[string]struct{}{
"C": {},
"E": {},
"G": {},
})

checklist.found("C", strategyValue)
checklist.found("C")
checkRemaining(t, checklist, map[string]struct{}{
"E": {},
"G": {},
})

checklist.found("E", strategyName)
checklist.found("E")
checkRemaining(t, checklist, map[string]struct{}{
"G": {},
})
Expand Down
37 changes: 0 additions & 37 deletions doc.go
Expand Up @@ -36,38 +36,6 @@ switch statement exhaustive. For an enum type defined in an external package, it
is sufficient for just the exported enum members to be present in order to
consider the switch statement exhaustive.
Exhaustiveness checking strategies
There are two strategies for checking exhaustiveness: the "name" strategy and
the "value" strategy (which is the default). The name strategy requires that
each independent enum member name is listed in a switch statement to satisfy
exhaustiveness. On the other hand, the value strategy only requires that each
independent enum value is listed in a switch statement to satisfy
exhaustiveness.
To illustrate the difference between the two strategies, consider the
enum and the switch statement in the code snippet below.
type AccessControl string
const (
AccessPublic AccessControl = "public"
AccessPrivate AccessControl = "private"
AccessDefault AccessControl = AccessPublic
)
func example(v AccessControl) {
switch v {
case AccessPublic:
case AccessPrivate:
}
}
The switch statement is not exhaustive when using the name strategy (because the
name AccessDefault is not listed in the switch), but it is exhaustive when using
the value strategy (because AccessDefault and AccessPublic have the same value,
and it suffices that one of them is listed in the switch).
Notable flags
The notable flags used by the analyzer are:
Expand All @@ -91,11 +59,6 @@ names inclusive of the enum package import path, e.g.
"github.com/foo/bar.Tundra", where the enum package import path is
"github.com/foo/bar" and the enum member name is "Tundra".
-checking-strategy <strategy>
Specifies the exhaustiveness checking strategy, which must be one of "name" or
"value" (default). For details see section: Exhaustiveness checking strategies.
Skipping analysis
If the following comment:
Expand Down
37 changes: 16 additions & 21 deletions enum.go
Expand Up @@ -6,37 +6,32 @@ import (
"go/types"
)

// constantValue is a constant.Value.ExactString().
type constantValue string

// enums holds the enum types and their members defined in a single package.
type enums map[string]*enumMembers // enum type name -> enum members

// enumMembers is the members for a single enum type.
// The zero value is ready to use.
type enumMembers struct {
// Names is the enum member names,
// in the order encountered in the AST.
Names []string

// NameToValue maps member name -> constVal.
NameToValue map[string]string

// ValueToNames maps constVal -> member names.
// Note the use of []string for the element type of the map: Multiple
// names can have the same value.
ValueToNames map[string][]string
Names []string // enum member names, AST order
NameToValue map[string]constantValue // enum member name -> constant value
ValueToNames map[constantValue][]string // constant value -> enum member names
}

func (em *enumMembers) add(name string, constVal string) {
func (em *enumMembers) add(name string, val constantValue) {
em.Names = append(em.Names, name)

if em.NameToValue == nil {
em.NameToValue = make(map[string]string)
em.NameToValue = make(map[string]constantValue)
}
em.NameToValue[name] = constVal
em.NameToValue[name] = val

if em.ValueToNames == nil {
em.ValueToNames = make(map[string][]string)
em.ValueToNames = make(map[constantValue][]string)
}
em.ValueToNames[constVal] = append(em.ValueToNames[constVal], name)
em.ValueToNames[val] = append(em.ValueToNames[val], name)
}

// Find the enums for the files in a package. The files is typically obtained from
Expand All @@ -52,11 +47,11 @@ func findEnums(files []*ast.File, typesInfo *types.Info) enums {
pkgEnums := make(enums)

// Gather enum members.
findEnumMembers(files, typesInfo, possibleEnumTypes, func(memberName, typeName string, constVal string) {
findEnumMembers(files, typesInfo, possibleEnumTypes, func(memberName, typeName string, val constantValue) {
if _, ok := pkgEnums[typeName]; !ok {
pkgEnums[typeName] = &enumMembers{}
}
pkgEnums[typeName].add(memberName, constVal)
pkgEnums[typeName].add(memberName, val)
})

return pkgEnums
Expand Down Expand Up @@ -98,7 +93,7 @@ func findPossibleEnumTypes(files []*ast.File, typesInfo *types.Info, found func(
}
}

func findEnumMembers(files []*ast.File, typesInfo *types.Info, knownEnumTypes map[string]struct{}, found func(memberName, typeName string, constVal string)) {
func findEnumMembers(files []*ast.File, typesInfo *types.Info, knownEnumTypes map[string]struct{}, found func(memberName, typeName string, val constantValue)) {
for _, f := range files {
for _, decl := range f.Decls {
gen, ok := decl.(*ast.GenDecl)
Expand Down Expand Up @@ -127,7 +122,7 @@ func findEnumMembers(files []*ast.File, typesInfo *types.Info, knownEnumTypes ma
}
}

func determineConstVal(name *ast.Ident, typesInfo *types.Info) string {
func determineConstVal(name *ast.Ident, typesInfo *types.Info) constantValue {
c := typesInfo.Defs[name].(*types.Const)
return c.Val().ExactString()
return constantValue(c.Val().ExactString())
}

0 comments on commit 0d093a5

Please sign in to comment.