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