github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/simple/s1001/s1001.go (about) 1 package s1001 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/token" 7 "go/types" 8 9 "github.com/amarpal/go-tools/analysis/code" 10 "github.com/amarpal/go-tools/analysis/edit" 11 "github.com/amarpal/go-tools/analysis/facts/generated" 12 "github.com/amarpal/go-tools/analysis/lint" 13 "github.com/amarpal/go-tools/analysis/report" 14 "github.com/amarpal/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.Documentation{ 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 }