Home | History | Annotate | Download | only in 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 	"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 }
    183