github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/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  type MethodSig struct {
    17  	args    []string
    18  	results []string
    19  }
    20  
    21  // canonicalMethods lists the input and output types for Go methods
    22  // that are checked using dynamic interface checks.  Because the
    23  // checks are dynamic, such methods would not cause a compile error
    24  // if they have the wrong signature: instead the dynamic check would
    25  // fail, sometimes mysteriously.  If a method is found with a name listed
    26  // here but not the input/output types listed here, vet complains.
    27  //
    28  // A few of the canonical methods have very common names.
    29  // For example, a type might implement a Scan method that
    30  // has nothing to do with fmt.Scanner, but we still want to check
    31  // the methods that are intended to implement fmt.Scanner.
    32  // To do that, the arguments that have a = prefix are treated as
    33  // signals that the canonical meaning is intended: if a Scan
    34  // method doesn't have a fmt.ScanState as its first argument,
    35  // we let it go.  But if it does have a fmt.ScanState, then the
    36  // rest has to match.
    37  var canonicalMethods = map[string]MethodSig{
    38  	// "Flush": {{}, {"error"}}, // http.Flusher and jpeg.writer conflict
    39  	"Format":        {[]string{"=fmt.State", "rune"}, []string{}},            // fmt.Formatter
    40  	"GobDecode":     {[]string{"[]byte"}, []string{"error"}},                 // gob.GobDecoder
    41  	"GobEncode":     {[]string{}, []string{"[]byte", "error"}},               // gob.GobEncoder
    42  	"MarshalJSON":   {[]string{}, []string{"[]byte", "error"}},               // json.Marshaler
    43  	"MarshalXML":    {[]string{}, []string{"[]byte", "error"}},               // xml.Marshaler
    44  	"Peek":          {[]string{"=int"}, []string{"[]byte", "error"}},         // image.reader (matching bufio.Reader)
    45  	"ReadByte":      {[]string{}, []string{"byte", "error"}},                 // io.ByteReader
    46  	"ReadFrom":      {[]string{"=io.Reader"}, []string{"int64", "error"}},    // io.ReaderFrom
    47  	"ReadRune":      {[]string{}, []string{"rune", "int", "error"}},          // io.RuneReader
    48  	"Scan":          {[]string{"=fmt.ScanState", "rune"}, []string{"error"}}, // fmt.Scanner
    49  	"Seek":          {[]string{"=int64", "int"}, []string{"int64", "error"}}, // io.Seeker
    50  	"UnmarshalJSON": {[]string{"[]byte"}, []string{"error"}},                 // json.Unmarshaler
    51  	"UnreadByte":    {[]string{}, []string{"error"}},
    52  	"UnreadRune":    {[]string{}, []string{"error"}},
    53  	"WriteByte":     {[]string{"byte"}, []string{"error"}},                // jpeg.writer (matching bufio.Writer)
    54  	"WriteTo":       {[]string{"=io.Writer"}, []string{"int64", "error"}}, // io.WriterTo
    55  }
    56  
    57  func (f *File) checkCanonicalMethod(id *ast.Ident, t *ast.FuncType) {
    58  	if !vet("methods") {
    59  		return
    60  	}
    61  	// Expected input/output.
    62  	expect, ok := canonicalMethods[id.Name]
    63  	if !ok {
    64  		return
    65  	}
    66  
    67  	// Actual input/output
    68  	args := typeFlatten(t.Params.List)
    69  	var results []ast.Expr
    70  	if t.Results != nil {
    71  		results = typeFlatten(t.Results.List)
    72  	}
    73  
    74  	// Do the =s (if any) all match?
    75  	if !f.matchParams(expect.args, args, "=") || !f.matchParams(expect.results, results, "=") {
    76  		return
    77  	}
    78  
    79  	// Everything must match.
    80  	if !f.matchParams(expect.args, args, "") || !f.matchParams(expect.results, results, "") {
    81  		expectFmt := id.Name + "(" + argjoin(expect.args) + ")"
    82  		if len(expect.results) == 1 {
    83  			expectFmt += " " + argjoin(expect.results)
    84  		} else if len(expect.results) > 1 {
    85  			expectFmt += " (" + argjoin(expect.results) + ")"
    86  		}
    87  
    88  		f.b.Reset()
    89  		if err := printer.Fprint(&f.b, f.fset, t); err != nil {
    90  			fmt.Fprintf(&f.b, "<%s>", err)
    91  		}
    92  		actual := f.b.String()
    93  		actual = strings.TrimPrefix(actual, "func")
    94  		actual = id.Name + actual
    95  
    96  		f.Badf(id.Pos(), "method %s should have signature %s", actual, expectFmt)
    97  	}
    98  }
    99  
   100  func argjoin(x []string) string {
   101  	y := make([]string, len(x))
   102  	for i, s := range x {
   103  		if s[0] == '=' {
   104  			s = s[1:]
   105  		}
   106  		y[i] = s
   107  	}
   108  	return strings.Join(y, ", ")
   109  }
   110  
   111  // Turn parameter list into slice of types
   112  // (in the ast, types are Exprs).
   113  // Have to handle f(int, bool) and f(x, y, z int)
   114  // so not a simple 1-to-1 conversion.
   115  func typeFlatten(l []*ast.Field) []ast.Expr {
   116  	var t []ast.Expr
   117  	for _, f := range l {
   118  		if len(f.Names) == 0 {
   119  			t = append(t, f.Type)
   120  			continue
   121  		}
   122  		for _ = range f.Names {
   123  			t = append(t, f.Type)
   124  		}
   125  	}
   126  	return t
   127  }
   128  
   129  // Does each type in expect with the given prefix match the corresponding type in actual?
   130  func (f *File) matchParams(expect []string, actual []ast.Expr, prefix string) bool {
   131  	for i, x := range expect {
   132  		if !strings.HasPrefix(x, prefix) {
   133  			continue
   134  		}
   135  		if i >= len(actual) {
   136  			return false
   137  		}
   138  		if !f.matchParamType(x, actual[i]) {
   139  			return false
   140  		}
   141  	}
   142  	if prefix == "" && len(actual) > len(expect) {
   143  		return false
   144  	}
   145  	return true
   146  }
   147  
   148  // Does this one type match?
   149  func (f *File) matchParamType(expect string, actual ast.Expr) bool {
   150  	if strings.HasPrefix(expect, "=") {
   151  		expect = expect[1:]
   152  	}
   153  	// Strip package name if we're in that package.
   154  	if n := len(f.file.Name.Name); len(expect) > n && expect[:n] == f.file.Name.Name && expect[n] == '.' {
   155  		expect = expect[n+1:]
   156  	}
   157  
   158  	// Overkill but easy.
   159  	f.b.Reset()
   160  	printer.Fprint(&f.b, f.fset, actual)
   161  	return f.b.String() == expect
   162  }