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 }