+++ /dev/null
-// Copyright 2020 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package analysisinternal exposes internal-only fields from go/analysis.
-package analysisinternal
-
-import (
- "bytes"
- "fmt"
- "go/ast"
- "go/token"
- "go/types"
- "strings"
-
- "golang.org/x/tools/go/ast/astutil"
- "golang.org/x/tools/internal/lsp/fuzzy"
-)
-
-var (
- GetTypeErrors func(p interface{}) []types.Error
- SetTypeErrors func(p interface{}, errors []types.Error)
-)
-
-func TypeErrorEndPos(fset *token.FileSet, src []byte, start token.Pos) token.Pos {
- // Get the end position for the type error.
- offset, end := fset.PositionFor(start, false).Offset, start
- if offset >= len(src) {
- return end
- }
- if width := bytes.IndexAny(src[offset:], " \n,():;[]+-*"); width > 0 {
- end = start + token.Pos(width)
- }
- return end
-}
-
-func ZeroValue(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Type) ast.Expr {
- under := typ
- if n, ok := typ.(*types.Named); ok {
- under = n.Underlying()
- }
- switch u := under.(type) {
- case *types.Basic:
- switch {
- case u.Info()&types.IsNumeric != 0:
- return &ast.BasicLit{Kind: token.INT, Value: "0"}
- case u.Info()&types.IsBoolean != 0:
- return &ast.Ident{Name: "false"}
- case u.Info()&types.IsString != 0:
- return &ast.BasicLit{Kind: token.STRING, Value: `""`}
- default:
- panic("unknown basic type")
- }
- case *types.Chan, *types.Interface, *types.Map, *types.Pointer, *types.Signature, *types.Slice, *types.Array:
- return ast.NewIdent("nil")
- case *types.Struct:
- texpr := TypeExpr(fset, f, pkg, typ) // typ because we want the name here.
- if texpr == nil {
- return nil
- }
- return &ast.CompositeLit{
- Type: texpr,
- }
- }
- return nil
-}
-
-// IsZeroValue checks whether the given expression is a 'zero value' (as determined by output of
-// analysisinternal.ZeroValue)
-func IsZeroValue(expr ast.Expr) bool {
- switch e := expr.(type) {
- case *ast.BasicLit:
- return e.Value == "0" || e.Value == `""`
- case *ast.Ident:
- return e.Name == "nil" || e.Name == "false"
- default:
- return false
- }
-}
-
-func TypeExpr(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Type) ast.Expr {
- switch t := typ.(type) {
- case *types.Basic:
- switch t.Kind() {
- case types.UnsafePointer:
- return &ast.SelectorExpr{X: ast.NewIdent("unsafe"), Sel: ast.NewIdent("Pointer")}
- default:
- return ast.NewIdent(t.Name())
- }
- case *types.Pointer:
- x := TypeExpr(fset, f, pkg, t.Elem())
- if x == nil {
- return nil
- }
- return &ast.UnaryExpr{
- Op: token.MUL,
- X: x,
- }
- case *types.Array:
- elt := TypeExpr(fset, f, pkg, t.Elem())
- if elt == nil {
- return nil
- }
- return &ast.ArrayType{
- Len: &ast.BasicLit{
- Kind: token.INT,
- Value: fmt.Sprintf("%d", t.Len()),
- },
- Elt: elt,
- }
- case *types.Slice:
- elt := TypeExpr(fset, f, pkg, t.Elem())
- if elt == nil {
- return nil
- }
- return &ast.ArrayType{
- Elt: elt,
- }
- case *types.Map:
- key := TypeExpr(fset, f, pkg, t.Key())
- value := TypeExpr(fset, f, pkg, t.Elem())
- if key == nil || value == nil {
- return nil
- }
- return &ast.MapType{
- Key: key,
- Value: value,
- }
- case *types.Chan:
- dir := ast.ChanDir(t.Dir())
- if t.Dir() == types.SendRecv {
- dir = ast.SEND | ast.RECV
- }
- value := TypeExpr(fset, f, pkg, t.Elem())
- if value == nil {
- return nil
- }
- return &ast.ChanType{
- Dir: dir,
- Value: value,
- }
- case *types.Signature:
- var params []*ast.Field
- for i := 0; i < t.Params().Len(); i++ {
- p := TypeExpr(fset, f, pkg, t.Params().At(i).Type())
- if p == nil {
- return nil
- }
- params = append(params, &ast.Field{
- Type: p,
- Names: []*ast.Ident{
- {
- Name: t.Params().At(i).Name(),
- },
- },
- })
- }
- var returns []*ast.Field
- for i := 0; i < t.Results().Len(); i++ {
- r := TypeExpr(fset, f, pkg, t.Results().At(i).Type())
- if r == nil {
- return nil
- }
- returns = append(returns, &ast.Field{
- Type: r,
- })
- }
- return &ast.FuncType{
- Params: &ast.FieldList{
- List: params,
- },
- Results: &ast.FieldList{
- List: returns,
- },
- }
- case *types.Named:
- if t.Obj().Pkg() == nil {
- return ast.NewIdent(t.Obj().Name())
- }
- if t.Obj().Pkg() == pkg {
- return ast.NewIdent(t.Obj().Name())
- }
- pkgName := t.Obj().Pkg().Name()
- // If the file already imports the package under another name, use that.
- for _, group := range astutil.Imports(fset, f) {
- for _, cand := range group {
- if strings.Trim(cand.Path.Value, `"`) == t.Obj().Pkg().Path() {
- if cand.Name != nil && cand.Name.Name != "" {
- pkgName = cand.Name.Name
- }
- }
- }
- }
- if pkgName == "." {
- return ast.NewIdent(t.Obj().Name())
- }
- return &ast.SelectorExpr{
- X: ast.NewIdent(pkgName),
- Sel: ast.NewIdent(t.Obj().Name()),
- }
- case *types.Struct:
- return ast.NewIdent(t.String())
- case *types.Interface:
- return ast.NewIdent(t.String())
- default:
- return nil
- }
-}
-
-type TypeErrorPass string
-
-const (
- NoNewVars TypeErrorPass = "nonewvars"
- NoResultValues TypeErrorPass = "noresultvalues"
- UndeclaredName TypeErrorPass = "undeclaredname"
-)
-
-// StmtToInsertVarBefore returns the ast.Stmt before which we can safely insert a new variable.
-// Some examples:
-//
-// Basic Example:
-// z := 1
-// y := z + x
-// If x is undeclared, then this function would return `y := z + x`, so that we
-// can insert `x := ` on the line before `y := z + x`.
-//
-// If stmt example:
-// if z == 1 {
-// } else if z == y {}
-// If y is undeclared, then this function would return `if z == 1 {`, because we cannot
-// insert a statement between an if and an else if statement. As a result, we need to find
-// the top of the if chain to insert `y := ` before.
-func StmtToInsertVarBefore(path []ast.Node) ast.Stmt {
- enclosingIndex := -1
- for i, p := range path {
- if _, ok := p.(ast.Stmt); ok {
- enclosingIndex = i
- break
- }
- }
- if enclosingIndex == -1 {
- return nil
- }
- enclosingStmt := path[enclosingIndex]
- switch enclosingStmt.(type) {
- case *ast.IfStmt:
- // The enclosingStmt is inside of the if declaration,
- // We need to check if we are in an else-if stmt and
- // get the base if statement.
- return baseIfStmt(path, enclosingIndex)
- case *ast.CaseClause:
- // Get the enclosing switch stmt if the enclosingStmt is
- // inside of the case statement.
- for i := enclosingIndex + 1; i < len(path); i++ {
- if node, ok := path[i].(*ast.SwitchStmt); ok {
- return node
- } else if node, ok := path[i].(*ast.TypeSwitchStmt); ok {
- return node
- }
- }
- }
- if len(path) <= enclosingIndex+1 {
- return enclosingStmt.(ast.Stmt)
- }
- // Check if the enclosing statement is inside another node.
- switch expr := path[enclosingIndex+1].(type) {
- case *ast.IfStmt:
- // Get the base if statement.
- return baseIfStmt(path, enclosingIndex+1)
- case *ast.ForStmt:
- if expr.Init == enclosingStmt || expr.Post == enclosingStmt {
- return expr
- }
- }
- return enclosingStmt.(ast.Stmt)
-}
-
-// baseIfStmt walks up the if/else-if chain until we get to
-// the top of the current if chain.
-func baseIfStmt(path []ast.Node, index int) ast.Stmt {
- stmt := path[index]
- for i := index + 1; i < len(path); i++ {
- if node, ok := path[i].(*ast.IfStmt); ok && node.Else == stmt {
- stmt = node
- continue
- }
- break
- }
- return stmt.(ast.Stmt)
-}
-
-// WalkASTWithParent walks the AST rooted at n. The semantics are
-// similar to ast.Inspect except it does not call f(nil).
-func WalkASTWithParent(n ast.Node, f func(n ast.Node, parent ast.Node) bool) {
- var ancestors []ast.Node
- ast.Inspect(n, func(n ast.Node) (recurse bool) {
- if n == nil {
- ancestors = ancestors[:len(ancestors)-1]
- return false
- }
-
- var parent ast.Node
- if len(ancestors) > 0 {
- parent = ancestors[len(ancestors)-1]
- }
- ancestors = append(ancestors, n)
- return f(n, parent)
- })
-}
-
-// FindMatchingIdents finds all identifiers in 'node' that match any of the given types.
-// 'pos' represents the position at which the identifiers may be inserted. 'pos' must be within
-// the scope of each of identifier we select. Otherwise, we will insert a variable at 'pos' that
-// is unrecognized.
-func FindMatchingIdents(typs []types.Type, node ast.Node, pos token.Pos, info *types.Info, pkg *types.Package) map[types.Type][]*ast.Ident {
- matches := map[types.Type][]*ast.Ident{}
- // Initialize matches to contain the variable types we are searching for.
- for _, typ := range typs {
- if typ == nil {
- continue
- }
- matches[typ] = []*ast.Ident{}
- }
- seen := map[types.Object]struct{}{}
- ast.Inspect(node, func(n ast.Node) bool {
- if n == nil {
- return false
- }
- // Prevent circular definitions. If 'pos' is within an assignment statement, do not
- // allow any identifiers in that assignment statement to be selected. Otherwise,
- // we could do the following, where 'x' satisfies the type of 'f0':
- //
- // x := fakeStruct{f0: x}
- //
- assignment, ok := n.(*ast.AssignStmt)
- if ok && pos > assignment.Pos() && pos <= assignment.End() {
- return false
- }
- if n.End() > pos {
- return n.Pos() <= pos
- }
- ident, ok := n.(*ast.Ident)
- if !ok || ident.Name == "_" {
- return true
- }
- obj := info.Defs[ident]
- if obj == nil || obj.Type() == nil {
- return true
- }
- if _, ok := obj.(*types.TypeName); ok {
- return true
- }
- // Prevent duplicates in matches' values.
- if _, ok = seen[obj]; ok {
- return true
- }
- seen[obj] = struct{}{}
- // Find the scope for the given position. Then, check whether the object
- // exists within the scope.
- innerScope := pkg.Scope().Innermost(pos)
- if innerScope == nil {
- return true
- }
- _, foundObj := innerScope.LookupParent(ident.Name, pos)
- if foundObj != obj {
- return true
- }
- // The object must match one of the types that we are searching for.
- if idents, ok := matches[obj.Type()]; ok {
- matches[obj.Type()] = append(idents, ast.NewIdent(ident.Name))
- }
- // If the object type does not exactly match any of the target types, greedily
- // find the first target type that the object type can satisfy.
- for typ := range matches {
- if obj.Type() == typ {
- continue
- }
- if equivalentTypes(obj.Type(), typ) {
- matches[typ] = append(matches[typ], ast.NewIdent(ident.Name))
- }
- }
- return true
- })
- return matches
-}
-
-func equivalentTypes(want, got types.Type) bool {
- if want == got || types.Identical(want, got) {
- return true
- }
- // Code segment to help check for untyped equality from (golang/go#32146).
- if rhs, ok := want.(*types.Basic); ok && rhs.Info()&types.IsUntyped > 0 {
- if lhs, ok := got.Underlying().(*types.Basic); ok {
- return rhs.Info()&types.IsConstType == lhs.Info()&types.IsConstType
- }
- }
- return types.AssignableTo(want, got)
-}
-
-// FindBestMatch employs fuzzy matching to evaluate the similarity of each given identifier to the
-// given pattern. We return the identifier whose name is most similar to the pattern.
-func FindBestMatch(pattern string, idents []*ast.Ident) ast.Expr {
- fuzz := fuzzy.NewMatcher(pattern)
- var bestFuzz ast.Expr
- highScore := float32(0) // minimum score is 0 (no match)
- for _, ident := range idents {
- // TODO: Improve scoring algorithm.
- score := fuzz.Score(ident.Name)
- if score > highScore {
- highScore = score
- bestFuzz = ident
- } else if score == 0 {
- // Order matters in the fuzzy matching algorithm. If we find no match
- // when matching the target to the identifier, try matching the identifier
- // to the target.
- revFuzz := fuzzy.NewMatcher(ident.Name)
- revScore := revFuzz.Score(pattern)
- if revScore > highScore {
- highScore = revScore
- bestFuzz = ident
- }
- }
- }
- return bestFuzz
-}