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