github.com/gopherjs/gopherjs@v1.19.0-beta1.0.20240506212314-27071a8796e4/compiler/astutil/astutil.go (about)

     1  package astutil
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/token"
     7  	"go/types"
     8  	"path"
     9  	"reflect"
    10  	"regexp"
    11  	"strconv"
    12  	"strings"
    13  )
    14  
    15  func RemoveParens(e ast.Expr) ast.Expr {
    16  	for {
    17  		p, isParen := e.(*ast.ParenExpr)
    18  		if !isParen {
    19  			return e
    20  		}
    21  		e = p.X
    22  	}
    23  }
    24  
    25  func SetType(info *types.Info, t types.Type, e ast.Expr) ast.Expr {
    26  	info.Types[e] = types.TypeAndValue{Type: t}
    27  	return e
    28  }
    29  
    30  func NewIdent(name string, t types.Type, info *types.Info, pkg *types.Package) *ast.Ident {
    31  	ident := ast.NewIdent(name)
    32  	info.Types[ident] = types.TypeAndValue{Type: t}
    33  	obj := types.NewVar(0, pkg, name, t)
    34  	info.Uses[ident] = obj
    35  	return ident
    36  }
    37  
    38  // IsTypeExpr returns true if expr denotes a type. This can be used to
    39  // distinguish between calls and type conversions.
    40  func IsTypeExpr(expr ast.Expr, info *types.Info) bool {
    41  	// Note that we could've used info.Types[expr].IsType() instead of doing our
    42  	// own analysis. However, that creates a problem because we synthesize some
    43  	// *ast.CallExpr nodes and, more importantly, *ast.Ident nodes that denote a
    44  	// type. Unfortunately, because the flag that controls
    45  	// types.TypeAndValue.IsType() return value is unexported we wouldn't be able
    46  	// to set it correctly. Thus, we can't rely on IsType().
    47  	switch e := expr.(type) {
    48  	case *ast.ArrayType, *ast.ChanType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.StructType:
    49  		return true
    50  	case *ast.StarExpr:
    51  		return IsTypeExpr(e.X, info)
    52  	case *ast.Ident:
    53  		_, ok := info.Uses[e].(*types.TypeName)
    54  		return ok
    55  	case *ast.SelectorExpr:
    56  		_, ok := info.Uses[e.Sel].(*types.TypeName)
    57  		return ok
    58  	case *ast.IndexExpr:
    59  		ident, ok := e.X.(*ast.Ident)
    60  		if !ok {
    61  			return false
    62  		}
    63  		_, ok = info.Uses[ident].(*types.TypeName)
    64  		return ok
    65  	case *ast.IndexListExpr:
    66  		ident, ok := e.X.(*ast.Ident)
    67  		if !ok {
    68  			return false
    69  		}
    70  		_, ok = info.Uses[ident].(*types.TypeName)
    71  		return ok
    72  	case *ast.ParenExpr:
    73  		return IsTypeExpr(e.X, info)
    74  	default:
    75  		return false
    76  	}
    77  }
    78  
    79  func ImportsUnsafe(file *ast.File) bool {
    80  	for _, imp := range file.Imports {
    81  		if imp.Path.Value == `"unsafe"` {
    82  			return true
    83  		}
    84  	}
    85  	return false
    86  }
    87  
    88  // ImportName tries to determine the package name for an import.
    89  //
    90  // If the package name isn't specified then this will make a best
    91  // make a best guess using the import path.
    92  // If the import name is dot (`.`), blank (`_`), or there
    93  // was an issue determining the package name then empty is returned.
    94  func ImportName(spec *ast.ImportSpec) string {
    95  	var name string
    96  	if spec.Name != nil {
    97  		name = spec.Name.Name
    98  	} else {
    99  		importPath, _ := strconv.Unquote(spec.Path.Value)
   100  		name = path.Base(importPath)
   101  	}
   102  
   103  	switch name {
   104  	case `_`, `.`, `/`:
   105  		return ``
   106  	default:
   107  		return name
   108  	}
   109  }
   110  
   111  // FuncKey returns a string, which uniquely identifies a top-level function or
   112  // method in a package.
   113  func FuncKey(d *ast.FuncDecl) string {
   114  	if recvKey := FuncReceiverKey(d); len(recvKey) > 0 {
   115  		return recvKey + "." + d.Name.Name
   116  	}
   117  	return d.Name.Name
   118  }
   119  
   120  // FuncReceiverKey returns a string that uniquely identifies the receiver
   121  // struct of the function or an empty string if there is no receiver.
   122  // This name will match the name of the struct in the struct's type spec.
   123  func FuncReceiverKey(d *ast.FuncDecl) string {
   124  	if d == nil || d.Recv == nil || len(d.Recv.List) == 0 {
   125  		return ``
   126  	}
   127  	recv := d.Recv.List[0].Type
   128  	for {
   129  		switch r := recv.(type) {
   130  		case *ast.IndexListExpr:
   131  			recv = r.X
   132  			continue
   133  		case *ast.IndexExpr:
   134  			recv = r.X
   135  			continue
   136  		case *ast.StarExpr:
   137  			recv = r.X
   138  			continue
   139  		case *ast.Ident:
   140  			return r.Name
   141  		default:
   142  			panic(fmt.Errorf(`unexpected type %T in receiver of function: %v`, recv, d))
   143  		}
   144  	}
   145  }
   146  
   147  // KeepOriginal returns true if gopherjs:keep-original directive is present
   148  // before a function decl.
   149  //
   150  // `//gopherjs:keep-original` is a GopherJS-specific directive, which can be
   151  // applied to functions in native overlays and will instruct the augmentation
   152  // logic to expose the original function such that it can be called. For a
   153  // function in the original called `foo`, it will be accessible by the name
   154  // `_gopherjs_original_foo`.
   155  func KeepOriginal(d *ast.FuncDecl) bool {
   156  	return hasDirective(d, `keep-original`)
   157  }
   158  
   159  // Purge returns true if gopherjs:purge directive is present
   160  // on a struct, interface, type, variable, constant, or function.
   161  //
   162  // `//gopherjs:purge` is a GopherJS-specific directive, which can be
   163  // applied in native overlays and will instruct the augmentation logic to
   164  // delete part of the standard library without a replacement. This directive
   165  // can be used to remove code that would be invalid in GopherJS, such as code
   166  // using unsupported features (e.g. generic interfaces before generics were
   167  // fully supported). It should be used with caution since it may remove needed
   168  // dependencies. If a type is purged, all methods using that type as
   169  // a receiver will also be purged.
   170  func Purge(d ast.Node) bool {
   171  	return hasDirective(d, `purge`)
   172  }
   173  
   174  // OverrideSignature returns true if gopherjs:override-signature directive is
   175  // present on a function.
   176  //
   177  // `//gopherjs:override-signature` is a GopherJS-specific directive, which can
   178  // be applied in native overlays and will instruct the augmentation logic to
   179  // replace the original function signature which has the same FuncKey with the
   180  // signature defined in the native overlays.
   181  // This directive can be used to remove generics from a function signature or
   182  // to replace a receiver of a function with another one. The given native
   183  // overlay function will be removed, so no method body is needed in the overlay.
   184  //
   185  // The new signature may not contain types which require a new import since
   186  // the imports will not be automatically added when needed, only removed.
   187  // Use a type alias in the overlay to deal manage imports.
   188  func OverrideSignature(d *ast.FuncDecl) bool {
   189  	return hasDirective(d, `override-signature`)
   190  }
   191  
   192  // directiveMatcher is a regex which matches a GopherJS directive
   193  // and finds the directive action.
   194  var directiveMatcher = regexp.MustCompile(`^\/(?:\/|\*)gopherjs:([\w-]+)`)
   195  
   196  // hasDirective returns true if the associated documentation
   197  // or line comments for the given node have the given directive action.
   198  //
   199  // All GopherJS-specific directives must start with `//gopherjs:` or
   200  // `/*gopherjs:` and followed by an action without any whitespace. The action
   201  // must be one or more letter, decimal, underscore, or hyphen.
   202  //
   203  // see https://pkg.go.dev/cmd/compile#hdr-Compiler_Directives
   204  func hasDirective(node ast.Node, directiveAction string) bool {
   205  	foundDirective := false
   206  	ast.Inspect(node, func(n ast.Node) bool {
   207  		switch a := n.(type) {
   208  		case *ast.Comment:
   209  			m := directiveMatcher.FindStringSubmatch(a.Text)
   210  			if len(m) == 2 && m[1] == directiveAction {
   211  				foundDirective = true
   212  			}
   213  			return false
   214  		case *ast.CommentGroup:
   215  			return !foundDirective
   216  		default:
   217  			return n == node
   218  		}
   219  	})
   220  	return foundDirective
   221  }
   222  
   223  // HasDirectivePrefix determines if any line in the given file
   224  // has the given directive prefix in it.
   225  func HasDirectivePrefix(file *ast.File, prefix string) bool {
   226  	for _, cg := range file.Comments {
   227  		for _, c := range cg.List {
   228  			if strings.HasPrefix(c.Text, prefix) {
   229  				return true
   230  			}
   231  		}
   232  	}
   233  	return false
   234  }
   235  
   236  // FindLoopStmt tries to find the loop statement among the AST nodes in the
   237  // |stack| that corresponds to the break/continue statement represented by
   238  // branch.
   239  //
   240  // This function is label-aware and assumes the code was successfully
   241  // type-checked.
   242  func FindLoopStmt(stack []ast.Node, branch *ast.BranchStmt, typeInfo *types.Info) ast.Stmt {
   243  	if branch.Tok != token.CONTINUE && branch.Tok != token.BREAK {
   244  		panic(fmt.Errorf("FindLoopStmt() must be used with a break or continue statement only, got: %v", branch))
   245  	}
   246  
   247  	for i := len(stack) - 1; i >= 0; i-- {
   248  		n := stack[i]
   249  
   250  		if branch.Label != nil {
   251  			// For a labelled continue the loop will always be in a labelled statement.
   252  			referencedLabel := typeInfo.Uses[branch.Label].(*types.Label)
   253  			labelStmt, ok := n.(*ast.LabeledStmt)
   254  			if !ok {
   255  				continue
   256  			}
   257  			if definedLabel := typeInfo.Defs[labelStmt.Label]; definedLabel != referencedLabel {
   258  				continue
   259  			}
   260  			n = labelStmt.Stmt
   261  		}
   262  
   263  		switch s := n.(type) {
   264  		case *ast.RangeStmt, *ast.ForStmt:
   265  			return s.(ast.Stmt)
   266  		}
   267  	}
   268  
   269  	// This should never happen in a source that passed type checking.
   270  	panic(fmt.Errorf("continue/break statement %v doesn't have a matching loop statement among ancestors", branch))
   271  }
   272  
   273  // EndsWithReturn returns true if the last effective statement is a "return".
   274  func EndsWithReturn(stmts []ast.Stmt) bool {
   275  	if len(stmts) == 0 {
   276  		return false
   277  	}
   278  	last := stmts[len(stmts)-1]
   279  	switch l := last.(type) {
   280  	case *ast.ReturnStmt:
   281  		return true
   282  	case *ast.LabeledStmt:
   283  		return EndsWithReturn([]ast.Stmt{l.Stmt})
   284  	case *ast.BlockStmt:
   285  		return EndsWithReturn(l.List)
   286  	default:
   287  		return false
   288  	}
   289  }
   290  
   291  // Squeeze removes all nil nodes from the slice.
   292  //
   293  // The given slice will be modified. This is designed for squeezing
   294  // declaration, specification, imports, and identifier lists.
   295  func Squeeze[E ast.Node, S ~[]E](s S) S {
   296  	var zero E
   297  	count, dest := len(s), 0
   298  	for src := 0; src < count; src++ {
   299  		if !reflect.DeepEqual(s[src], zero) {
   300  			// Swap the values, this will put the nil values to the end
   301  			// of the slice so that the tail isn't holding onto pointers.
   302  			s[dest], s[src] = s[src], s[dest]
   303  			dest++
   304  		}
   305  	}
   306  	return s[:dest]
   307  }