go/types/internal/play: show Cursor path (e.g. File.Decls[0].(*ast.GenDecl) etc) Also, add Cursor.GoString method that shows the complete path. Updates golang/go#80024 Change-Id: I437b7314ef19b5d3077256c25a4990a17ced53b6 Reviewed-on: https://go-review.googlesource.com/c/tools/+/790900 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Alex Putman <aputman@golang.org>
diff --git a/go/ast/inspector/cursor.go b/go/ast/inspector/cursor.go index 239b10c..1c48225 100644 --- a/go/ast/inspector/cursor.go +++ b/go/ast/inspector/cursor.go
@@ -10,6 +10,7 @@ "go/token" "iter" "reflect" + "strings" "golang.org/x/tools/go/ast/edge" ) @@ -110,6 +111,46 @@ return reflect.TypeOf(c.Node()).String() } +// GoString returns a string describing the cursor's path from the +// root, if any. +func (c Cursor) GoString() string { + if !c.Valid() { + return "(invalid)" + } + if c.index < 0 { + return "(root)" + } + // e.g "File.Decls[1].(*ast.GenDecl).Specs[0].(*ast.TypeSpec)" + // + // In hindsight even the File node should have reported a + // virtual ParentEdge of (Root_Files, i) where i is the index + // among the files passed to NewInspector. Then the path would + // read "(root).Files[i]", etc; but we missed the boat. + var buf strings.Builder + buf.WriteString("File") + var visit func(Cursor) + visit = func(c Cursor) { + ek, idx := c.ParentEdge() + if ek == edge.Invalid { + return // File + } + visit(c.Parent()) + fmt.Fprintf(&buf, ".%s", ek.FieldName()) + if idx >= 0 { + fmt.Fprintf(&buf, "[%d]", idx) + } + ftype := ek.FieldType() + if idx >= 0 { + ftype = ftype.Elem() // []T -> T + } + if ftype.Kind() == reflect.Interface { + fmt.Fprintf(&buf, ".(%T)", c.Node()) + } + } + visit(c) + return buf.String() +} + // indices return the [start, end) half-open interval of event indices. func (c Cursor) indices() (int32, int32) { if c.index < 0 {
diff --git a/go/ast/inspector/cursor_test.go b/go/ast/inspector/cursor_test.go index c216492..42ba57b 100644 --- a/go/ast/inspector/cursor_test.go +++ b/go/ast/inspector/cursor_test.go
@@ -542,6 +542,33 @@ } } +func TestCursor_GoString(t *testing.T) { + const src = `package a +func f() +func g() { + print("goodbye") +} +` + fset := token.NewFileSet() + file, _ := parser.ParseFile(fset, "a.go", src, 0) + root := inspector.New([]*ast.File{file}).Root() + + // Find sole (string) literal. + var curLit inspector.Cursor + for cur := range root.Preorder((*ast.BasicLit)(nil)) { + curLit = cur + } + got := curLit.GoString() + want := "File.Decls[1].(*ast.FuncDecl).Body.List[0].(*ast.ExprStmt).X.(*ast.CallExpr).Args[0].(*ast.BasicLit)" + if got != want { + t.Errorf("GoString = %q, want %s", got, want) + } + + if file.Decls[1].(*ast.FuncDecl).Body.List[0].(*ast.ExprStmt).X.(*ast.CallExpr).Args[0].(*ast.BasicLit) != curLit.Node() { + t.Errorf("GoString path was inaccurate") + } +} + func is[T any](x any) bool { _, ok := x.(T) return ok
diff --git a/go/types/internal/play/play.go b/go/types/internal/play/play.go index f48394c..a682dc2 100644 --- a/go/types/internal/play/play.go +++ b/go/types/internal/play/play.go
@@ -169,10 +169,10 @@ // cases (e.g. around FuncType.Func). curFile, _ := inspector.New([]*ast.File{file}).Root().FirstChild() if cur, ok := curFile.FindByPos(startPos, endPos); ok { - fmt.Fprintf(out, "Cursor.FindByPos().Enclosing() = %v\n", - slices.Collect(cur.Enclosing())) + fmt.Fprintf(out, "Cursor.GoString = %#v\n", cur) + fmt.Fprintf(out, "Cursor.Enclosing = %v\n", slices.Collect(cur.Enclosing())) } else { - fmt.Fprintf(out, "Cursor.FindPos() failed\n") + fmt.Fprintf(out, "Cursor.FindPos failed\n") } // And show the astutil.Select result (enclosing, leftmost & rightmost enclosed). if curEnclosing, curStart, curEnd, err := astutil.Select(curFile, startPos, endPos); err == nil {