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.
5 // This file contains the code to check canonical methods.
18 "check that canonically named methods are canonically defined",
20 funcDecl
, interfaceType
)
23 type MethodSig
struct {
28 // canonicalMethods lists the input and output types for Go methods
29 // that are checked using dynamic interface checks. Because the
30 // checks are dynamic, such methods would not cause a compile error
31 // if they have the wrong signature: instead the dynamic check would
32 // fail, sometimes mysteriously. If a method is found with a name listed
33 // here but not the input/output types listed here, vet complains.
35 // A few of the canonical methods have very common names.
36 // For example, a type might implement a Scan method that
37 // has nothing to do with fmt.Scanner, but we still want to check
38 // the methods that are intended to implement fmt.Scanner.
39 // To do that, the arguments that have a = prefix are treated as
40 // signals that the canonical meaning is intended: if a Scan
41 // method doesn't have a fmt.ScanState as its first argument,
42 // we let it go. But if it does have a fmt.ScanState, then the
44 var canonicalMethods
= map[string]MethodSig
{
45 // "Flush": {{}, {"error"}}, // http.Flusher and jpeg.writer conflict
46 "Format": {[]string{"=fmt.State", "rune"}, []string{}}, // fmt.Formatter
47 "GobDecode": {[]string{"[]byte"}, []string{"error"}}, // gob.GobDecoder
48 "GobEncode": {[]string{}, []string{"[]byte", "error"}}, // gob.GobEncoder
49 "MarshalJSON": {[]string{}, []string{"[]byte", "error"}}, // json.Marshaler
50 "MarshalXML": {[]string{"*xml.Encoder", "xml.StartElement"}, []string{"error"}}, // xml.Marshaler
51 "ReadByte": {[]string{}, []string{"byte", "error"}}, // io.ByteReader
52 "ReadFrom": {[]string{"=io.Reader"}, []string{"int64", "error"}}, // io.ReaderFrom
53 "ReadRune": {[]string{}, []string{"rune", "int", "error"}}, // io.RuneReader
54 "Scan": {[]string{"=fmt.ScanState", "rune"}, []string{"error"}}, // fmt.Scanner
55 "Seek": {[]string{"=int64", "int"}, []string{"int64", "error"}}, // io.Seeker
56 "UnmarshalJSON": {[]string{"[]byte"}, []string{"error"}}, // json.Unmarshaler
57 "UnmarshalXML": {[]string{"*xml.Decoder", "xml.StartElement"}, []string{"error"}}, // xml.Unmarshaler
58 "UnreadByte": {[]string{}, []string{"error"}},
59 "UnreadRune": {[]string{}, []string{"error"}},
60 "WriteByte": {[]string{"byte"}, []string{"error"}}, // jpeg.writer (matching bufio.Writer)
61 "WriteTo": {[]string{"=io.Writer"}, []string{"int64", "error"}}, // io.WriterTo
64 func checkCanonicalMethod(f
*File
, node ast
.Node
) {
65 switch n
:= node
.(type) {
68 canonicalMethod(f
, n
.Name
, n
.Type
)
70 case *ast
.InterfaceType
:
71 for _
, field
:= range n
.Methods
.List
{
72 for _
, id
:= range field
.Names
{
73 canonicalMethod(f
, id
, field
.Type
.(*ast
.FuncType
))
79 func canonicalMethod(f
*File
, id
*ast
.Ident
, t
*ast
.FuncType
) {
80 // Expected input/output.
81 expect
, ok
:= canonicalMethods
[id
.Name
]
86 // Actual input/output
87 args
:= typeFlatten(t
.Params
.List
)
88 var results
[]ast
.Expr
90 results
= typeFlatten(t
.Results
.List
)
93 // Do the =s (if any) all match?
94 if !f
.matchParams(expect
.args
, args
, "=") ||
!f
.matchParams(expect
.results
, results
, "=") {
98 // Everything must match.
99 if !f
.matchParams(expect
.args
, args
, "") ||
!f
.matchParams(expect
.results
, results
, "") {
100 expectFmt
:= id
.Name
+ "(" + argjoin(expect
.args
) + ")"
101 if len(expect
.results
) == 1 {
102 expectFmt
+= " " + argjoin(expect
.results
)
103 } else if len(expect
.results
) > 1 {
104 expectFmt
+= " (" + argjoin(expect
.results
) + ")"
108 if err
:= printer
.Fprint(&f
.b
, f
.fset
, t
); err
!= nil {
109 fmt
.Fprintf(&f
.b
, "<%s>", err
)
111 actual
:= f
.b
.String()
112 actual
= strings
.TrimPrefix(actual
, "func")
113 actual
= id
.Name
+ actual
115 f
.Badf(id
.Pos(), "method %s should have signature %s", actual
, expectFmt
)
119 func argjoin(x
[]string) string {
120 y
:= make([]string, len(x
))
121 for i
, s
:= range x
{
127 return strings
.Join(y
, ", ")
130 // Turn parameter list into slice of types
131 // (in the ast, types are Exprs).
132 // Have to handle f(int, bool) and f(x, y, z int)
133 // so not a simple 1-to-1 conversion.
134 func typeFlatten(l
[]*ast
.Field
) []ast
.Expr
{
136 for _
, f
:= range l
{
137 if len(f
.Names
) == 0 {
138 t
= append(t
, f
.Type
)
142 t
= append(t
, f
.Type
)
148 // Does each type in expect with the given prefix match the corresponding type in actual?
149 func (f
*File
) matchParams(expect
[]string, actual
[]ast
.Expr
, prefix
string) bool {
150 for i
, x
:= range expect
{
151 if !strings
.HasPrefix(x
, prefix
) {
154 if i
>= len(actual
) {
157 if !f
.matchParamType(x
, actual
[i
]) {
161 if prefix
== "" && len(actual
) > len(expect
) {
167 // Does this one type match?
168 func (f
*File
) matchParamType(expect
string, actual ast
.Expr
) bool {
169 expect
= strings
.TrimPrefix(expect
, "=")
170 // Strip package name if we're in that package.
171 if n
:= len(f
.file
.Name
.Name
); len(expect
) > n
&& expect
[:n
] == f
.file
.Name
.Name
&& expect
[n
] == '.' {
172 expect
= expect
[n
+1:]
175 // Overkill but easy.
177 printer
.Fprint(&f
.b
, f
.fset
, actual
)
178 return f
.b
.String() == expect