Skip to content

Commit b75baa0

Browse files
committed
internal/astutil/cursor: Cursor API for inspector
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 <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent ebeac1b commit b75baa0

File tree

6 files changed

+886
-3
lines changed

6 files changed

+886
-3
lines changed

Diff for: go/ast/inspector/inspector.go

+4
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ package inspector
3636

3737
import (
3838
"go/ast"
39+
_ "unsafe"
3940
)
4041

4142
// An Inspector provides methods for inspecting
@@ -44,6 +45,9 @@ type Inspector struct {
4445
events []event
4546
}
4647

48+
//go:linkname events
49+
func events(in *Inspector) []event { return in.events }
50+
4751
// New returns an Inspector for the specified syntax trees.
4852
func New(files []*ast.File) *Inspector {
4953
return &Inspector{traverse(files)}

Diff for: go/ast/inspector/typeof.go

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ package inspector
1212
import (
1313
"go/ast"
1414
"math"
15+
16+
_ "unsafe"
1517
)
1618

1719
const (
@@ -215,6 +217,7 @@ func typeOf(n ast.Node) uint64 {
215217
return 0
216218
}
217219

220+
//go:linkname maskOf
218221
func maskOf(nodes []ast.Node) uint64 {
219222
if nodes == nil {
220223
return math.MaxUint64 // match all node types

Diff for: gopls/internal/analysis/unusedparams/unusedparams.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"golang.org/x/tools/go/ast/inspector"
1616
"golang.org/x/tools/gopls/internal/util/moreslices"
1717
"golang.org/x/tools/internal/analysisinternal"
18+
"golang.org/x/tools/internal/astutil/cursor"
1819
)
1920

2021
//go:embed doc.go
@@ -141,7 +142,7 @@ func run(pass *analysis.Pass) (any, error) {
141142
(*ast.FuncDecl)(nil),
142143
(*ast.FuncLit)(nil),
143144
}
144-
inspect.WithStack(filter, func(n ast.Node, push bool, stack []ast.Node) bool {
145+
cursor.Root(inspect).Inspect(filter, func(c cursor.Cursor, push bool) bool {
145146
// (We always return true so that we visit nested FuncLits.)
146147

147148
if !push {
@@ -153,7 +154,7 @@ func run(pass *analysis.Pass) (any, error) {
153154
ftype *ast.FuncType
154155
body *ast.BlockStmt
155156
)
156-
switch n := n.(type) {
157+
switch n := c.Node().(type) {
157158
case *ast.FuncDecl:
158159
// We can't analyze non-Go functions.
159160
if n.Body == nil {
@@ -182,7 +183,8 @@ func run(pass *analysis.Pass) (any, error) {
182183
// Find the symbol for the variable (if any)
183184
// to which the FuncLit is bound.
184185
// (We don't bother to allow ParenExprs.)
185-
switch parent := stack[len(stack)-2].(type) {
186+
stack := c.Stack(nil)
187+
switch parent := stack[len(stack)-2].Node().(type) {
186188
case *ast.AssignStmt:
187189
// f = func() {...}
188190
// f := func() {...}

0 commit comments

Comments
 (0)