s1001.go raw

   1  package s1001
   2  
   3  import (
   4  	"fmt"
   5  	"go/ast"
   6  	"go/token"
   7  	"go/types"
   8  
   9  	"honnef.co/go/tools/analysis/code"
  10  	"honnef.co/go/tools/analysis/edit"
  11  	"honnef.co/go/tools/analysis/facts/generated"
  12  	"honnef.co/go/tools/analysis/lint"
  13  	"honnef.co/go/tools/analysis/report"
  14  	"honnef.co/go/tools/pattern"
  15  
  16  	"golang.org/x/tools/go/analysis"
  17  	"golang.org/x/tools/go/analysis/passes/inspect"
  18  )
  19  
  20  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
  21  	Analyzer: &analysis.Analyzer{
  22  		Name:     "S1001",
  23  		Run:      run,
  24  		Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
  25  	},
  26  	Doc: &lint.RawDocumentation{
  27  		Title: `Replace for loop with call to copy`,
  28  		Text: `
  29  Use \'copy()\' for copying elements from one slice to another. For
  30  arrays of identical size, you can use simple assignment.`,
  31  		Before: `
  32  for i, x := range src {
  33      dst[i] = x
  34  }`,
  35  		After: `copy(dst, src)`,
  36  		Since: "2017.1",
  37  		// MergeIfAll because the types of src and dst might be different under different build tags.
  38  		// You shouldn't write code like that…
  39  		MergeIf: lint.MergeIfAll,
  40  	},
  41  })
  42  
  43  var Analyzer = SCAnalyzer.Analyzer
  44  
  45  var (
  46  	checkLoopCopyQ = pattern.MustParse(`
  47  		(Or
  48  			(RangeStmt
  49  				key@(Ident _) value@(Ident _) ":=" src
  50  				[(AssignStmt (IndexExpr dst key) "=" value)])
  51  			(RangeStmt
  52  				key@(Ident _) nil ":=" src
  53  				[(AssignStmt (IndexExpr dst key) "=" (IndexExpr src key))])
  54  			(ForStmt
  55  				(AssignStmt key@(Ident _) ":=" (IntegerLiteral "0"))
  56  				(BinaryExpr key "<" (CallExpr (Symbol "len") [src]))
  57  				(IncDecStmt key "++")
  58  				[(AssignStmt (IndexExpr dst key) "=" (IndexExpr src key))]))`)
  59  )
  60  
  61  func run(pass *analysis.Pass) (interface{}, error) {
  62  	// TODO revisit once range doesn't require a structural type
  63  
  64  	isInvariant := func(k, v types.Object, node ast.Expr) bool {
  65  		if code.MayHaveSideEffects(pass, node, nil) {
  66  			return false
  67  		}
  68  		invariant := true
  69  		ast.Inspect(node, func(node ast.Node) bool {
  70  			if node, ok := node.(*ast.Ident); ok {
  71  				obj := pass.TypesInfo.ObjectOf(node)
  72  				if obj == k || obj == v {
  73  					// don't allow loop bodies like 'a[i][i] = v'
  74  					invariant = false
  75  					return false
  76  				}
  77  			}
  78  			return true
  79  		})
  80  		return invariant
  81  	}
  82  
  83  	var elType func(T types.Type) (el types.Type, isArray bool, isArrayPointer bool, ok bool)
  84  	elType = func(T types.Type) (el types.Type, isArray bool, isArrayPointer bool, ok bool) {
  85  		switch typ := T.Underlying().(type) {
  86  		case *types.Slice:
  87  			return typ.Elem(), false, false, true
  88  		case *types.Array:
  89  			return typ.Elem(), true, false, true
  90  		case *types.Pointer:
  91  			el, isArray, _, ok = elType(typ.Elem())
  92  			return el, isArray, true, ok
  93  		default:
  94  			return nil, false, false, false
  95  		}
  96  	}
  97  
  98  	fn := func(node ast.Node) {
  99  		m, ok := code.Match(pass, checkLoopCopyQ, node)
 100  		if !ok {
 101  			return
 102  		}
 103  
 104  		src := m.State["src"].(ast.Expr)
 105  		dst := m.State["dst"].(ast.Expr)
 106  
 107  		k := pass.TypesInfo.ObjectOf(m.State["key"].(*ast.Ident))
 108  		var v types.Object
 109  		if value, ok := m.State["value"]; ok {
 110  			v = pass.TypesInfo.ObjectOf(value.(*ast.Ident))
 111  		}
 112  		if !isInvariant(k, v, dst) {
 113  			return
 114  		}
 115  		if !isInvariant(k, v, src) {
 116  			// For example: 'for i := range foo()'
 117  			return
 118  		}
 119  
 120  		Tsrc := pass.TypesInfo.TypeOf(src)
 121  		Tdst := pass.TypesInfo.TypeOf(dst)
 122  		TsrcElem, TsrcArray, TsrcPointer, ok := elType(Tsrc)
 123  		if !ok {
 124  			return
 125  		}
 126  		if TsrcPointer {
 127  			Tsrc = Tsrc.Underlying().(*types.Pointer).Elem()
 128  		}
 129  		TdstElem, TdstArray, TdstPointer, ok := elType(Tdst)
 130  		if !ok {
 131  			return
 132  		}
 133  		if TdstPointer {
 134  			Tdst = Tdst.Underlying().(*types.Pointer).Elem()
 135  		}
 136  
 137  		if !types.Identical(TsrcElem, TdstElem) {
 138  			return
 139  		}
 140  
 141  		if TsrcArray && TdstArray && types.Identical(Tsrc, Tdst) {
 142  			if TsrcPointer {
 143  				src = &ast.StarExpr{
 144  					X: src,
 145  				}
 146  			}
 147  			if TdstPointer {
 148  				dst = &ast.StarExpr{
 149  					X: dst,
 150  				}
 151  			}
 152  			r := &ast.AssignStmt{
 153  				Lhs: []ast.Expr{dst},
 154  				Rhs: []ast.Expr{src},
 155  				Tok: token.ASSIGN,
 156  			}
 157  
 158  			report.Report(pass, node, "should copy arrays using assignment instead of using a loop",
 159  				report.FilterGenerated(),
 160  				report.ShortRange(),
 161  				report.Fixes(edit.Fix("replace loop with assignment", edit.ReplaceWithNode(pass.Fset, node, r))))
 162  		} else {
 163  			tv, err := types.Eval(pass.Fset, pass.Pkg, node.Pos(), "copy")
 164  			if err == nil && tv.IsBuiltin() {
 165  				to := "to"
 166  				from := "from"
 167  				src := m.State["src"].(ast.Expr)
 168  				if TsrcArray {
 169  					from = "from[:]"
 170  					src = &ast.SliceExpr{
 171  						X: src,
 172  					}
 173  				}
 174  				dst := m.State["dst"].(ast.Expr)
 175  				if TdstArray {
 176  					to = "to[:]"
 177  					dst = &ast.SliceExpr{
 178  						X: dst,
 179  					}
 180  				}
 181  
 182  				r := &ast.CallExpr{
 183  					Fun:  &ast.Ident{Name: "copy"},
 184  					Args: []ast.Expr{dst, src},
 185  				}
 186  				opts := []report.Option{
 187  					report.ShortRange(),
 188  					report.FilterGenerated(),
 189  					report.Fixes(edit.Fix("replace loop with call to copy()", edit.ReplaceWithNode(pass.Fset, node, r))),
 190  				}
 191  				report.Report(pass, node, fmt.Sprintf("should use copy(%s, %s) instead of a loop", to, from), opts...)
 192  			}
 193  		}
 194  	}
 195  	code.Preorder(pass, fn, (*ast.ForStmt)(nil), (*ast.RangeStmt)(nil))
 196  	return nil, nil
 197  }
 198