github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/src/cmd/vet/method.go (about) 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 // This file contains the code to check canonical methods. 6 7 package main 8 9 import ( 10 "fmt" 11 "go/ast" 12 "go/printer" 13 "strings" 14 ) 15 16 type MethodSig struct { 17 args []string 18 results []string 19 } 20 21 // canonicalMethods lists the input and output types for Go methods 22 // that are checked using dynamic interface checks. Because the 23 // checks are dynamic, such methods would not cause a compile error 24 // if they have the wrong signature: instead the dynamic check would 25 // fail, sometimes mysteriously. If a method is found with a name listed 26 // here but not the input/output types listed here, vet complains. 27 // 28 // A few of the canonical methods have very common names. 29 // For example, a type might implement a Scan method that 30 // has nothing to do with fmt.Scanner, but we still want to check 31 // the methods that are intended to implement fmt.Scanner. 32 // To do that, the arguments that have a = prefix are treated as 33 // signals that the canonical meaning is intended: if a Scan 34 // method doesn't have a fmt.ScanState as its first argument, 35 // we let it go. But if it does have a fmt.ScanState, then the 36 // rest has to match. 37 var canonicalMethods = map[string]MethodSig{ 38 // "Flush": {{}, {"error"}}, // http.Flusher and jpeg.writer conflict 39 "Format": {[]string{"=fmt.State", "rune"}, []string{}}, // fmt.Formatter 40 "GobDecode": {[]string{"[]byte"}, []string{"error"}}, // gob.GobDecoder 41 "GobEncode": {[]string{}, []string{"[]byte", "error"}}, // gob.GobEncoder 42 "MarshalJSON": {[]string{}, []string{"[]byte", "error"}}, // json.Marshaler 43 "MarshalXML": {[]string{}, []string{"[]byte", "error"}}, // xml.Marshaler 44 "Peek": {[]string{"=int"}, []string{"[]byte", "error"}}, // image.reader (matching bufio.Reader) 45 "ReadByte": {[]string{}, []string{"byte", "error"}}, // io.ByteReader 46 "ReadFrom": {[]string{"=io.Reader"}, []string{"int64", "error"}}, // io.ReaderFrom 47 "ReadRune": {[]string{}, []string{"rune", "int", "error"}}, // io.RuneReader 48 "Scan": {[]string{"=fmt.ScanState", "rune"}, []string{"error"}}, // fmt.Scanner 49 "Seek": {[]string{"=int64", "int"}, []string{"int64", "error"}}, // io.Seeker 50 "UnmarshalJSON": {[]string{"[]byte"}, []string{"error"}}, // json.Unmarshaler 51 "UnreadByte": {[]string{}, []string{"error"}}, 52 "UnreadRune": {[]string{}, []string{"error"}}, 53 "WriteByte": {[]string{"byte"}, []string{"error"}}, // jpeg.writer (matching bufio.Writer) 54 "WriteTo": {[]string{"=io.Writer"}, []string{"int64", "error"}}, // io.WriterTo 55 } 56 57 func (f *File) checkCanonicalMethod(id *ast.Ident, t *ast.FuncType) { 58 if !vet("methods") { 59 return 60 } 61 // Expected input/output. 62 expect, ok := canonicalMethods[id.Name] 63 if !ok { 64 return 65 } 66 67 // Actual input/output 68 args := typeFlatten(t.Params.List) 69 var results []ast.Expr 70 if t.Results != nil { 71 results = typeFlatten(t.Results.List) 72 } 73 74 // Do the =s (if any) all match? 75 if !f.matchParams(expect.args, args, "=") || !f.matchParams(expect.results, results, "=") { 76 return 77 } 78 79 // Everything must match. 80 if !f.matchParams(expect.args, args, "") || !f.matchParams(expect.results, results, "") { 81 expectFmt := id.Name + "(" + argjoin(expect.args) + ")" 82 if len(expect.results) == 1 { 83 expectFmt += " " + argjoin(expect.results) 84 } else if len(expect.results) > 1 { 85 expectFmt += " (" + argjoin(expect.results) + ")" 86 } 87 88 f.b.Reset() 89 if err := printer.Fprint(&f.b, f.fset, t); err != nil { 90 fmt.Fprintf(&f.b, "<%s>", err) 91 } 92 actual := f.b.String() 93 actual = strings.TrimPrefix(actual, "func") 94 actual = id.Name + actual 95 96 f.Badf(id.Pos(), "method %s should have signature %s", actual, expectFmt) 97 } 98 } 99 100 func argjoin(x []string) string { 101 y := make([]string, len(x)) 102 for i, s := range x { 103 if s[0] == '=' { 104 s = s[1:] 105 } 106 y[i] = s 107 } 108 return strings.Join(y, ", ") 109 } 110 111 // Turn parameter list into slice of types 112 // (in the ast, types are Exprs). 113 // Have to handle f(int, bool) and f(x, y, z int) 114 // so not a simple 1-to-1 conversion. 115 func typeFlatten(l []*ast.Field) []ast.Expr { 116 var t []ast.Expr 117 for _, f := range l { 118 if len(f.Names) == 0 { 119 t = append(t, f.Type) 120 continue 121 } 122 for _ = range f.Names { 123 t = append(t, f.Type) 124 } 125 } 126 return t 127 } 128 129 // Does each type in expect with the given prefix match the corresponding type in actual? 130 func (f *File) matchParams(expect []string, actual []ast.Expr, prefix string) bool { 131 for i, x := range expect { 132 if !strings.HasPrefix(x, prefix) { 133 continue 134 } 135 if i >= len(actual) { 136 return false 137 } 138 if !f.matchParamType(x, actual[i]) { 139 return false 140 } 141 } 142 if prefix == "" && len(actual) > len(expect) { 143 return false 144 } 145 return true 146 } 147 148 // Does this one type match? 149 func (f *File) matchParamType(expect string, actual ast.Expr) bool { 150 if strings.HasPrefix(expect, "=") { 151 expect = expect[1:] 152 } 153 // Strip package name if we're in that package. 154 if n := len(f.file.Name.Name); len(expect) > n && expect[:n] == f.file.Name.Name && expect[n] == '.' { 155 expect = expect[n+1:] 156 } 157 158 // Overkill but easy. 159 f.b.Reset() 160 printer.Fprint(&f.b, f.fset, actual) 161 return f.b.String() == expect 162 }