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 	"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 	if strings.HasPrefix(expect, "=") {
    170 		expect = expect[1:]
    171 	}
    172 	// Strip package name if we're in that package.
    173 	if n := len(f.file.Name.Name); len(expect) > n && expect[:n] == f.file.Name.Name && expect[n] == '.' {
    174 		expect = expect[n+1:]
    175 	}
    176 
    177 	// Overkill but easy.
    178 	f.b.Reset()
    179 	printer.Fprint(&f.b, f.fset, actual)
    180 	return f.b.String() == expect
    181 }
    182