src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

s1001.go (4914B)


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