Source file src/cmd/vendor/golang.org/x/tools/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
     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  }
   203  

View as plain text