1 | // Copyright 2010 The Go Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style |
3 | // license that can be found in the LICENSE file. |
4 | |
5 | // Package stdmethods defines an Analyzer that checks for misspellings |
6 | // in the signatures of methods similar to well-known interfaces. |
7 | package stdmethods |
8 | |
9 | import ( |
10 | "go/ast" |
11 | "go/types" |
12 | "strings" |
13 | |
14 | "golang.org/x/tools/go/analysis" |
15 | "golang.org/x/tools/go/analysis/passes/inspect" |
16 | "golang.org/x/tools/go/ast/inspector" |
17 | ) |
18 | |
19 | const Doc = `check signature of methods of well-known interfaces |
20 | |
21 | Sometimes a type may be intended to satisfy an interface but may fail to |
22 | do so because of a mistake in its method signature. |
23 | For example, the result of this WriteTo method should be (int64, error), |
24 | not error, to satisfy io.WriterTo: |
25 | |
26 | type myWriterTo struct{...} |
27 | func (myWriterTo) WriteTo(w io.Writer) error { ... } |
28 | |
29 | This check ensures that each method whose name matches one of several |
30 | well-known interface methods from the standard library has the correct |
31 | signature for that interface. |
32 | |
33 | Checked method names include: |
34 | Format GobEncode GobDecode MarshalJSON MarshalXML |
35 | Peek ReadByte ReadFrom ReadRune Scan Seek |
36 | UnmarshalJSON UnreadByte UnreadRune WriteByte |
37 | WriteTo |
38 | ` |
39 | |
40 | var Analyzer = &analysis.Analyzer{ |
41 | Name: "stdmethods", |
42 | Doc: Doc, |
43 | Requires: []*analysis.Analyzer{inspect.Analyzer}, |
44 | Run: run, |
45 | } |
46 | |
47 | // canonicalMethods lists the input and output types for Go methods |
48 | // that are checked using dynamic interface checks. Because the |
49 | // checks are dynamic, such methods would not cause a compile error |
50 | // if they have the wrong signature: instead the dynamic check would |
51 | // fail, sometimes mysteriously. If a method is found with a name listed |
52 | // here but not the input/output types listed here, vet complains. |
53 | // |
54 | // A few of the canonical methods have very common names. |
55 | // For example, a type might implement a Scan method that |
56 | // has nothing to do with fmt.Scanner, but we still want to check |
57 | // the methods that are intended to implement fmt.Scanner. |
58 | // To do that, the arguments that have a = prefix are treated as |
59 | // signals that the canonical meaning is intended: if a Scan |
60 | // method doesn't have a fmt.ScanState as its first argument, |
61 | // we let it go. But if it does have a fmt.ScanState, then the |
62 | // rest has to match. |
63 | var canonicalMethods = map[string]struct{ args, results []string }{ |
64 | "As": {[]string{"any"}, []string{"bool"}}, // errors.As |
65 | // "Flush": {{}, {"error"}}, // http.Flusher and jpeg.writer conflict |
66 | "Format": {[]string{"=fmt.State", "rune"}, []string{}}, // fmt.Formatter |
67 | "GobDecode": {[]string{"[]byte"}, []string{"error"}}, // gob.GobDecoder |
68 | "GobEncode": {[]string{}, []string{"[]byte", "error"}}, // gob.GobEncoder |
69 | "Is": {[]string{"error"}, []string{"bool"}}, // errors.Is |
70 | "MarshalJSON": {[]string{}, []string{"[]byte", "error"}}, // json.Marshaler |
71 | "MarshalXML": {[]string{"*xml.Encoder", "xml.StartElement"}, []string{"error"}}, // xml.Marshaler |
72 | "ReadByte": {[]string{}, []string{"byte", "error"}}, // io.ByteReader |
73 | "ReadFrom": {[]string{"=io.Reader"}, []string{"int64", "error"}}, // io.ReaderFrom |
74 | "ReadRune": {[]string{}, []string{"rune", "int", "error"}}, // io.RuneReader |
75 | "Scan": {[]string{"=fmt.ScanState", "rune"}, []string{"error"}}, // fmt.Scanner |
76 | "Seek": {[]string{"=int64", "int"}, []string{"int64", "error"}}, // io.Seeker |
77 | "UnmarshalJSON": {[]string{"[]byte"}, []string{"error"}}, // json.Unmarshaler |
78 | "UnmarshalXML": {[]string{"*xml.Decoder", "xml.StartElement"}, []string{"error"}}, // xml.Unmarshaler |
79 | "UnreadByte": {[]string{}, []string{"error"}}, |
80 | "UnreadRune": {[]string{}, []string{"error"}}, |
81 | "Unwrap": {[]string{}, []string{"error"}}, // errors.Unwrap |
82 | "WriteByte": {[]string{"byte"}, []string{"error"}}, // jpeg.writer (matching bufio.Writer) |
83 | "WriteTo": {[]string{"=io.Writer"}, []string{"int64", "error"}}, // io.WriterTo |
84 | } |
85 | |
86 | func run(pass *analysis.Pass) (interface{}, error) { |
87 | inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) |
88 | |
89 | nodeFilter := []ast.Node{ |
90 | (*ast.FuncDecl)(nil), |
91 | (*ast.InterfaceType)(nil), |
92 | } |
93 | inspect.Preorder(nodeFilter, func(n ast.Node) { |
94 | switch n := n.(type) { |
95 | case *ast.FuncDecl: |
96 | if n.Recv != nil { |
97 | canonicalMethod(pass, n.Name) |
98 | } |
99 | case *ast.InterfaceType: |
100 | for _, field := range n.Methods.List { |
101 | for _, id := range field.Names { |
102 | canonicalMethod(pass, id) |
103 | } |
104 | } |
105 | } |
106 | }) |
107 | return nil, nil |
108 | } |
109 | |
110 | func canonicalMethod(pass *analysis.Pass, id *ast.Ident) { |
111 | // Expected input/output. |
112 | expect, ok := canonicalMethods[id.Name] |
113 | if !ok { |
114 | return |
115 | } |
116 | |
117 | // Actual input/output |
118 | sign := pass.TypesInfo.Defs[id].Type().(*types.Signature) |
119 | args := sign.Params() |
120 | results := sign.Results() |
121 | |
122 | // Special case: WriteTo with more than one argument, |
123 | // not trying at all to implement io.WriterTo, |
124 | // comes up often enough to skip. |
125 | if id.Name == "WriteTo" && args.Len() > 1 { |
126 | return |
127 | } |
128 | |
129 | // Special case: Is, As and Unwrap only apply when type |
130 | // implements error. |
131 | if id.Name == "Is" || id.Name == "As" || id.Name == "Unwrap" { |
132 | if recv := sign.Recv(); recv == nil || !implementsError(recv.Type()) { |
133 | return |
134 | } |
135 | } |
136 | |
137 | // Special case: Unwrap has two possible signatures. |
138 | // Check for Unwrap() []error here. |
139 | if id.Name == "Unwrap" { |
140 | if args.Len() == 0 && results.Len() == 1 { |
141 | t := typeString(results.At(0).Type()) |
142 | if t == "error" || t == "[]error" { |
143 | return |
144 | } |
145 | } |
146 | pass.ReportRangef(id, "method Unwrap() should have signature Unwrap() error or Unwrap() []error") |
147 | return |
148 | } |
149 | |
150 | // Do the =s (if any) all match? |
151 | if !matchParams(pass, expect.args, args, "=") || !matchParams(pass, expect.results, results, "=") { |
152 | return |
153 | } |
154 | |
155 | // Everything must match. |
156 | if !matchParams(pass, expect.args, args, "") || !matchParams(pass, expect.results, results, "") { |
157 | expectFmt := id.Name + "(" + argjoin(expect.args) + ")" |
158 | if len(expect.results) == 1 { |
159 | expectFmt += " " + argjoin(expect.results) |
160 | } else if len(expect.results) > 1 { |
161 | expectFmt += " (" + argjoin(expect.results) + ")" |
162 | } |
163 | |
164 | actual := typeString(sign) |
165 | actual = strings.TrimPrefix(actual, "func") |
166 | actual = id.Name + actual |
167 | |
168 | pass.ReportRangef(id, "method %s should have signature %s", actual, expectFmt) |
169 | } |
170 | } |
171 | |
172 | func typeString(typ types.Type) string { |
173 | return types.TypeString(typ, (*types.Package).Name) |
174 | } |
175 | |
176 | func argjoin(x []string) string { |
177 | y := make([]string, len(x)) |
178 | for i, s := range x { |
179 | if s[0] == '=' { |
180 | s = s[1:] |
181 | } |
182 | y[i] = s |
183 | } |
184 | return strings.Join(y, ", ") |
185 | } |
186 | |
187 | // Does each type in expect with the given prefix match the corresponding type in actual? |
188 | func matchParams(pass *analysis.Pass, expect []string, actual *types.Tuple, prefix string) bool { |
189 | for i, x := range expect { |
190 | if !strings.HasPrefix(x, prefix) { |
191 | continue |
192 | } |
193 | if i >= actual.Len() { |
194 | return false |
195 | } |
196 | if !matchParamType(x, actual.At(i).Type()) { |
197 | return false |
198 | } |
199 | } |
200 | if prefix == "" && actual.Len() > len(expect) { |
201 | return false |
202 | } |
203 | return true |
204 | } |
205 | |
206 | // Does this one type match? |
207 | func matchParamType(expect string, actual types.Type) bool { |
208 | expect = strings.TrimPrefix(expect, "=") |
209 | // Overkill but easy. |
210 | t := typeString(actual) |
211 | return t == expect || |
212 | (t == "any" || t == "interface{}") && (expect == "any" || expect == "interface{}") |
213 | } |
214 | |
215 | var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) |
216 | |
217 | func implementsError(actual types.Type) bool { |
218 | return types.Implements(actual, errorType) |
219 | } |
220 |
Members