GoPLS Viewer

Home|gopls/go/analysis/passes/stdmethods/stdmethods.go
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.
7package stdmethods
8
9import (
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
19const Doc = `check signature of methods of well-known interfaces
20
21Sometimes a type may be intended to satisfy an interface but may fail to
22do so because of a mistake in its method signature.
23For example, the result of this WriteTo method should be (int64, error),
24not error, to satisfy io.WriterTo:
25
26    type myWriterTo struct{...}
27        func (myWriterTo) WriteTo(w io.Writer) error { ... }
28
29This check ensures that each method whose name matches one of several
30well-known interface methods from the standard library has the correct
31signature for that interface.
32
33Checked 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
40var 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.
63var canonicalMethods = map[string]struct{ argsresults []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
86func 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(passn.Name)
98            }
99        case *ast.InterfaceType:
100            for _field := range n.Methods.List {
101                for _id := range field.Names {
102                    canonicalMethod(passid)
103                }
104            }
105        }
106    })
107    return nilnil
108}
109
110func canonicalMethod(pass *analysis.Passid *ast.Ident) {
111    // Expected input/output.
112    expectok := 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(passexpect.argsargs"=") || !matchParams(passexpect.resultsresults"=") {
152        return
153    }
154
155    // Everything must match.
156    if !matchParams(passexpect.argsargs"") || !matchParams(passexpect.resultsresults"") {
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"actualexpectFmt)
169    }
170}
171
172func typeString(typ types.Typestring {
173    return types.TypeString(typ, (*types.Package).Name)
174}
175
176func argjoin(x []stringstring {
177    y := make([]stringlen(x))
178    for is := 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?
188func matchParams(pass *analysis.Passexpect []stringactual *types.Tupleprefix stringbool {
189    for ix := range expect {
190        if !strings.HasPrefix(xprefix) {
191            continue
192        }
193        if i >= actual.Len() {
194            return false
195        }
196        if !matchParamType(xactual.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?
207func matchParamType(expect stringactual types.Typebool {
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
215var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
216
217func implementsError(actual types.Typebool {
218    return types.Implements(actualerrorType)
219}
220
MembersX
argjoin.RangeStmt_6700.s
matchParams
analysis
canonicalMethod
run.pass
canonicalMethod.pass
matchParams.prefix
inspect
run
matchParamType.expect
matchParamType.actual
implementsError.actual
run.BlockStmt.BlockStmt.RangeStmt_4607.field
argjoin
argjoin.x
matchParams.expect
matchParams.RangeStmt_6994.i
ast
inspector
canonicalMethod.BlockStmt.BlockStmt.t
typeString
argjoin.y
argjoin.RangeStmt_6700.i
matchParams.RangeStmt_6994.x
matchParamType.t
run.BlockStmt.BlockStmt.RangeStmt_4607.BlockStmt.RangeStmt_4650.id
canonicalMethod.args
matchParams.actual
implementsError
run.nodeFilter
canonicalMethod.BlockStmt.actual
canonicalMethod.id
canonicalMethod.results
canonicalMethod.BlockStmt.recv
typeString.typ
strings
Doc
matchParamType
types
matchParams.pass
Members
X