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 }