Involved Source Filescursor.go Package inspector provides helper functions for traversal over the
syntax trees of a package, including node filtering by type, and
materialization of the traversal stack.
During construction, the inspector does a complete traversal and
builds a list of push/pop events and their node type. Subsequent
method calls that request a traversal scan this list, rather than walk
the AST, and perform type filtering using efficient bit sets.
This representation is sometimes called a "balanced parenthesis tree."
Experiments suggest the inspector's traversals are about 2.5x faster
than [ast.Inspect], but it may take around 5 traversals for this
benefit to amortize the inspector's construction cost.
If efficiency is the primary concern, do not use Inspector for
one-off traversals.
The [Cursor] type provides a more flexible API for efficient
navigation of syntax trees in all four "cardinal directions". For
example, traversals may be nested, so you can find each node of
type A and then search within it for nodes of type B. Or you can
traverse from a node to its immediate neighbors: its parent, its
previous and next sibling, or its first and last child. We
recommend using methods of Cursor in preference to Inspector where
possible.iter.gotypeof.gowalk.go
Package-Level Type Names (total 5, in which 2 are exported)
/* sort exporteds by: | */
A Cursor represents an [ast.Node]. It is immutable.
Two Cursors compare equal if they represent the same node.
The zero value of Cursor is not valid.
Call [Inspector.Root] to obtain a cursor for the virtual root node
of the traversal. This is the sole valid cursor for which [Cursor.Node]
returns nil.
Use the following methods to navigate efficiently around the tree:
- for ancestors, use [Cursor.Parent] and [Cursor.Enclosing];
- for children, use [Cursor.Child], [Cursor.Children],
[Cursor.FirstChild], and [Cursor.LastChild];
- for siblings, use [Cursor.PrevSibling] and [Cursor.NextSibling];
- for descendants, use [Cursor.FindByPos], [Cursor.FindNode],
[Cursor.Inspect], and [Cursor.Preorder].
Use the [Cursor.ChildAt] and [Cursor.ParentEdge] methods for
information about the edges in a tree: which field (and slice
element) of the parent node holds the child.in*Inspector // index of push node; -1 for virtual root node Child returns the cursor for n, which must be a direct child of c's Node.
Child must not be called on the Root node (whose [Cursor.Node] returns nil). ChildAt returns the cursor for the child of the
current node identified by its edge and index.
The index must be -1 if the edge.Kind is not a slice.
The indicated child node must exist.
ChildAt must not be called on the Root node (whose [Cursor.Node] returns nil).
Invariant: c.Parent().ChildAt(c.ParentEdge()) == c. Children returns an iterator over the direct children of the
current node, if any.
When using Children, NextChild, and PrevChild, bear in mind that a
Node's children may come from different fields, some of which may
be lists of nodes without a distinguished intervening container
such as [ast.BlockStmt].
For example, [ast.CaseClause] has a field List of expressions and a
field Body of statements, so the children of a CaseClause are a mix
of expressions and statements. Other nodes that have "uncontained"
list fields include:
- [ast.ValueSpec] (Names, Values)
- [ast.CompositeLit] (Type, Elts)
- [ast.IndexListExpr] (X, Indices)
- [ast.CallExpr] (Fun, Args)
- [ast.AssignStmt] (Lhs, Rhs)
So, do not assume that the previous sibling of an ast.Stmt is also
an ast.Stmt, or if it is, that they are executed sequentially,
unless you have established that, say, its parent is a BlockStmt
or its [Cursor.ParentEdge] is [edge.BlockStmt_List].
For example, given "for S1; ; S2 {}", the predecessor of S2 is S1,
even though they are not executed in sequence. Contains reports whether c contains or is equal to c2.
Both Cursors must belong to the same [Inspector];
neither may be its Root node. Enclosing returns an iterator over the nodes enclosing the current
current node, starting with the Cursor itself.
Enclosing must not be called on the Root node (whose [Cursor.Node] returns nil).
The types argument, if non-empty, enables type-based filtering of
events: the sequence includes only enclosing nodes whose type
matches an element of the types slice. FindByPos returns the cursor for the innermost node n in the tree
rooted at c such that n.Pos() <= start && end <= n.End().
(For an *ast.File, it uses the bounds n.FileStart-n.FileEnd.)
An empty range (start == end) between two adjacent nodes is
considered to belong to the first node.
It returns zero if none is found.
Precondition: start <= end.
See also [astutil.PathEnclosingInterval], which
tolerates adjoining whitespace. FindNode returns the cursor for node n if it belongs to the subtree
rooted at c. It returns zero if n is not found. FirstChild returns the first direct child of the current node,
or zero if it has no children. Index returns the index of this cursor position within the package.
Clients should not assume anything about the numeric Index value
except that it increases monotonically throughout the traversal.
It is provided for use with [Inspector.At].
Index must not be called on the Root node. Inspect visits the nodes of the subtree represented by c in
depth-first order. It calls f(n) for each node n before it
visits n's children. If f returns true, Inspect invokes f
recursively for each of the non-nil children of the node.
Each node is represented by a Cursor that allows access to the
Node, but may also be used to start a new traversal, or to obtain
the stack of nodes enclosing the cursor.
The complete traversal sequence is determined by [ast.Inspect].
The types argument, if non-empty, enables type-based filtering of
events. The function f if is called only for nodes whose type
matches an element of the types slice. Inspector returns the cursor's Inspector.
It returns nil if the Cursor is not valid. LastChild returns the last direct child of the current node,
or zero if it has no children. NextSibling returns the cursor for the next sibling node in the same list
(for example, of files, decls, specs, statements, fields, or expressions) as
the current node. It returns (zero, false) if the node is the last node in
the list, or is not part of a list.
NextSibling must not be called on the Root node.
See note at [Cursor.Children]. Node returns the node at the current cursor position,
or nil for the cursor returned by [Inspector.Root]. Parent returns the parent of the current node.
Parent must not be called on the Root node (whose [Cursor.Node] returns nil). ParentEdge returns the identity of the field in the parent node
that holds this cursor's node, and if it is a list, the index within it.
For example, f(x, y) is a CallExpr whose three children are Idents.
f has edge kind [edge.CallExpr_Fun] and index -1.
x and y have kind [edge.CallExpr_Args] and indices 0 and 1, respectively.
If called on a child of the Root node, it returns ([edge.Invalid], -1).
ParentEdge must not be called on the Root node (whose [Cursor.Node] returns nil). ParentEdgeIndex returns the index component of the result of [Cursor.ParentEdge]. ParentEdgeKind returns the kind component of the result of [Cursor.ParentEdge]. Preorder returns an iterator over the nodes of the subtree
represented by c in depth-first order. Each node in the sequence is
represented by a Cursor that allows access to the Node, but may
also be used to start a new traversal, or to obtain the stack of
nodes enclosing the cursor.
The traversal sequence is determined by [ast.Inspect]. The types
argument, if non-empty, enables type-based filtering of events. The
function f if is called only for nodes whose type matches an
element of the types slice.
If you need control over descent into subtrees,
or need both pre- and post-order notifications, use [Cursor.Inspect] PrevSibling returns the cursor for the previous sibling node in the
same list (for example, of files, decls, specs, statements, fields,
or expressions) as the current node. It returns zero if the node is
the first node in the list, or is not part of a list.
It must not be called on the Root node.
See note at [Cursor.Children]. String returns information about the cursor's node, if any. Valid reports whether the cursor is valid.
The zero value of cursor is invalid.
Unless otherwise documented, it is not safe to call
any other method on an invalid cursor. indices return the [start, end) half-open interval of event indices.
Cursor : expvar.Var
Cursor : fmt.Stringer
Cursor : context.stringer
Cursor : runtime.stringer
func Cursor.Child(n ast.Node) Cursor
func Cursor.ChildAt(k edge.Kind, idx int) Cursor
func Cursor.FindByPos(start, end token.Pos) (Cursor, bool)
func Cursor.FindNode(n ast.Node) (Cursor, bool)
func Cursor.FirstChild() (Cursor, bool)
func Cursor.LastChild() (Cursor, bool)
func Cursor.NextSibling() (Cursor, bool)
func Cursor.Parent() Cursor
func Cursor.PrevSibling() (Cursor, bool)
func (*Inspector).At(index int32) Cursor
func (*Inspector).Root() Cursor
func Cursor.Contains(c2 Cursor) bool
func golang.org/x/tools/internal/typesinternal.EnclosingScope(info *types.Info, cur Cursor) *types.Scope
An Inspector provides methods for inspecting
(traversing) the syntax trees of a package.events[]event At returns the cursor at the specified index in the traversal,
which must have been obtained from [Cursor.Index] on a Cursor
belonging to the same Inspector (see [Cursor.Inspector]). Nodes visits the nodes of the files supplied to [New] in depth-first
order. It calls f(n, true) for each node n before it visits n's
children. If f returns true, Nodes invokes f recursively for each
of the non-nil children of the node, followed by a call of
f(n, false).
The complete traversal sequence is determined by [ast.Inspect].
The types argument, if non-empty, enables type-based filtering of
events. The function f if is called only for nodes whose type
matches an element of the types slice.
The [Cursor.Inspect] method provides a richer alternative interface.
Example:
in.Root().Inspect(types, func(c Cursor) bool {
...
return true
} Preorder visits all the nodes of the files supplied to [New] in
depth-first order. It calls f(n) for each node n before it visits
n's children.
The complete traversal sequence is determined by [ast.Inspect].
The types argument, if non-empty, enables type-based filtering of
events. The function f is called only for nodes whose type
matches an element of the types slice.
The [Cursor.Preorder] method provides a richer alternative interface.
Example:
for c := range in.Root().Preorder(types) { ... } PreorderSeq returns an iterator that visits all the
nodes of the files supplied to [New] in depth-first order.
It visits each node n before n's children.
The complete traversal sequence is determined by ast.Inspect.
The types argument, if non-empty, enables type-based filtering:
only nodes whose type matches an element of the types slice are
included in the sequence.
Example:
for call := range in.PreorderSeq((*ast.CallExpr)(nil)) { ... }
The [All] function is more convenient if there is exactly one node type:
for call := range All[*ast.CallExpr](in) { ... }
See also the newer and more flexible [Cursor] API, which lets you
start the traversal at an arbitrary node, and reports each matching
node by its Cursor, enabling easier navigation.
The above example would be written thus:
for curCall := range in.Root().Preorder((*ast.CallExpr)(nil)) {
call := curCall.Node().(*ast.CallExpr)
...
} Root returns a valid cursor for the virtual root node,
whose children are the files provided to [New].
Its [Cursor.Node] method return nil. WithStack visits nodes in a similar manner to Nodes, but it
supplies each call to f an additional argument, the current
traversal stack. The stack's first element is the outermost node,
an *ast.File; its last is the innermost, n.
The [Cursor.Inspect] method provides a richer alternative interface.
Example:
in.Root().Inspect(types, func(c Cursor) bool {
stack := slices.Collect(c.Enclosing())
...
return true
})
func New(files []*ast.File) *Inspector
func Cursor.Inspector() *Inspector
func All[N, S](in *Inspector) iter.Seq[N]
An event represents a push or a pop
of an ast.Node during a traversal. // index of corresponding push or pop eventnodeast.Node // index of parent's push node (push nodes only), or packed edge kind/index (pop nodes only) // typeOf(node) on push event, or union of typ strictly between push and pop events on pop events
func traverse(files []*ast.File) []event
// edge.Kind and index, bit packed // index of current node's push event // index of parent node's push event // accumulated type bits of current node's descendants
Package-Level Functions (total 9, in which 2 are exported)
Type Parameters:
N: interface{*S; ast.Node}
S: any All[N] returns an iterator over all the nodes of type N.
N must be a pointer-to-struct type that implements ast.Node.
Example:
for call := range All[*ast.CallExpr](in) { ... }
See also the newer and more flexible [Cursor] API, which lets you
start the traversal at an arbitrary node, and reports each matching
node by its Cursor, enabling easier navigation.
The above example would be written thus:
for curCall := range in.Root().Preorder((*ast.CallExpr)(nil)) {
call := curCall.Node().(*ast.CallExpr)
...
}
New returns an Inspector for the specified syntax trees.
traverse builds the table of events representing a traversal.
typeOf returns a distinct single-bit value that represents the type of n.
Various implementations were benchmarked with BenchmarkNewInspector:
GOGC=off
- type switch 4.9-5.5ms 2.1ms
- binary search over a sorted list of types 5.5-5.9ms 2.5ms
- linear scan, frequency-ordered list 5.9-6.1ms 2.7ms
- linear scan, unordered list 6.4ms 2.7ms
- hash table 6.5ms 3.1ms
A perfect hash seemed like overkill.
The compiler's switch statement is the clear winner
as it produces a binary tree in code,
with constant conditions and good branch prediction.
(Sadly it is the most verbose in source code.)
Binary search suffered from poor branch prediction.
unpackEdgeKindAndIndex unpacks the edge kind and edge index (within
an []ast.Node slice) from the parent field of a pop event.