| 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