1 | // Copyright 2020 The Go Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style |
3 | // license that can be found in the LICENSE file. |
4 | |
5 | // Package analysisinternal provides gopls' internal analyses with a |
6 | // number of helper functions that operate on typed syntax trees. |
7 | package analysisinternal |
8 | |
9 | import ( |
10 | "bytes" |
11 | "fmt" |
12 | "go/ast" |
13 | "go/token" |
14 | "go/types" |
15 | "strconv" |
16 | ) |
17 | |
18 | // DiagnoseFuzzTests controls whether the 'tests' analyzer diagnoses fuzz tests |
19 | // in Go 1.18+. |
20 | var DiagnoseFuzzTests bool = false |
21 | |
22 | func TypeErrorEndPos(fset *token.FileSet, src []byte, start token.Pos) token.Pos { |
23 | // Get the end position for the type error. |
24 | offset, end := fset.PositionFor(start, false).Offset, start |
25 | if offset >= len(src) { |
26 | return end |
27 | } |
28 | if width := bytes.IndexAny(src[offset:], " \n,():;[]+-*"); width > 0 { |
29 | end = start + token.Pos(width) |
30 | } |
31 | return end |
32 | } |
33 | |
34 | func ZeroValue(f *ast.File, pkg *types.Package, typ types.Type) ast.Expr { |
35 | under := typ |
36 | if n, ok := typ.(*types.Named); ok { |
37 | under = n.Underlying() |
38 | } |
39 | switch u := under.(type) { |
40 | case *types.Basic: |
41 | switch { |
42 | case u.Info()&types.IsNumeric != 0: |
43 | return &ast.BasicLit{Kind: token.INT, Value: "0"} |
44 | case u.Info()&types.IsBoolean != 0: |
45 | return &ast.Ident{Name: "false"} |
46 | case u.Info()&types.IsString != 0: |
47 | return &ast.BasicLit{Kind: token.STRING, Value: `""`} |
48 | default: |
49 | panic("unknown basic type") |
50 | } |
51 | case *types.Chan, *types.Interface, *types.Map, *types.Pointer, *types.Signature, *types.Slice, *types.Array: |
52 | return ast.NewIdent("nil") |
53 | case *types.Struct: |
54 | texpr := TypeExpr(f, pkg, typ) // typ because we want the name here. |
55 | if texpr == nil { |
56 | return nil |
57 | } |
58 | return &ast.CompositeLit{ |
59 | Type: texpr, |
60 | } |
61 | } |
62 | return nil |
63 | } |
64 | |
65 | // IsZeroValue checks whether the given expression is a 'zero value' (as determined by output of |
66 | // analysisinternal.ZeroValue) |
67 | func IsZeroValue(expr ast.Expr) bool { |
68 | switch e := expr.(type) { |
69 | case *ast.BasicLit: |
70 | return e.Value == "0" || e.Value == `""` |
71 | case *ast.Ident: |
72 | return e.Name == "nil" || e.Name == "false" |
73 | default: |
74 | return false |
75 | } |
76 | } |
77 | |
78 | // TypeExpr returns syntax for the specified type. References to |
79 | // named types from packages other than pkg are qualified by an appropriate |
80 | // package name, as defined by the import environment of file. |
81 | func TypeExpr(f *ast.File, pkg *types.Package, typ types.Type) ast.Expr { |
82 | switch t := typ.(type) { |
83 | case *types.Basic: |
84 | switch t.Kind() { |
85 | case types.UnsafePointer: |
86 | return &ast.SelectorExpr{X: ast.NewIdent("unsafe"), Sel: ast.NewIdent("Pointer")} |
87 | default: |
88 | return ast.NewIdent(t.Name()) |
89 | } |
90 | case *types.Pointer: |
91 | x := TypeExpr(f, pkg, t.Elem()) |
92 | if x == nil { |
93 | return nil |
94 | } |
95 | return &ast.UnaryExpr{ |
96 | Op: token.MUL, |
97 | X: x, |
98 | } |
99 | case *types.Array: |
100 | elt := TypeExpr(f, pkg, t.Elem()) |
101 | if elt == nil { |
102 | return nil |
103 | } |
104 | return &ast.ArrayType{ |
105 | Len: &ast.BasicLit{ |
106 | Kind: token.INT, |
107 | Value: fmt.Sprintf("%d", t.Len()), |
108 | }, |
109 | Elt: elt, |
110 | } |
111 | case *types.Slice: |
112 | elt := TypeExpr(f, pkg, t.Elem()) |
113 | if elt == nil { |
114 | return nil |
115 | } |
116 | return &ast.ArrayType{ |
117 | Elt: elt, |
118 | } |
119 | case *types.Map: |
120 | key := TypeExpr(f, pkg, t.Key()) |
121 | value := TypeExpr(f, pkg, t.Elem()) |
122 | if key == nil || value == nil { |
123 | return nil |
124 | } |
125 | return &ast.MapType{ |
126 | Key: key, |
127 | Value: value, |
128 | } |
129 | case *types.Chan: |
130 | dir := ast.ChanDir(t.Dir()) |
131 | if t.Dir() == types.SendRecv { |
132 | dir = ast.SEND | ast.RECV |
133 | } |
134 | value := TypeExpr(f, pkg, t.Elem()) |
135 | if value == nil { |
136 | return nil |
137 | } |
138 | return &ast.ChanType{ |
139 | Dir: dir, |
140 | Value: value, |
141 | } |
142 | case *types.Signature: |
143 | var params []*ast.Field |
144 | for i := 0; i < t.Params().Len(); i++ { |
145 | p := TypeExpr(f, pkg, t.Params().At(i).Type()) |
146 | if p == nil { |
147 | return nil |
148 | } |
149 | params = append(params, &ast.Field{ |
150 | Type: p, |
151 | Names: []*ast.Ident{ |
152 | { |
153 | Name: t.Params().At(i).Name(), |
154 | }, |
155 | }, |
156 | }) |
157 | } |
158 | var returns []*ast.Field |
159 | for i := 0; i < t.Results().Len(); i++ { |
160 | r := TypeExpr(f, pkg, t.Results().At(i).Type()) |
161 | if r == nil { |
162 | return nil |
163 | } |
164 | returns = append(returns, &ast.Field{ |
165 | Type: r, |
166 | }) |
167 | } |
168 | return &ast.FuncType{ |
169 | Params: &ast.FieldList{ |
170 | List: params, |
171 | }, |
172 | Results: &ast.FieldList{ |
173 | List: returns, |
174 | }, |
175 | } |
176 | case *types.Named: |
177 | if t.Obj().Pkg() == nil { |
178 | return ast.NewIdent(t.Obj().Name()) |
179 | } |
180 | if t.Obj().Pkg() == pkg { |
181 | return ast.NewIdent(t.Obj().Name()) |
182 | } |
183 | pkgName := t.Obj().Pkg().Name() |
184 | |
185 | // If the file already imports the package under another name, use that. |
186 | for _, cand := range f.Imports { |
187 | if path, _ := strconv.Unquote(cand.Path.Value); path == t.Obj().Pkg().Path() { |
188 | if cand.Name != nil && cand.Name.Name != "" { |
189 | pkgName = cand.Name.Name |
190 | } |
191 | } |
192 | } |
193 | if pkgName == "." { |
194 | return ast.NewIdent(t.Obj().Name()) |
195 | } |
196 | return &ast.SelectorExpr{ |
197 | X: ast.NewIdent(pkgName), |
198 | Sel: ast.NewIdent(t.Obj().Name()), |
199 | } |
200 | case *types.Struct: |
201 | return ast.NewIdent(t.String()) |
202 | case *types.Interface: |
203 | return ast.NewIdent(t.String()) |
204 | default: |
205 | return nil |
206 | } |
207 | } |
208 | |
209 | // StmtToInsertVarBefore returns the ast.Stmt before which we can safely insert a new variable. |
210 | // Some examples: |
211 | // |
212 | // Basic Example: |
213 | // z := 1 |
214 | // y := z + x |
215 | // If x is undeclared, then this function would return `y := z + x`, so that we |
216 | // can insert `x := ` on the line before `y := z + x`. |
217 | // |
218 | // If stmt example: |
219 | // if z == 1 { |
220 | // } else if z == y {} |
221 | // If y is undeclared, then this function would return `if z == 1 {`, because we cannot |
222 | // insert a statement between an if and an else if statement. As a result, we need to find |
223 | // the top of the if chain to insert `y := ` before. |
224 | func StmtToInsertVarBefore(path []ast.Node) ast.Stmt { |
225 | enclosingIndex := -1 |
226 | for i, p := range path { |
227 | if _, ok := p.(ast.Stmt); ok { |
228 | enclosingIndex = i |
229 | break |
230 | } |
231 | } |
232 | if enclosingIndex == -1 { |
233 | return nil |
234 | } |
235 | enclosingStmt := path[enclosingIndex] |
236 | switch enclosingStmt.(type) { |
237 | case *ast.IfStmt: |
238 | // The enclosingStmt is inside of the if declaration, |
239 | // We need to check if we are in an else-if stmt and |
240 | // get the base if statement. |
241 | return baseIfStmt(path, enclosingIndex) |
242 | case *ast.CaseClause: |
243 | // Get the enclosing switch stmt if the enclosingStmt is |
244 | // inside of the case statement. |
245 | for i := enclosingIndex + 1; i < len(path); i++ { |
246 | if node, ok := path[i].(*ast.SwitchStmt); ok { |
247 | return node |
248 | } else if node, ok := path[i].(*ast.TypeSwitchStmt); ok { |
249 | return node |
250 | } |
251 | } |
252 | } |
253 | if len(path) <= enclosingIndex+1 { |
254 | return enclosingStmt.(ast.Stmt) |
255 | } |
256 | // Check if the enclosing statement is inside another node. |
257 | switch expr := path[enclosingIndex+1].(type) { |
258 | case *ast.IfStmt: |
259 | // Get the base if statement. |
260 | return baseIfStmt(path, enclosingIndex+1) |
261 | case *ast.ForStmt: |
262 | if expr.Init == enclosingStmt || expr.Post == enclosingStmt { |
263 | return expr |
264 | } |
265 | } |
266 | return enclosingStmt.(ast.Stmt) |
267 | } |
268 | |
269 | // baseIfStmt walks up the if/else-if chain until we get to |
270 | // the top of the current if chain. |
271 | func baseIfStmt(path []ast.Node, index int) ast.Stmt { |
272 | stmt := path[index] |
273 | for i := index + 1; i < len(path); i++ { |
274 | if node, ok := path[i].(*ast.IfStmt); ok && node.Else == stmt { |
275 | stmt = node |
276 | continue |
277 | } |
278 | break |
279 | } |
280 | return stmt.(ast.Stmt) |
281 | } |
282 | |
283 | // WalkASTWithParent walks the AST rooted at n. The semantics are |
284 | // similar to ast.Inspect except it does not call f(nil). |
285 | func WalkASTWithParent(n ast.Node, f func(n ast.Node, parent ast.Node) bool) { |
286 | var ancestors []ast.Node |
287 | ast.Inspect(n, func(n ast.Node) (recurse bool) { |
288 | if n == nil { |
289 | ancestors = ancestors[:len(ancestors)-1] |
290 | return false |
291 | } |
292 | |
293 | var parent ast.Node |
294 | if len(ancestors) > 0 { |
295 | parent = ancestors[len(ancestors)-1] |
296 | } |
297 | ancestors = append(ancestors, n) |
298 | return f(n, parent) |
299 | }) |
300 | } |
301 | |
302 | // MatchingIdents finds the names of all identifiers in 'node' that match any of the given types. |
303 | // 'pos' represents the position at which the identifiers may be inserted. 'pos' must be within |
304 | // the scope of each of identifier we select. Otherwise, we will insert a variable at 'pos' that |
305 | // is unrecognized. |
306 | func MatchingIdents(typs []types.Type, node ast.Node, pos token.Pos, info *types.Info, pkg *types.Package) map[types.Type][]string { |
307 | |
308 | // Initialize matches to contain the variable types we are searching for. |
309 | matches := make(map[types.Type][]string) |
310 | for _, typ := range typs { |
311 | if typ == nil { |
312 | continue // TODO(adonovan): is this reachable? |
313 | } |
314 | matches[typ] = nil // create entry |
315 | } |
316 | |
317 | seen := map[types.Object]struct{}{} |
318 | ast.Inspect(node, func(n ast.Node) bool { |
319 | if n == nil { |
320 | return false |
321 | } |
322 | // Prevent circular definitions. If 'pos' is within an assignment statement, do not |
323 | // allow any identifiers in that assignment statement to be selected. Otherwise, |
324 | // we could do the following, where 'x' satisfies the type of 'f0': |
325 | // |
326 | // x := fakeStruct{f0: x} |
327 | // |
328 | if assign, ok := n.(*ast.AssignStmt); ok && pos > assign.Pos() && pos <= assign.End() { |
329 | return false |
330 | } |
331 | if n.End() > pos { |
332 | return n.Pos() <= pos |
333 | } |
334 | ident, ok := n.(*ast.Ident) |
335 | if !ok || ident.Name == "_" { |
336 | return true |
337 | } |
338 | obj := info.Defs[ident] |
339 | if obj == nil || obj.Type() == nil { |
340 | return true |
341 | } |
342 | if _, ok := obj.(*types.TypeName); ok { |
343 | return true |
344 | } |
345 | // Prevent duplicates in matches' values. |
346 | if _, ok = seen[obj]; ok { |
347 | return true |
348 | } |
349 | seen[obj] = struct{}{} |
350 | // Find the scope for the given position. Then, check whether the object |
351 | // exists within the scope. |
352 | innerScope := pkg.Scope().Innermost(pos) |
353 | if innerScope == nil { |
354 | return true |
355 | } |
356 | _, foundObj := innerScope.LookupParent(ident.Name, pos) |
357 | if foundObj != obj { |
358 | return true |
359 | } |
360 | // The object must match one of the types that we are searching for. |
361 | // TODO(adonovan): opt: use typeutil.Map? |
362 | if names, ok := matches[obj.Type()]; ok { |
363 | matches[obj.Type()] = append(names, ident.Name) |
364 | } else { |
365 | // If the object type does not exactly match |
366 | // any of the target types, greedily find the first |
367 | // target type that the object type can satisfy. |
368 | for typ := range matches { |
369 | if equivalentTypes(obj.Type(), typ) { |
370 | matches[typ] = append(matches[typ], ident.Name) |
371 | } |
372 | } |
373 | } |
374 | return true |
375 | }) |
376 | return matches |
377 | } |
378 | |
379 | func equivalentTypes(want, got types.Type) bool { |
380 | if types.Identical(want, got) { |
381 | return true |
382 | } |
383 | // Code segment to help check for untyped equality from (golang/go#32146). |
384 | if rhs, ok := want.(*types.Basic); ok && rhs.Info()&types.IsUntyped > 0 { |
385 | if lhs, ok := got.Underlying().(*types.Basic); ok { |
386 | return rhs.Info()&types.IsConstType == lhs.Info()&types.IsConstType |
387 | } |
388 | } |
389 | return types.AssignableTo(want, got) |
390 | } |
391 |
Members