1
2
3
4
5 package modernize
6
7
8
9 import (
10 "fmt"
11 "go/ast"
12 "go/token"
13 "go/types"
14
15 "golang.org/x/tools/go/analysis"
16 "golang.org/x/tools/go/analysis/passes/inspect"
17 "golang.org/x/tools/go/ast/inspector"
18 "golang.org/x/tools/internal/analysis/analyzerutil"
19 "golang.org/x/tools/internal/astutil"
20 "golang.org/x/tools/internal/refactor"
21 "golang.org/x/tools/internal/typeparams"
22 "golang.org/x/tools/internal/typesinternal"
23 "golang.org/x/tools/internal/versions"
24 )
25
26 var MapsLoopAnalyzer = &analysis.Analyzer{
27 Name: "mapsloop",
28 Doc: analyzerutil.MustExtractDoc(doc, "mapsloop"),
29 Requires: []*analysis.Analyzer{inspect.Analyzer},
30 Run: mapsloop,
31 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#mapsloop",
32 }
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54 func mapsloop(pass *analysis.Pass) (any, error) {
55
56
57 if within(pass, "maps", "bytes", "runtime") {
58 return nil, nil
59 }
60
61 info := pass.TypesInfo
62
63
64
65 check := func(file *ast.File, curRange inspector.Cursor, assign *ast.AssignStmt, m, x ast.Expr) {
66
67
68 tx := types.Unalias(info.TypeOf(x))
69 var xmap bool
70 switch typeparams.CoreType(tx).(type) {
71 case *types.Map:
72 xmap = true
73
74 case *types.Signature:
75 k, v, ok := assignableToIterSeq2(tx)
76 if !ok {
77 return
78 }
79 xmap = false
80
81
82
83
84 tx = types.NewMap(k, v)
85
86 default:
87 return
88 }
89
90
91
92
93
94
95 var mrhs ast.Expr
96 if curPrev, ok := curRange.PrevSibling(); ok {
97 if assign, ok := curPrev.Node().(*ast.AssignStmt); ok &&
98 len(assign.Lhs) == 1 &&
99 len(assign.Rhs) == 1 &&
100 astutil.EqualSyntax(assign.Lhs[0], m) {
101
102
103 var newMap bool
104 rhs := assign.Rhs[0]
105 switch rhs := ast.Unparen(rhs).(type) {
106 case *ast.CallExpr:
107 if id, ok := ast.Unparen(rhs.Fun).(*ast.Ident); ok &&
108 info.Uses[id] == builtinMake {
109
110 newMap = true
111 }
112 case *ast.CompositeLit:
113 if len(rhs.Elts) == 0 {
114
115 newMap = true
116 }
117 }
118
119
120 if newMap {
121 trhs := info.TypeOf(rhs)
122
123
124
125
126
127 if assign.Tok == token.DEFINE {
128
129
130 if types.Identical(tx, trhs) {
131 mrhs = rhs
132 }
133 } else {
134
135
136 if types.AssignableTo(tx, trhs) {
137 mrhs = rhs
138 }
139 }
140
141
142
143
144
145
146
147 if xmap {
148 mrhs = nil
149 }
150 }
151 }
152 }
153
154
155 var funcName string
156 if mrhs != nil {
157 funcName = cond(xmap, "Clone", "Collect")
158 } else {
159 funcName = cond(xmap, "Copy", "Insert")
160 }
161
162
163 rng := curRange.Node()
164 prefix, importEdits := refactor.AddImport(info, file, "maps", "maps", funcName, rng.Pos())
165 var (
166 newText []byte
167 start, end token.Pos
168 )
169 if mrhs != nil {
170
171
172
173
174
175
176
177
178
179 curPrev, _ := curRange.PrevSibling()
180 start, end = curPrev.Node().Pos(), rng.End()
181 newText = fmt.Appendf(nil, "%s%s = %s%s(%s)",
182 allComments(file, start, end),
183 astutil.Format(pass.Fset, m),
184 prefix,
185 funcName,
186 astutil.Format(pass.Fset, x))
187 } else {
188
189
190
191
192
193
194
195
196 start, end = rng.Pos(), rng.End()
197 newText = fmt.Appendf(nil, "%s%s%s(%s, %s)",
198 allComments(file, start, end),
199 prefix,
200 funcName,
201 astutil.Format(pass.Fset, m),
202 astutil.Format(pass.Fset, x))
203 }
204 pass.Report(analysis.Diagnostic{
205 Pos: assign.Lhs[0].Pos(),
206 End: assign.Lhs[0].End(),
207 Message: "Replace m[k]=v loop with maps." + funcName,
208 SuggestedFixes: []analysis.SuggestedFix{{
209 Message: "Replace m[k]=v loop with maps." + funcName,
210 TextEdits: append(importEdits, []analysis.TextEdit{{
211 Pos: start,
212 End: end,
213 NewText: newText,
214 }}...),
215 }},
216 })
217
218 }
219
220
221 for curFile := range filesUsingGoVersion(pass, versions.Go1_23) {
222 file := curFile.Node().(*ast.File)
223
224 for curRange := range curFile.Preorder((*ast.RangeStmt)(nil)) {
225 rng := curRange.Node().(*ast.RangeStmt)
226
227 if rng.Tok == token.DEFINE &&
228 rng.Key != nil &&
229 rng.Value != nil &&
230 isAssignBlock(rng.Body) {
231
232
233 assign := rng.Body.List[0].(*ast.AssignStmt)
234
235
236 usesKV := func(e ast.Expr) bool {
237 k := info.Defs[rng.Key.(*ast.Ident)]
238 v := info.Defs[rng.Value.(*ast.Ident)]
239 for n := range ast.Preorder(e) {
240 if id, ok := n.(*ast.Ident); ok {
241 obj := info.Uses[id]
242 if obj != nil &&
243 (obj == k || obj == v) {
244 return true
245 }
246 }
247 }
248 return false
249 }
250
251 if index, ok := assign.Lhs[0].(*ast.IndexExpr); ok &&
252 len(assign.Lhs) == 1 &&
253 astutil.EqualSyntax(rng.Key, index.Index) &&
254 astutil.EqualSyntax(rng.Value, assign.Rhs[0]) &&
255 !usesKV(index.X) {
256 if tmap, ok := typeparams.CoreType(info.TypeOf(index.X)).(*types.Map); ok &&
257 types.Identical(info.TypeOf(index), info.TypeOf(rng.Value)) &&
258 types.Identical(tmap.Key(), info.TypeOf(rng.Key)) {
259
260
261
262
263 check(file, curRange, assign, index.X, rng.X)
264 }
265 }
266 }
267 }
268 }
269 return nil, nil
270 }
271
272
273
274 func assignableToIterSeq2(t types.Type) (k, v types.Type, ok bool) {
275
276 if is[*types.Named](t) {
277 if !typesinternal.IsTypeNamed(t, "iter", "Seq2") {
278 return
279 }
280 t = t.Underlying()
281 }
282
283 if t, ok := t.(*types.Signature); ok {
284
285 if t.Params().Len() == 1 && t.Results().Len() == 0 {
286 if yield, ok := t.Params().At(0).Type().(*types.Signature); ok {
287 if yield.Params().Len() == 2 &&
288 yield.Results().Len() == 1 &&
289 types.Identical(yield.Results().At(0).Type(), builtinBool.Type()) {
290 return yield.Params().At(0).Type(), yield.Params().At(1).Type(), true
291 }
292 }
293 }
294 }
295 return
296 }
297
View as plain text