...

Source file src/cmd/vendor/golang.org/x/tools/go/analysis/passes/stdmethods/stdmethods.go

     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	// Package stdmethods defines an Analyzer that checks for misspellings
     6	// in the signatures of methods similar to well-known interfaces.
     7	package stdmethods
     8	
     9	import (
    10		"go/ast"
    11		"go/types"
    12		"strings"
    13	
    14		"golang.org/x/tools/go/analysis"
    15		"golang.org/x/tools/go/analysis/passes/inspect"
    16		"golang.org/x/tools/go/ast/inspector"
    17	)
    18	
    19	const Doc = `check signature of methods of well-known interfaces
    20	
    21	Sometimes a type may be intended to satisfy an interface but may fail to
    22	do so because of a mistake in its method signature.
    23	For example, the result of this WriteTo method should be (int64, error),
    24	not error, to satisfy io.WriterTo:
    25	
    26		type myWriterTo struct{...}
    27	        func (myWriterTo) WriteTo(w io.Writer) error { ... }
    28	
    29	This check ensures that each method whose name matches one of several
    30	well-known interface methods from the standard library has the correct
    31	signature for that interface.
    32	
    33	Checked method names include:
    34		Format GobEncode GobDecode MarshalJSON MarshalXML
    35		Peek ReadByte ReadFrom ReadRune Scan Seek
    36		UnmarshalJSON UnreadByte UnreadRune WriteByte
    37		WriteTo
    38	`
    39	
    40	var Analyzer = &analysis.Analyzer{
    41		Name:     "stdmethods",
    42		Doc:      Doc,
    43		Requires: []*analysis.Analyzer{inspect.Analyzer},
    44		Run:      run,
    45	}
    46	
    47	// canonicalMethods lists the input and output types for Go methods
    48	// that are checked using dynamic interface checks. Because the
    49	// checks are dynamic, such methods would not cause a compile error
    50	// if they have the wrong signature: instead the dynamic check would
    51	// fail, sometimes mysteriously. If a method is found with a name listed
    52	// here but not the input/output types listed here, vet complains.
    53	//
    54	// A few of the canonical methods have very common names.
    55	// For example, a type might implement a Scan method that
    56	// has nothing to do with fmt.Scanner, but we still want to check
    57	// the methods that are intended to implement fmt.Scanner.
    58	// To do that, the arguments that have a = prefix are treated as
    59	// signals that the canonical meaning is intended: if a Scan
    60	// method doesn't have a fmt.ScanState as its first argument,
    61	// we let it go. But if it does have a fmt.ScanState, then the
    62	// rest has to match.
    63	var canonicalMethods = map[string]struct{ args, results []string }{
    64		// "Flush": {{}, {"error"}}, // http.Flusher and jpeg.writer conflict
    65		"Format":        {[]string{"=fmt.State", "rune"}, []string{}},                      // fmt.Formatter
    66		"GobDecode":     {[]string{"[]byte"}, []string{"error"}},                           // gob.GobDecoder
    67		"GobEncode":     {[]string{}, []string{"[]byte", "error"}},                         // gob.GobEncoder
    68		"MarshalJSON":   {[]string{}, []string{"[]byte", "error"}},                         // json.Marshaler
    69		"MarshalXML":    {[]string{"*xml.Encoder", "xml.StartElement"}, []string{"error"}}, // xml.Marshaler
    70		"ReadByte":      {[]string{}, []string{"byte", "error"}},                           // io.ByteReader
    71		"ReadFrom":      {[]string{"=io.Reader"}, []string{"int64", "error"}},              // io.ReaderFrom
    72		"ReadRune":      {[]string{}, []string{"rune", "int", "error"}},                    // io.RuneReader
    73		"Scan":          {[]string{"=fmt.ScanState", "rune"}, []string{"error"}},           // fmt.Scanner
    74		"Seek":          {[]string{"=int64", "int"}, []string{"int64", "error"}},           // io.Seeker
    75		"UnmarshalJSON": {[]string{"[]byte"}, []string{"error"}},                           // json.Unmarshaler
    76		"UnmarshalXML":  {[]string{"*xml.Decoder", "xml.StartElement"}, []string{"error"}}, // xml.Unmarshaler
    77		"UnreadByte":    {[]string{}, []string{"error"}},
    78		"UnreadRune":    {[]string{}, []string{"error"}},
    79		"WriteByte":     {[]string{"byte"}, []string{"error"}},                // jpeg.writer (matching bufio.Writer)
    80		"WriteTo":       {[]string{"=io.Writer"}, []string{"int64", "error"}}, // io.WriterTo
    81	}
    82	
    83	func run(pass *analysis.Pass) (interface{}, error) {
    84		inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    85	
    86		nodeFilter := []ast.Node{
    87			(*ast.FuncDecl)(nil),
    88			(*ast.InterfaceType)(nil),
    89		}
    90		inspect.Preorder(nodeFilter, func(n ast.Node) {
    91			switch n := n.(type) {
    92			case *ast.FuncDecl:
    93				if n.Recv != nil {
    94					canonicalMethod(pass, n.Name)
    95				}
    96			case *ast.InterfaceType:
    97				for _, field := range n.Methods.List {
    98					for _, id := range field.Names {
    99						canonicalMethod(pass, id)
   100					}
   101				}
   102			}
   103		})
   104		return nil, nil
   105	}
   106	
   107	func canonicalMethod(pass *analysis.Pass, id *ast.Ident) {
   108		// Expected input/output.
   109		expect, ok := canonicalMethods[id.Name]
   110		if !ok {
   111			return
   112		}
   113	
   114		// Actual input/output
   115		sign := pass.TypesInfo.Defs[id].Type().(*types.Signature)
   116		args := sign.Params()
   117		results := sign.Results()
   118	
   119		// Special case: WriteTo with more than one argument,
   120		// not trying at all to implement io.WriterTo,
   121		// comes up often enough to skip.
   122		if id.Name == "WriteTo" && args.Len() > 1 {
   123			return
   124		}
   125	
   126		// Do the =s (if any) all match?
   127		if !matchParams(pass, expect.args, args, "=") || !matchParams(pass, expect.results, results, "=") {
   128			return
   129		}
   130	
   131		// Everything must match.
   132		if !matchParams(pass, expect.args, args, "") || !matchParams(pass, expect.results, results, "") {
   133			expectFmt := id.Name + "(" + argjoin(expect.args) + ")"
   134			if len(expect.results) == 1 {
   135				expectFmt += " " + argjoin(expect.results)
   136			} else if len(expect.results) > 1 {
   137				expectFmt += " (" + argjoin(expect.results) + ")"
   138			}
   139	
   140			actual := typeString(sign)
   141			actual = strings.TrimPrefix(actual, "func")
   142			actual = id.Name + actual
   143	
   144			pass.Reportf(id.Pos(), "method %s should have signature %s", actual, expectFmt)
   145		}
   146	}
   147	
   148	func typeString(typ types.Type) string {
   149		return types.TypeString(typ, (*types.Package).Name)
   150	}
   151	
   152	func argjoin(x []string) string {
   153		y := make([]string, len(x))
   154		for i, s := range x {
   155			if s[0] == '=' {
   156				s = s[1:]
   157			}
   158			y[i] = s
   159		}
   160		return strings.Join(y, ", ")
   161	}
   162	
   163	// Does each type in expect with the given prefix match the corresponding type in actual?
   164	func matchParams(pass *analysis.Pass, expect []string, actual *types.Tuple, prefix string) bool {
   165		for i, x := range expect {
   166			if !strings.HasPrefix(x, prefix) {
   167				continue
   168			}
   169			if i >= actual.Len() {
   170				return false
   171			}
   172			if !matchParamType(x, actual.At(i).Type()) {
   173				return false
   174			}
   175		}
   176		if prefix == "" && actual.Len() > len(expect) {
   177			return false
   178		}
   179		return true
   180	}
   181	
   182	// Does this one type match?
   183	func matchParamType(expect string, actual types.Type) bool {
   184		expect = strings.TrimPrefix(expect, "=")
   185		// Overkill but easy.
   186		return typeString(actual) == expect
   187	}
   188	

View as plain text