GoPLS Viewer

Home|gopls/internal/analysisinternal/analysis.go
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.
7package analysisinternal
8
9import (
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+.
20var DiagnoseFuzzTests bool = false
21
22func TypeErrorEndPos(fset *token.FileSetsrc []bytestart token.Postoken.Pos {
23    // Get the end position for the type error.
24    offsetend := fset.PositionFor(startfalse).Offsetstart
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
34func ZeroValue(f *ast.Filepkg *types.Packagetyp types.Typeast.Expr {
35    under := typ
36    if nok := 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{Kindtoken.INTValue"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{Kindtoken.STRINGValue`""`}
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(fpkgtyp// typ because we want the name here.
55        if texpr == nil {
56            return nil
57        }
58        return &ast.CompositeLit{
59            Typetexpr,
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)
67func IsZeroValue(expr ast.Exprbool {
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.
81func TypeExpr(f *ast.Filepkg *types.Packagetyp types.Typeast.Expr {
82    switch t := typ.(type) {
83    case *types.Basic:
84        switch t.Kind() {
85        case types.UnsafePointer:
86            return &ast.SelectorExpr{Xast.NewIdent("unsafe"), Selast.NewIdent("Pointer")}
87        default:
88            return ast.NewIdent(t.Name())
89        }
90    case *types.Pointer:
91        x := TypeExpr(fpkgt.Elem())
92        if x == nil {
93            return nil
94        }
95        return &ast.UnaryExpr{
96            Optoken.MUL,
97            X:  x,
98        }
99    case *types.Array:
100        elt := TypeExpr(fpkgt.Elem())
101        if elt == nil {
102            return nil
103        }
104        return &ast.ArrayType{
105            Len: &ast.BasicLit{
106                Kind:  token.INT,
107                Valuefmt.Sprintf("%d"t.Len()),
108            },
109            Eltelt,
110        }
111    case *types.Slice:
112        elt := TypeExpr(fpkgt.Elem())
113        if elt == nil {
114            return nil
115        }
116        return &ast.ArrayType{
117            Eltelt,
118        }
119    case *types.Map:
120        key := TypeExpr(fpkgt.Key())
121        value := TypeExpr(fpkgt.Elem())
122        if key == nil || value == nil {
123            return nil
124        }
125        return &ast.MapType{
126            Key:   key,
127            Valuevalue,
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(fpkgt.Elem())
135        if value == nil {
136            return nil
137        }
138        return &ast.ChanType{
139            Dir:   dir,
140            Valuevalue,
141        }
142    case *types.Signature:
143        var params []*ast.Field
144        for i := 0i < t.Params().Len(); i++ {
145            p := TypeExpr(fpkgt.Params().At(i).Type())
146            if p == nil {
147                return nil
148            }
149            params = append(params, &ast.Field{
150                Typep,
151                Names: []*ast.Ident{
152                    {
153                        Namet.Params().At(i).Name(),
154                    },
155                },
156            })
157        }
158        var returns []*ast.Field
159        for i := 0i < t.Results().Len(); i++ {
160            r := TypeExpr(fpkgt.Results().At(i).Type())
161            if r == nil {
162                return nil
163            }
164            returns = append(returns, &ast.Field{
165                Typer,
166            })
167        }
168        return &ast.FuncType{
169            Params: &ast.FieldList{
170                Listparams,
171            },
172            Results: &ast.FieldList{
173                Listreturns,
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            Selast.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.
224func StmtToInsertVarBefore(path []ast.Nodeast.Stmt {
225    enclosingIndex := -1
226    for ip := 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(pathenclosingIndex)
242    case *ast.CaseClause:
243        // Get the enclosing switch stmt if the enclosingStmt is
244        // inside of the case statement.
245        for i := enclosingIndex + 1i < len(path); i++ {
246            if nodeok := path[i].(*ast.SwitchStmt); ok {
247                return node
248            } else if nodeok := 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(pathenclosingIndex+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.
271func baseIfStmt(path []ast.Nodeindex intast.Stmt {
272    stmt := path[index]
273    for i := index + 1i < len(path); i++ {
274        if nodeok := 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).
285func WalkASTWithParent(n ast.Nodef func(n ast.Nodeparent ast.Nodebool) {
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(ancestorsn)
298        return f(nparent)
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.
306func MatchingIdents(typs []types.Typenode ast.Nodepos token.Posinfo *types.Infopkg *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.Nodebool {
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 assignok := 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        identok := 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.Namepos)
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 namesok := matches[obj.Type()]; ok {
363            matches[obj.Type()] = append(namesident.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
379func equivalentTypes(wantgot types.Typebool {
380    if types.Identical(wantgot) {
381        return true
382    }
383    // Code segment to help check for untyped equality from (golang/go#32146).
384    if rhsok := want.(*types.Basic); ok && rhs.Info()&types.IsUntyped > 0 {
385        if lhsok := got.Underlying().(*types.Basic); ok {
386            return rhs.Info()&types.IsConstType == lhs.Info()&types.IsConstType
387        }
388    }
389    return types.AssignableTo(wantgot)
390}
391
MembersX
TypeErrorEndPos.src
TypeExpr.BlockStmt.RangeStmt_4504.cand
MatchingIdents.pos
MatchingIdents.BlockStmt.foundObj
equivalentTypes.want
ast
TypeExpr.BlockStmt.elt
MatchingIdents.typs
ZeroValue.under
equivalentTypes
TypeExpr.BlockStmt.params
ZeroValue.typ
TypeExpr.BlockStmt.RangeStmt_4504.BlockStmt._
MatchingIdents
strconv
TypeErrorEndPos.start
TypeExpr.BlockStmt.x
TypeExpr.BlockStmt.key
MatchingIdents.BlockStmt._
fmt
TypeErrorEndPos
types
TypeExpr.BlockStmt.BlockStmt.p
baseIfStmt.index
TypeExpr.pkg
TypeErrorEndPos.end
TypeErrorEndPos.width
TypeExpr.BlockStmt.returns
MatchingIdents.pkg
MatchingIdents.BlockStmt.BlockStmt.RangeStmt_9978.typ
bytes
ZeroValue.BlockStmt.texpr
IsZeroValue.expr
WalkASTWithParent.BlockStmt.parent
MatchingIdents.node
MatchingIdents.seen
DiagnoseFuzzTests
IsZeroValue
TypeExpr.BlockStmt.pkgName
StmtToInsertVarBefore.RangeStmt_5687.i
WalkASTWithParent.f
TypeErrorEndPos.offset
TypeExpr.BlockStmt.BlockStmt.r
TypeExpr.BlockStmt.RangeStmt_4504.BlockStmt.path
baseIfStmt
ZeroValue
baseIfStmt.path
TypeErrorEndPos.fset
TypeExpr.BlockStmt.value
StmtToInsertVarBefore
WalkASTWithParent
MatchingIdents.matches
equivalentTypes.got
TypeExpr.typ
ZeroValue.f
TypeExpr
TypeExpr.BlockStmt.dir
WalkASTWithParent.n
token
StmtToInsertVarBefore.path
WalkASTWithParent.ancestors
TypeExpr.BlockStmt.i
TypeExpr.f
StmtToInsertVarBefore.RangeStmt_5687.p
MatchingIdents.info
MatchingIdents.RangeStmt_8271.typ
MatchingIdents.BlockStmt.innerScope
ZeroValue.pkg
Members
X