...
Run Format

Source file src/cmd/vet/method.go

Documentation: cmd/vet

     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  func init() {
    17  	register("methods",
    18  		"check that canonically named methods are canonically defined",
    19  		checkCanonicalMethod,
    20  		funcDecl, interfaceType)
    21  }
    22  
    23  type MethodSig struct {
    24  	args    []string
    25  	results []string
    26  }
    27  
    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.
    34  //
    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
    43  // rest has to match.
    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
    62  }
    63  
    64  func checkCanonicalMethod(f *File, node ast.Node) {
    65  	switch n := node.(type) {
    66  	case *ast.FuncDecl:
    67  		if n.Recv != nil {
    68  			canonicalMethod(f, n.Name, n.Type)
    69  		}
    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))
    74  			}
    75  		}
    76  	}
    77  }
    78  
    79  func canonicalMethod(f *File, id *ast.Ident, t *ast.FuncType) {
    80  	// Expected input/output.
    81  	expect, ok := canonicalMethods[id.Name]
    82  	if !ok {
    83  		return
    84  	}
    85  
    86  	// Actual input/output
    87  	args := typeFlatten(t.Params.List)
    88  	var results []ast.Expr
    89  	if t.Results != nil {
    90  		results = typeFlatten(t.Results.List)
    91  	}
    92  
    93  	// Do the =s (if any) all match?
    94  	if !f.matchParams(expect.args, args, "=") || !f.matchParams(expect.results, results, "=") {
    95  		return
    96  	}
    97  
    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) + ")"
   105  		}
   106  
   107  		f.b.Reset()
   108  		if err := printer.Fprint(&f.b, f.fset, t); err != nil {
   109  			fmt.Fprintf(&f.b, "<%s>", err)
   110  		}
   111  		actual := f.b.String()
   112  		actual = strings.TrimPrefix(actual, "func")
   113  		actual = id.Name + actual
   114  
   115  		f.Badf(id.Pos(), "method %s should have signature %s", actual, expectFmt)
   116  	}
   117  }
   118  
   119  func argjoin(x []string) string {
   120  	y := make([]string, len(x))
   121  	for i, s := range x {
   122  		if s[0] == '=' {
   123  			s = s[1:]
   124  		}
   125  		y[i] = s
   126  	}
   127  	return strings.Join(y, ", ")
   128  }
   129  
   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 {
   135  	var t []ast.Expr
   136  	for _, f := range l {
   137  		if len(f.Names) == 0 {
   138  			t = append(t, f.Type)
   139  			continue
   140  		}
   141  		for range f.Names {
   142  			t = append(t, f.Type)
   143  		}
   144  	}
   145  	return t
   146  }
   147  
   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) {
   152  			continue
   153  		}
   154  		if i >= len(actual) {
   155  			return false
   156  		}
   157  		if !f.matchParamType(x, actual[i]) {
   158  			return false
   159  		}
   160  	}
   161  	if prefix == "" && len(actual) > len(expect) {
   162  		return false
   163  	}
   164  	return true
   165  }
   166  
   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:]
   173  	}
   174  
   175  	// Overkill but easy.
   176  	f.b.Reset()
   177  	printer.Fprint(&f.b, f.fset, actual)
   178  	return f.b.String() == expect
   179  }
   180  

View as plain text