github.com/miolini/go@v0.0.0-20160405192216-fca68c8cb408/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  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  	"Peek":          {[]string{"=int"}, []string{"[]byte", "error"}},                   // image.reader (matching bufio.Reader)
    52  	"ReadByte":      {[]string{}, []string{"byte", "error"}},                           // io.ByteReader
    53  	"ReadFrom":      {[]string{"=io.Reader"}, []string{"int64", "error"}},              // io.ReaderFrom
    54  	"ReadRune":      {[]string{}, []string{"rune", "int", "error"}},                    // io.RuneReader
    55  	"Scan":          {[]string{"=fmt.ScanState", "rune"}, []string{"error"}},           // fmt.Scanner
    56  	"Seek":          {[]string{"=int64", "int"}, []string{"int64", "error"}},           // io.Seeker
    57  	"UnmarshalJSON": {[]string{"[]byte"}, []string{"error"}},                           // json.Unmarshaler
    58  	"UnmarshalXML":  {[]string{"*xml.Decoder", "xml.StartElement"}, []string{"error"}}, // xml.Unmarshaler
    59  	"UnreadByte":    {[]string{}, []string{"error"}},
    60  	"UnreadRune":    {[]string{}, []string{"error"}},
    61  	"WriteByte":     {[]string{"byte"}, []string{"error"}},                // jpeg.writer (matching bufio.Writer)
    62  	"WriteTo":       {[]string{"=io.Writer"}, []string{"int64", "error"}}, // io.WriterTo
    63  }
    64  
    65  func checkCanonicalMethod(f *File, node ast.Node) {
    66  	switch n := node.(type) {
    67  	case *ast.FuncDecl:
    68  		if n.Recv != nil {
    69  			canonicalMethod(f, n.Name, n.Type)
    70  		}
    71  	case *ast.InterfaceType:
    72  		for _, field := range n.Methods.List {
    73  			for _, id := range field.Names {
    74  				canonicalMethod(f, id, field.Type.(*ast.FuncType))
    75  			}
    76  		}
    77  	}
    78  }
    79  
    80  func canonicalMethod(f *File, id *ast.Ident, t *ast.FuncType) {
    81  	// Expected input/output.
    82  	expect, ok := canonicalMethods[id.Name]
    83  	if !ok {
    84  		return
    85  	}
    86  
    87  	// Actual input/output
    88  	args := typeFlatten(t.Params.List)
    89  	var results []ast.Expr
    90  	if t.Results != nil {
    91  		results = typeFlatten(t.Results.List)
    92  	}
    93  
    94  	// Do the =s (if any) all match?
    95  	if !f.matchParams(expect.args, args, "=") || !f.matchParams(expect.results, results, "=") {
    96  		return
    97  	}
    98  
    99  	// Everything must match.
   100  	if !f.matchParams(expect.args, args, "") || !f.matchParams(expect.results, results, "") {
   101  		expectFmt := id.Name + "(" + argjoin(expect.args) + ")"
   102  		if len(expect.results) == 1 {
   103  			expectFmt += " " + argjoin(expect.results)
   104  		} else if len(expect.results) > 1 {
   105  			expectFmt += " (" + argjoin(expect.results) + ")"
   106  		}
   107  
   108  		f.b.Reset()
   109  		if err := printer.Fprint(&f.b, f.fset, t); err != nil {
   110  			fmt.Fprintf(&f.b, "<%s>", err)
   111  		}
   112  		actual := f.b.String()
   113  		actual = strings.TrimPrefix(actual, "func")
   114  		actual = id.Name + actual
   115  
   116  		f.Badf(id.Pos(), "method %s should have signature %s", actual, expectFmt)
   117  	}
   118  }
   119  
   120  func argjoin(x []string) string {
   121  	y := make([]string, len(x))
   122  	for i, s := range x {
   123  		if s[0] == '=' {
   124  			s = s[1:]
   125  		}
   126  		y[i] = s
   127  	}
   128  	return strings.Join(y, ", ")
   129  }
   130  
   131  // Turn parameter list into slice of types
   132  // (in the ast, types are Exprs).
   133  // Have to handle f(int, bool) and f(x, y, z int)
   134  // so not a simple 1-to-1 conversion.
   135  func typeFlatten(l []*ast.Field) []ast.Expr {
   136  	var t []ast.Expr
   137  	for _, f := range l {
   138  		if len(f.Names) == 0 {
   139  			t = append(t, f.Type)
   140  			continue
   141  		}
   142  		for _ = range f.Names {
   143  			t = append(t, f.Type)
   144  		}
   145  	}
   146  	return t
   147  }
   148  
   149  // Does each type in expect with the given prefix match the corresponding type in actual?
   150  func (f *File) matchParams(expect []string, actual []ast.Expr, prefix string) bool {
   151  	for i, x := range expect {
   152  		if !strings.HasPrefix(x, prefix) {
   153  			continue
   154  		}
   155  		if i >= len(actual) {
   156  			return false
   157  		}
   158  		if !f.matchParamType(x, actual[i]) {
   159  			return false
   160  		}
   161  	}
   162  	if prefix == "" && len(actual) > len(expect) {
   163  		return false
   164  	}
   165  	return true
   166  }
   167  
   168  // Does this one type match?
   169  func (f *File) matchParamType(expect string, actual ast.Expr) bool {
   170  	if strings.HasPrefix(expect, "=") {
   171  		expect = expect[1:]
   172  	}
   173  	// Strip package name if we're in that package.
   174  	if n := len(f.file.Name.Name); len(expect) > n && expect[:n] == f.file.Name.Name && expect[n] == '.' {
   175  		expect = expect[n+1:]
   176  	}
   177  
   178  	// Overkill but easy.
   179  	f.b.Reset()
   180  	printer.Fprint(&f.b, f.fset, actual)
   181  	return f.b.String() == expect
   182  }