s1011.go raw

   1  package s1011
   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/facts/purity"
  13  	"honnef.co/go/tools/analysis/lint"
  14  	"honnef.co/go/tools/analysis/report"
  15  	"honnef.co/go/tools/pattern"
  16  
  17  	"golang.org/x/tools/go/analysis"
  18  	"golang.org/x/tools/go/analysis/passes/inspect"
  19  )
  20  
  21  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
  22  	Analyzer: &analysis.Analyzer{
  23  		Name:     "S1011",
  24  		Run:      run,
  25  		Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer, purity.Analyzer},
  26  	},
  27  	Doc: &lint.RawDocumentation{
  28  		Title: `Use a single \'append\' to concatenate two slices`,
  29  		Before: `
  30  for _, e := range y {
  31      x = append(x, e)
  32  }
  33  
  34  for i := range y {
  35      x = append(x, y[i])
  36  }
  37  
  38  for i := range y {
  39      v := y[i]
  40      x = append(x, v)
  41  }`,
  42  
  43  		After: `
  44  x = append(x, y...)
  45  x = append(x, y...)
  46  x = append(x, y...)`,
  47  		Since: "2017.1",
  48  		// MergeIfAll because y might not be a slice under all build tags.
  49  		MergeIf: lint.MergeIfAll,
  50  	},
  51  })
  52  
  53  var Analyzer = SCAnalyzer.Analyzer
  54  
  55  var checkLoopAppendQ = pattern.MustParse(`
  56  (Or
  57  	(RangeStmt
  58  		(Ident "_")
  59  		val@(Object _)
  60  		_
  61  		x
  62  		[(AssignStmt [lhs] "=" [(CallExpr (Builtin "append") [lhs val])])])
  63  	(RangeStmt
  64  		idx@(Object _)
  65  		nil
  66  		_
  67  		x
  68  		[(AssignStmt [lhs] "=" [(CallExpr (Builtin "append") [lhs (IndexExpr x idx)])])])
  69  	(RangeStmt
  70  		idx@(Object _)
  71  		nil
  72  		_
  73  		x
  74  		[(AssignStmt val@(Object _) ":=" (IndexExpr x idx))
  75  		(AssignStmt [lhs] "=" [(CallExpr (Builtin "append") [lhs val])])]))`)
  76  
  77  func run(pass *analysis.Pass) (interface{}, error) {
  78  	pure := pass.ResultOf[purity.Analyzer].(purity.Result)
  79  
  80  	fn := func(node ast.Node) {
  81  		m, ok := code.Match(pass, checkLoopAppendQ, node)
  82  		if !ok {
  83  			return
  84  		}
  85  
  86  		if val, ok := m.State["val"].(types.Object); ok && code.RefersTo(pass, m.State["lhs"].(ast.Expr), val) {
  87  			return
  88  		}
  89  
  90  		if m.State["idx"] != nil && code.MayHaveSideEffects(pass, m.State["x"].(ast.Expr), pure) {
  91  			// When using an index-based loop, x gets evaluated repeatedly and thus should be pure.
  92  			// This doesn't matter for value-based loops, because x only gets evaluated once.
  93  			return
  94  		}
  95  
  96  		if idx, ok := m.State["idx"].(types.Object); ok && code.RefersTo(pass, m.State["lhs"].(ast.Expr), idx) {
  97  			// The lhs mustn't refer to the index loop variable.
  98  			return
  99  		}
 100  
 101  		if code.MayHaveSideEffects(pass, m.State["lhs"].(ast.Expr), pure) {
 102  			// The lhs may be dynamic and return different values on each iteration. For example:
 103  			//
 104  			// 	func bar() map[int][]int { /* return one of several maps */ }
 105  			//
 106  			// 	func foo(x []int, y [][]int) {
 107  			// 		for i := range x {
 108  			// 			bar()[0] = append(bar()[0], x[i])
 109  			// 		}
 110  			// 	}
 111  			//
 112  			// The dynamic nature of the lhs might also affect the value of the index.
 113  			return
 114  		}
 115  
 116  		src := pass.TypesInfo.TypeOf(m.State["x"].(ast.Expr))
 117  		dst := pass.TypesInfo.TypeOf(m.State["lhs"].(ast.Expr))
 118  		if !types.Identical(src, dst) {
 119  			return
 120  		}
 121  
 122  		r := &ast.AssignStmt{
 123  			Lhs: []ast.Expr{m.State["lhs"].(ast.Expr)},
 124  			Tok: token.ASSIGN,
 125  			Rhs: []ast.Expr{
 126  				&ast.CallExpr{
 127  					Fun: &ast.Ident{Name: "append"},
 128  					Args: []ast.Expr{
 129  						m.State["lhs"].(ast.Expr),
 130  						m.State["x"].(ast.Expr),
 131  					},
 132  					Ellipsis: 1,
 133  				},
 134  			},
 135  		}
 136  
 137  		report.Report(pass, node, fmt.Sprintf("should replace loop with %s", report.Render(pass, r)),
 138  			report.ShortRange(),
 139  			report.FilterGenerated(),
 140  			report.Fixes(edit.Fix("replace loop with call to append", edit.ReplaceWithNode(pass.Fset, node, r))))
 141  	}
 142  	code.Preorder(pass, fn, (*ast.RangeStmt)(nil))
 143  	return nil, nil
 144  }
 145