-
Notifications
You must be signed in to change notification settings - Fork 18k
x/tools/go/ast/inspector: add Cursor, to enable partial and multi-level traversals #70859
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Related Issues
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.) |
Change https://go.dev/cl/636156 mentions this issue: |
Change https://go.dev/cl/636656 mentions this issue: |
Summarizing discussion from the CL: we're a little concerned about
We are going to use the |
This CL defines the Cursor type, which represents a node in the traversal done by go/ast/inspector (represented by the index of its push event). From this, we can derive all the operators for navigating the AST in the four "cardinal directions", similar to the DOM of a web browser: - Parent (the immediate parent) - Stack (all ancestors) - Children, FirstChild, and LastChild - PrevSibling and NextSibling All operations are O(1) except Parent, which is O(n) for a node in a list of length n. In addition, all the usual traversals over the inspector can be offered as traversals within a subtree rooted at a given node, allowing multi-level traversals with independent control over type-based node filtering and independent control over pruning descent into subtrees. We can also provide fast means to obtain the cursor for a particular node or position. - Cursor.Inspect(types []ast.Node, f func(c Cursor, push bool) (bool)) - Cursor.Preorder(types ...ast.Node) iter.Seq[Cursor] - Cursor.FindNode(n ast.Node) (Cursor, bool) - Cursor.FindPos(start, end token.Pos) (Cursor, bool) All of these operations are simple to express using inspector's existing threaded tree representation, and they make it both simpler and more efficient to express the kinds of queries that turn up in our refactoring efforts. For example: "Find all function literals; then find all defer statements within them; then go to the previous statement to see if it is a call to Mutex.Lock" etc. This CL also includes one token usage of Cursor from an existing analyzer. The CursorStack benchmark is significantly slower than the existing WithStack operation, but I suspect the frequency of calls to Cursor.Stack is actually much rarer in practice than the benchmark would suggest, because one typically calls Stack only after a highly selective filtering. When Stack is called infrequently, the Cursor-based traversal is about 27% faster while still providing the option to obtain the stack when needed. We will evaluate these operators in the coming weeks and update proposal golang/go#70859 in light of our experience. In the meantime, we must use linkname hackery to augment the existing inspector for use in gopls. Updates golang/go#70859 Change-Id: I1ec8c1a20dc07ad80dad8f0038c0cf3f8f791050 Reviewed-on: https://go-review.googlesource.com/c/tools/+/636656 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Robert Findley <rfindley@google.com>
The Inspect method provides pruning, at the cost of the need to deal with the annoying
(It's actually fairly uncommon to prune the parent traversal after a nested traversal.)
It's true that Stack is not as fast as WithStack if you need the stack for every visited node, but one never does: the stack is typically needed only for a small minority of visited nodes, and typically only the parent or perhaps grandparent stack elements are needed. So in practice Cursor is much faster. |
This proposal has been added to the active column of the proposals project |
An internal version of this is widely used in gopls and is being used in x/tools analysis, so it's already proven to be a good and useful API. @adonovan , you mentioned there were a few tweaks you've made while using this. Could you update the proposed API to reflect them? @griesemer and @adonovan are going to sit down "together" and make sure they agree on the API. |
I've made some minor tweaks to the issue body above. The remaining issues to discuss are:
// 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.
func (*inspector.Inspector) At(index int32) Cursor
// 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.
// The value is intended only for use with [At].
//
// Index must not be called on the Root node.
func (c Cursor) Index() int32 Also,
|
Addressing the above:
|
Would it be worth having a
It's not clear from the name if this returns a Cursor completely containing [start, end) or completely contained within [start, end), or somehow otherwise overlapping. Maybe we try to capture "cursor containing pos range" in the name? |
Change https://go.dev/cl/666056 mentions this issue: |
If we were starting over, I would remove all the methods from Inspector, since the Root Cursor provides everything, so that Inspector becomes just an abstract type representing the traversal data structure. I would also define New as
@gri suggested Innermost, but was ok with FindByPos, which is what I changed it to in the most recent CL. I don't have any better ideas. |
1. Eliminate Cursor.Stack: it is redundant wrt Cursor.Enclosing, and essentially never needed since the stack can easily be computed cheaply on demand. 2. Rename FindPos to FindByPos. 3. Remove "push bool" parameter from Inspect callback. It has never once been needed; the only times we have ever needed to have some effect after visiting a node, we have wanted to express the whole recursion ourselves (see e.g. freeVisitor in gopls). 4. Add Cursor.Inspector accessor for completeness. Also, simplify the traversal in unusedparams. Updates golang/go#70859 Change-Id: I1c5218d1597105d6ffb423cbbb2306d62cabc47d Reviewed-on: https://go-review.googlesource.com/c/tools/+/666056 Commit-Queue: Alan Donovan <adonovan@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Robert Findley <rfindley@google.com> Auto-Submit: Alan Donovan <adonovan@google.com>
|
Based on the discussion above, this proposal seems like a likely accept. The proposal details are in #70859 (comment) |
No change in consensus, so accepted. 🎉 The proposal details are in #70859 (comment) |
Change https://go.dev/cl/670835 mentions this issue: |
Background: The inspector package enables efficient repeated traversal over the list of ast.Files that make up a package. However, unlike ast.Inspect, it provides no way to inspect just some subtree, which is a common and recurring need. In particular, it is common to need a two-level traversal, in which one first inspects to find each node of type A, then conditionally visits some subtrees of it looking for nodes of type B.
This need could be addressed if the inspector could iterate over a sequence of abstract cursor positions from which the actual ast.Node could be queried. A cursor can then be used as the basis for a new traversal of the subtree rooted at that node, with a different type filter.
As a bonus, the WithStack operation would not be needed because the stack can be derived relatively efficiently from the cursor position by walking back from the current point, skipping from pop to push nodes.
Unfortunately the inspector as defined is stateless, so we can't simply add a
Current() Cursor
method to it, nor sneak a Cursor among the arguments to the existing callback functions. Cursor would need all the same visitation methods as Inspector, and Inspector's methods would conceptually delegate to the "root" Cursor. However, we can do better than simply duplicating the old API.Prototype implementation at https://pkg.go.dev/golang.org/x/tools/internal/astutil/cursor.
Proposal: We propose to add the inspector.Cursor type and related methods, plus the supporting edge.Kind enum type.
Summary:
Detailed API
Example usage:
See https://go.dev/cl/636656 for the implementation.
@findleyr @timothy-king @griesemer
Summary of changes:
The text was updated successfully, but these errors were encountered: