sa4020.go raw

   1  package sa4020
   2  
   3  import (
   4  	"fmt"
   5  	"go/ast"
   6  	"go/types"
   7  
   8  	"honnef.co/go/tools/analysis/code"
   9  	"honnef.co/go/tools/analysis/lint"
  10  	"honnef.co/go/tools/analysis/report"
  11  
  12  	"golang.org/x/exp/typeparams"
  13  	"golang.org/x/tools/go/analysis"
  14  	"golang.org/x/tools/go/analysis/passes/inspect"
  15  )
  16  
  17  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
  18  	Analyzer: &analysis.Analyzer{
  19  		Name:     "SA4020",
  20  		Run:      run,
  21  		Requires: []*analysis.Analyzer{inspect.Analyzer},
  22  	},
  23  	Doc: &lint.RawDocumentation{
  24  		Title: `Unreachable case clause in a type switch`,
  25  		Text: `In a type switch like the following
  26  
  27      type T struct{}
  28      func (T) Read(b []byte) (int, error) { return 0, nil }
  29  
  30      var v interface{} = T{}
  31  
  32      switch v.(type) {
  33      case io.Reader:
  34          // ...
  35      case T:
  36          // unreachable
  37      }
  38  
  39  the second case clause can never be reached because \'T\' implements
  40  \'io.Reader\' and case clauses are evaluated in source order.
  41  
  42  Another example:
  43  
  44      type T struct{}
  45      func (T) Read(b []byte) (int, error) { return 0, nil }
  46      func (T) Close() error { return nil }
  47  
  48      var v interface{} = T{}
  49  
  50      switch v.(type) {
  51      case io.Reader:
  52          // ...
  53      case io.ReadCloser:
  54          // unreachable
  55      }
  56  
  57  Even though \'T\' has a \'Close\' method and thus implements \'io.ReadCloser\',
  58  \'io.Reader\' will always match first. The method set of \'io.Reader\' is a
  59  subset of \'io.ReadCloser\'. Thus it is impossible to match the second
  60  case without matching the first case.
  61  
  62  
  63  Structurally equivalent interfaces
  64  
  65  A special case of the previous example are structurally identical
  66  interfaces. Given these declarations
  67  
  68      type T error
  69      type V error
  70  
  71      func doSomething() error {
  72          err, ok := doAnotherThing()
  73          if ok {
  74              return T(err)
  75          }
  76  
  77          return U(err)
  78      }
  79  
  80  the following type switch will have an unreachable case clause:
  81  
  82      switch doSomething().(type) {
  83      case T:
  84          // ...
  85      case V:
  86          // unreachable
  87      }
  88  
  89  \'T\' will always match before V because they are structurally equivalent
  90  and therefore \'doSomething()\''s return value implements both.`,
  91  		Since:    "2019.2",
  92  		Severity: lint.SeverityWarning,
  93  		MergeIf:  lint.MergeIfAll,
  94  	},
  95  })
  96  
  97  var Analyzer = SCAnalyzer.Analyzer
  98  
  99  func run(pass *analysis.Pass) (interface{}, error) {
 100  	// Check if T subsumes V in a type switch. T subsumes V if T is an interface and T's method set is a subset of V's method set.
 101  	subsumes := func(T, V types.Type) bool {
 102  		if typeparams.IsTypeParam(T) {
 103  			return false
 104  		}
 105  		tIface, ok := T.Underlying().(*types.Interface)
 106  		if !ok {
 107  			return false
 108  		}
 109  
 110  		return types.Implements(V, tIface)
 111  	}
 112  
 113  	subsumesAny := func(Ts, Vs []types.Type) (types.Type, types.Type, bool) {
 114  		for _, T := range Ts {
 115  			for _, V := range Vs {
 116  				if subsumes(T, V) {
 117  					return T, V, true
 118  				}
 119  			}
 120  		}
 121  
 122  		return nil, nil, false
 123  	}
 124  
 125  	fn := func(node ast.Node) {
 126  		tsStmt := node.(*ast.TypeSwitchStmt)
 127  
 128  		type ccAndTypes struct {
 129  			cc    *ast.CaseClause
 130  			types []types.Type
 131  		}
 132  
 133  		// All asserted types in the order of case clauses.
 134  		ccs := make([]ccAndTypes, 0, len(tsStmt.Body.List))
 135  		for _, stmt := range tsStmt.Body.List {
 136  			cc, _ := stmt.(*ast.CaseClause)
 137  
 138  			// Exclude the 'default' case.
 139  			if len(cc.List) == 0 {
 140  				continue
 141  			}
 142  
 143  			Ts := make([]types.Type, 0, len(cc.List))
 144  			for _, expr := range cc.List {
 145  				// Exclude the 'nil' value from any 'case' statement (it is always reachable).
 146  				if typ := pass.TypesInfo.TypeOf(expr); typ != types.Typ[types.UntypedNil] {
 147  					Ts = append(Ts, typ)
 148  				}
 149  			}
 150  
 151  			ccs = append(ccs, ccAndTypes{cc: cc, types: Ts})
 152  		}
 153  
 154  		if len(ccs) <= 1 {
 155  			// Zero or one case clauses, nothing to check.
 156  			return
 157  		}
 158  
 159  		// Check if case clauses following cc have types that are subsumed by cc.
 160  		for i, cc := range ccs[:len(ccs)-1] {
 161  			for _, next := range ccs[i+1:] {
 162  				if T, V, yes := subsumesAny(cc.types, next.types); yes {
 163  					report.Report(pass, next.cc, fmt.Sprintf("unreachable case clause: %s will always match before %s", T.String(), V.String()),
 164  						report.ShortRange())
 165  				}
 166  			}
 167  		}
 168  	}
 169  
 170  	code.Preorder(pass, fn, (*ast.TypeSwitchStmt)(nil))
 171  	return nil, nil
 172  }
 173