...

Source file src/golang.org/x/tools/go/analysis/passes/stdmethods/stdmethods.go

Documentation: golang.org/x/tools/go/analysis/passes/stdmethods

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

View as plain text