Skip to content

Commit e6754ce

Browse files
adonovangopherbot
authored andcommitted
gopls/internal/cache/parsego: add File.Cursor, and use it
This CL adds a Cursor to the parsego.File. Though the primary motivation is convenience and flexibility, it is expected to be an optimization: though it is computed eagerly, it is retained in the parse cache, and is expected to pay for itself very quickly by allowing us to replace many whole-File ast.Inspect operations with more targeted traversals. The CL replaces all ast.Inspect(file) operations with Cursor, but there remain many more opportunities for using it in narrower traversals, and in places that need to navigate to siblings or ancestors. Also, amend Cursor.FindPos to use the complete range of the File, as CL 637738 recently did for astutil.NodeContains. Also, various clean-ups to InlayHint: - push the traversals down in InlayHint to avoid having to scan a slice for every single node we visit; - simplify the function signature used for each hint algorithm. Change-Id: I64d0c2cae75fd73a4b539ceb81ad9d6f7d80cfb8 Reviewed-on: https://go-review.googlesource.com/c/tools/+/650396 Reviewed-by: Jonathan Amsterdam <[email protected]> Auto-Submit: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Commit-Queue: Alan Donovan <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent 776604a commit e6754ce

File tree

9 files changed

+326
-317
lines changed

9 files changed

+326
-317
lines changed

gopls/internal/cache/parsego/file.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"golang.org/x/tools/gopls/internal/protocol"
1515
"golang.org/x/tools/gopls/internal/util/bug"
1616
"golang.org/x/tools/gopls/internal/util/safetoken"
17+
"golang.org/x/tools/internal/astutil/cursor"
1718
)
1819

1920
// A File contains the results of parsing a Go file.
@@ -32,6 +33,8 @@ type File struct {
3233
// actual content of the file if we have fixed the AST.
3334
Src []byte
3435

36+
Cursor cursor.Cursor // cursor of *ast.File, sans sibling files
37+
3538
// fixedSrc and fixedAST report on "fixing" that occurred during parsing of
3639
// this file.
3740
//
@@ -71,6 +74,11 @@ func (pgf *File) PositionPos(p protocol.Position) (token.Pos, error) {
7174
return safetoken.Pos(pgf.Tok, offset)
7275
}
7376

77+
// PosPosition returns a protocol Position for the token.Pos in this file.
78+
func (pgf *File) PosPosition(pos token.Pos) (protocol.Position, error) {
79+
return pgf.Mapper.PosPosition(pgf.Tok, pos)
80+
}
81+
7482
// PosRange returns a protocol Range for the token.Pos interval in this file.
7583
func (pgf *File) PosRange(start, end token.Pos) (protocol.Range, error) {
7684
return pgf.Mapper.PosRange(pgf.Tok, start, end)

gopls/internal/cache/parsego/parse.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@ import (
2323
"reflect"
2424
"slices"
2525

26+
"golang.org/x/tools/go/ast/inspector"
2627
"golang.org/x/tools/gopls/internal/label"
2728
"golang.org/x/tools/gopls/internal/protocol"
2829
"golang.org/x/tools/gopls/internal/util/astutil"
2930
"golang.org/x/tools/gopls/internal/util/bug"
3031
"golang.org/x/tools/gopls/internal/util/safetoken"
32+
"golang.org/x/tools/internal/astutil/cursor"
3133
"golang.org/x/tools/internal/diff"
3234
"golang.org/x/tools/internal/event"
3335
)
@@ -153,6 +155,11 @@ func Parse(ctx context.Context, fset *token.FileSet, uri protocol.DocumentURI, s
153155
}
154156
assert(file != nil, "nil *ast.File")
155157

158+
// Provide a cursor for fast and convenient navigation.
159+
inspect := inspector.New([]*ast.File{file})
160+
curFile, _ := cursor.Root(inspect).FirstChild()
161+
_ = curFile.Node().(*ast.File)
162+
156163
return &File{
157164
URI: uri,
158165
Mode: mode,
@@ -161,6 +168,7 @@ func Parse(ctx context.Context, fset *token.FileSet, uri protocol.DocumentURI, s
161168
fixedAST: fixedAST,
162169
File: file,
163170
Tok: tok,
171+
Cursor: curFile,
164172
Mapper: protocol.NewMapper(uri, src),
165173
ParseErr: parseErr,
166174
}, fixes

gopls/internal/cache/xrefs/xrefs.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ func Index(files []*parsego.File, pkg *types.Package, info *types.Info) []byte {
4444
objectpathFor := new(objectpath.Encoder).For
4545

4646
for fileIndex, pgf := range files {
47-
ast.Inspect(pgf.File, func(n ast.Node) bool {
48-
switch n := n.(type) {
47+
for cur := range pgf.Cursor.Preorder((*ast.Ident)(nil), (*ast.ImportSpec)(nil)) {
48+
switch n := cur.Node().(type) {
4949
case *ast.Ident:
5050
// Report a reference for each identifier that
5151
// uses a symbol exported from another package.
@@ -68,7 +68,7 @@ func Index(files []*parsego.File, pkg *types.Package, info *types.Info) []byte {
6868
if err != nil {
6969
// Capitalized but not exported
7070
// (e.g. local const/var/type).
71-
return true
71+
continue
7272
}
7373
gobObj = &gobObject{Path: path}
7474
objects[obj] = gobObj
@@ -91,7 +91,7 @@ func Index(files []*parsego.File, pkg *types.Package, info *types.Info) []byte {
9191
// string to the imported package.
9292
pkgname := info.PkgNameOf(n)
9393
if pkgname == nil {
94-
return true // missing import
94+
continue // missing import
9595
}
9696
objects := getObjects(pkgname.Imported())
9797
gobObj, ok := objects[nil]
@@ -109,8 +109,7 @@ func Index(files []*parsego.File, pkg *types.Package, info *types.Info) []byte {
109109
bug.Reportf("out of bounds import spec %+v", n.Path)
110110
}
111111
}
112-
return true
113-
})
112+
}
114113
}
115114

116115
// Flatten the maps into slices, and sort for determinism.

gopls/internal/golang/folding_range.go

Lines changed: 77 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,84 @@ func FoldingRange(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle,
4646
ranges := commentsFoldingRange(pgf)
4747

4848
// Walk the ast and collect folding ranges.
49-
ast.Inspect(pgf.File, func(n ast.Node) bool {
50-
if rng, ok := foldingRangeFunc(pgf, n, lineFoldingOnly); ok {
51-
ranges = append(ranges, rng)
49+
filter := []ast.Node{
50+
(*ast.BasicLit)(nil),
51+
(*ast.BlockStmt)(nil),
52+
(*ast.CallExpr)(nil),
53+
(*ast.CaseClause)(nil),
54+
(*ast.CommClause)(nil),
55+
(*ast.CompositeLit)(nil),
56+
(*ast.FieldList)(nil),
57+
(*ast.GenDecl)(nil),
58+
}
59+
for cur := range pgf.Cursor.Preorder(filter...) {
60+
// TODO(suzmue): include trailing empty lines before the closing
61+
// parenthesis/brace.
62+
var kind protocol.FoldingRangeKind
63+
// start and end define the range of content to fold away.
64+
var start, end token.Pos
65+
switch n := cur.Node().(type) {
66+
case *ast.BlockStmt:
67+
// Fold between positions of or lines between "{" and "}".
68+
start, end = getLineFoldingRange(pgf, n.Lbrace, n.Rbrace, lineFoldingOnly)
69+
70+
case *ast.CaseClause:
71+
// Fold from position of ":" to end.
72+
start, end = n.Colon+1, n.End()
73+
74+
case *ast.CommClause:
75+
// Fold from position of ":" to end.
76+
start, end = n.Colon+1, n.End()
77+
78+
case *ast.CallExpr:
79+
// Fold between positions of or lines between "(" and ")".
80+
start, end = getLineFoldingRange(pgf, n.Lparen, n.Rparen, lineFoldingOnly)
81+
82+
case *ast.FieldList:
83+
// Fold between positions of or lines between opening parenthesis/brace and closing parenthesis/brace.
84+
start, end = getLineFoldingRange(pgf, n.Opening, n.Closing, lineFoldingOnly)
85+
86+
case *ast.GenDecl:
87+
// If this is an import declaration, set the kind to be protocol.Imports.
88+
if n.Tok == token.IMPORT {
89+
kind = protocol.Imports
90+
}
91+
// Fold between positions of or lines between "(" and ")".
92+
start, end = getLineFoldingRange(pgf, n.Lparen, n.Rparen, lineFoldingOnly)
93+
94+
case *ast.BasicLit:
95+
// Fold raw string literals from position of "`" to position of "`".
96+
if n.Kind == token.STRING && len(n.Value) >= 2 && n.Value[0] == '`' && n.Value[len(n.Value)-1] == '`' {
97+
start, end = n.Pos(), n.End()
98+
}
99+
100+
case *ast.CompositeLit:
101+
// Fold between positions of or lines between "{" and "}".
102+
start, end = getLineFoldingRange(pgf, n.Lbrace, n.Rbrace, lineFoldingOnly)
103+
104+
default:
105+
panic(n)
52106
}
53-
return true
54-
})
107+
108+
// Check that folding positions are valid.
109+
if !start.IsValid() || !end.IsValid() {
110+
continue
111+
}
112+
if start == end {
113+
// Nothing to fold.
114+
continue
115+
}
116+
// in line folding mode, do not fold if the start and end lines are the same.
117+
if lineFoldingOnly && safetoken.Line(pgf.Tok, start) == safetoken.Line(pgf.Tok, end) {
118+
continue
119+
}
120+
rng, err := pgf.PosRange(start, end)
121+
if err != nil {
122+
bug.Reportf("failed to create range: %s", err) // can't happen
123+
continue
124+
}
125+
ranges = append(ranges, foldingRange(kind, rng))
126+
}
55127

56128
// Sort by start position.
57129
slices.SortFunc(ranges, func(x, y protocol.FoldingRange) int {
@@ -64,66 +136,6 @@ func FoldingRange(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle,
64136
return ranges, nil
65137
}
66138

67-
// foldingRangeFunc calculates the line folding range for ast.Node n
68-
func foldingRangeFunc(pgf *parsego.File, n ast.Node, lineFoldingOnly bool) (protocol.FoldingRange, bool) {
69-
// TODO(suzmue): include trailing empty lines before the closing
70-
// parenthesis/brace.
71-
var kind protocol.FoldingRangeKind
72-
// start and end define the range of content to fold away.
73-
var start, end token.Pos
74-
switch n := n.(type) {
75-
case *ast.BlockStmt:
76-
// Fold between positions of or lines between "{" and "}".
77-
start, end = getLineFoldingRange(pgf, n.Lbrace, n.Rbrace, lineFoldingOnly)
78-
case *ast.CaseClause:
79-
// Fold from position of ":" to end.
80-
start, end = n.Colon+1, n.End()
81-
case *ast.CommClause:
82-
// Fold from position of ":" to end.
83-
start, end = n.Colon+1, n.End()
84-
case *ast.CallExpr:
85-
// Fold between positions of or lines between "(" and ")".
86-
start, end = getLineFoldingRange(pgf, n.Lparen, n.Rparen, lineFoldingOnly)
87-
case *ast.FieldList:
88-
// Fold between positions of or lines between opening parenthesis/brace and closing parenthesis/brace.
89-
start, end = getLineFoldingRange(pgf, n.Opening, n.Closing, lineFoldingOnly)
90-
case *ast.GenDecl:
91-
// If this is an import declaration, set the kind to be protocol.Imports.
92-
if n.Tok == token.IMPORT {
93-
kind = protocol.Imports
94-
}
95-
// Fold between positions of or lines between "(" and ")".
96-
start, end = getLineFoldingRange(pgf, n.Lparen, n.Rparen, lineFoldingOnly)
97-
case *ast.BasicLit:
98-
// Fold raw string literals from position of "`" to position of "`".
99-
if n.Kind == token.STRING && len(n.Value) >= 2 && n.Value[0] == '`' && n.Value[len(n.Value)-1] == '`' {
100-
start, end = n.Pos(), n.End()
101-
}
102-
case *ast.CompositeLit:
103-
// Fold between positions of or lines between "{" and "}".
104-
start, end = getLineFoldingRange(pgf, n.Lbrace, n.Rbrace, lineFoldingOnly)
105-
}
106-
107-
// Check that folding positions are valid.
108-
if !start.IsValid() || !end.IsValid() {
109-
return protocol.FoldingRange{}, false
110-
}
111-
if start == end {
112-
// Nothing to fold.
113-
return protocol.FoldingRange{}, false
114-
}
115-
// in line folding mode, do not fold if the start and end lines are the same.
116-
if lineFoldingOnly && safetoken.Line(pgf.Tok, start) == safetoken.Line(pgf.Tok, end) {
117-
return protocol.FoldingRange{}, false
118-
}
119-
rng, err := pgf.PosRange(start, end)
120-
if err != nil {
121-
bug.Reportf("failed to create range: %s", err) // can't happen
122-
return protocol.FoldingRange{}, false
123-
}
124-
return foldingRange(kind, rng), true
125-
}
126-
127139
// getLineFoldingRange returns the folding range for nodes with parentheses/braces/brackets
128140
// that potentially can take up multiple lines.
129141
func getLineFoldingRange(pgf *parsego.File, open, close token.Pos, lineFoldingOnly bool) (token.Pos, token.Pos) {

gopls/internal/golang/implementation.go

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -352,17 +352,14 @@ func localImplementations(ctx context.Context, snapshot *cache.Snapshot, pkg *ca
352352
var locs []protocol.Location
353353
var methodLocs []methodsets.Location
354354
for _, pgf := range pkg.CompiledGoFiles() {
355-
ast.Inspect(pgf.File, func(n ast.Node) bool {
356-
spec, ok := n.(*ast.TypeSpec)
357-
if !ok {
358-
return true // not a type declaration
359-
}
355+
for cur := range pgf.Cursor.Preorder((*ast.TypeSpec)(nil)) {
356+
spec := cur.Node().(*ast.TypeSpec)
360357
def := pkg.TypesInfo().Defs[spec.Name]
361358
if def == nil {
362-
return true // "can't happen" for types
359+
continue // "can't happen" for types
363360
}
364361
if def.(*types.TypeName).IsAlias() {
365-
return true // skip type aliases to avoid duplicate reporting
362+
continue // skip type aliases to avoid duplicate reporting
366363
}
367364
candidateType := methodsets.EnsurePointer(def.Type())
368365

@@ -373,20 +370,20 @@ func localImplementations(ctx context.Context, snapshot *cache.Snapshot, pkg *ca
373370
// TODO(adonovan): UX: report I/I pairs too?
374371
// The same question appears in the global algorithm (methodsets).
375372
if !concreteImplementsIntf(&msets, candidateType, queryType) {
376-
return true // not assignable
373+
continue // not assignable
377374
}
378375

379376
// Ignore types with empty method sets.
380377
// (No point reporting that every type satisfies 'any'.)
381378
mset := msets.MethodSet(candidateType)
382379
if mset.Len() == 0 {
383-
return true
380+
continue
384381
}
385382

386383
if method == nil {
387384
// Found matching type.
388385
locs = append(locs, mustLocation(pgf, spec.Name))
389-
return true
386+
continue
390387
}
391388

392389
// Find corresponding method.
@@ -407,8 +404,7 @@ func localImplementations(ctx context.Context, snapshot *cache.Snapshot, pkg *ca
407404
break
408405
}
409406
}
410-
return true
411-
})
407+
}
412408
}
413409

414410
// Finally convert method positions to protocol form by reading the files.

0 commit comments

Comments
 (0)