| // Copyright 2023 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 astutil |
| |
| import ( |
| "go/ast" |
| "go/token" |
| "reflect" |
| |
| "golang.org/x/tools/internal/typeparams" |
| ) |
| |
| // UnpackRecv unpacks a receiver type expression, reporting whether it is a |
| // pointer receiver, along with the type name identifier and any receiver type |
| // parameter identifiers. |
| // |
| // Copied (with modifications) from go/types. |
| func UnpackRecv(rtyp ast.Expr) (ptr bool, rname *ast.Ident, tparams []*ast.Ident) { |
| L: // unpack receiver type |
| // This accepts invalid receivers such as ***T and does not |
| // work for other invalid receivers, but we don't care. The |
| // validity of receiver expressions is checked elsewhere. |
| for { |
| switch t := rtyp.(type) { |
| case *ast.ParenExpr: |
| rtyp = t.X |
| case *ast.StarExpr: |
| ptr = true |
| rtyp = t.X |
| default: |
| break L |
| } |
| } |
| |
| // unpack type parameters, if any |
| switch rtyp.(type) { |
| case *ast.IndexExpr, *ast.IndexListExpr: |
| var indices []ast.Expr |
| rtyp, _, indices, _ = typeparams.UnpackIndexExpr(rtyp) |
| for _, arg := range indices { |
| var par *ast.Ident |
| switch arg := arg.(type) { |
| case *ast.Ident: |
| par = arg |
| default: |
| // ignore errors |
| } |
| if par == nil { |
| par = &ast.Ident{NamePos: arg.Pos(), Name: "_"} |
| } |
| tparams = append(tparams, par) |
| } |
| } |
| |
| // unpack receiver name |
| if name, _ := rtyp.(*ast.Ident); name != nil { |
| rname = name |
| } |
| |
| return |
| } |
| |
| // NodeContains reports whether the Pos/End range of node n encloses |
| // the given position pos. |
| // |
| // It is inclusive of both end points, to allow hovering (etc) when |
| // the cursor is immediately after a node. |
| // |
| // For unfortunate historical reasons, the Pos/End extent of an |
| // ast.File runs from the start of its package declaration---excluding |
| // copyright comments, build tags, and package documentation---to the |
| // end of its last declaration, excluding any trailing comments. So, |
| // as a special case, if n is an [ast.File], NodeContains uses |
| // n.FileStart <= pos && pos <= n.FileEnd to report whether the |
| // position lies anywhere within the file. |
| // |
| // Precondition: n must not be nil. |
| func NodeContains(n ast.Node, pos token.Pos) bool { |
| var start, end token.Pos |
| if file, ok := n.(*ast.File); ok { |
| start, end = file.FileStart, file.FileEnd // entire file |
| } else { |
| start, end = n.Pos(), n.End() |
| } |
| return start <= pos && pos <= end |
| } |
| |
| // Equal reports whether two nodes are structurally equal, |
| // ignoring fields of type [token.Pos], [ast.Object], |
| // and [ast.Scope], and comments. |
| // |
| // The operands x and y may be nil. |
| // A nil slice is not equal to an empty slice. |
| // |
| // The provided function determines whether two identifiers |
| // should be considered identical. |
| func Equal(x, y ast.Node, identical func(x, y *ast.Ident) bool) bool { |
| if x == nil || y == nil { |
| return x == y |
| } |
| return equal(reflect.ValueOf(x), reflect.ValueOf(y), identical) |
| } |
| |
| func equal(x, y reflect.Value, identical func(x, y *ast.Ident) bool) bool { |
| // Ensure types are the same |
| if x.Type() != y.Type() { |
| return false |
| } |
| switch x.Kind() { |
| case reflect.Pointer: |
| if x.IsNil() || y.IsNil() { |
| return x.IsNil() == y.IsNil() |
| } |
| switch t := x.Interface().(type) { |
| // Skip fields of types potentially involved in cycles. |
| case *ast.Object, *ast.Scope, *ast.CommentGroup: |
| return true |
| case *ast.Ident: |
| return identical(t, y.Interface().(*ast.Ident)) |
| default: |
| return equal(x.Elem(), y.Elem(), identical) |
| } |
| |
| case reflect.Interface: |
| if x.IsNil() || y.IsNil() { |
| return x.IsNil() == y.IsNil() |
| } |
| return equal(x.Elem(), y.Elem(), identical) |
| |
| case reflect.Struct: |
| for i := range x.NumField() { |
| xf := x.Field(i) |
| yf := y.Field(i) |
| // Skip position fields. |
| if xpos, ok := xf.Interface().(token.Pos); ok { |
| ypos := yf.Interface().(token.Pos) |
| // Numeric value of a Pos is not significant but its "zeroness" is, |
| // because it is often significant, e.g. CallExpr.Variadic(Ellipsis), ChanType.Arrow. |
| if xpos.IsValid() != ypos.IsValid() { |
| return false |
| } |
| } else if !equal(xf, yf, identical) { |
| return false |
| } |
| } |
| return true |
| |
| case reflect.Slice: |
| if x.IsNil() || y.IsNil() { |
| return x.IsNil() == y.IsNil() |
| } |
| if x.Len() != y.Len() { |
| return false |
| } |
| for i := range x.Len() { |
| if !equal(x.Index(i), y.Index(i), identical) { |
| return false |
| } |
| } |
| return true |
| |
| case reflect.String: |
| return x.String() == y.String() |
| |
| case reflect.Bool: |
| return x.Bool() == y.Bool() |
| |
| case reflect.Int: |
| return x.Int() == y.Int() |
| |
| default: |
| panic(x) |
| } |
| } |