Skip to content

Commit 865cd20

Browse files
adonovangopherbot
authored andcommitted
x/tools: various cleanups related to planned parser changes
This CL extracts the good bits out of a large exploratory patch (CL 668677) to update x/tools in anticipation of parser and AST changes that I was hoping to land in go1.25; however that isn't going to happen. Updates golang/go#73438 Updates golang/go#66790 Updates golang/go#66683 Updates golang/go#67704 Change-Id: Iba4a0a7c4a93d04fc6d46466c9fb9980d52067a3 Reviewed-on: https://go-review.googlesource.com/c/tools/+/672055 LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Alan Donovan <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent 8ab19ea commit 865cd20

File tree

14 files changed

+102
-99
lines changed

14 files changed

+102
-99
lines changed

go/ast/astutil/enclosing.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,9 @@ func childrenOf(n ast.Node) []ast.Node {
207207
return false // no recursion
208208
})
209209

210+
// TODO(adonovan): be more careful about missing (!Pos.Valid)
211+
// tokens in trees produced from invalid input.
212+
210213
// Then add fake Nodes for bare tokens.
211214
switch n := n.(type) {
212215
case *ast.ArrayType:
@@ -226,9 +229,12 @@ func childrenOf(n ast.Node) []ast.Node {
226229
children = append(children, tok(n.OpPos, len(n.Op.String())))
227230

228231
case *ast.BlockStmt:
229-
children = append(children,
230-
tok(n.Lbrace, len("{")),
231-
tok(n.Rbrace, len("}")))
232+
if n.Lbrace.IsValid() {
233+
children = append(children, tok(n.Lbrace, len("{")))
234+
}
235+
if n.Rbrace.IsValid() {
236+
children = append(children, tok(n.Rbrace, len("}")))
237+
}
232238

233239
case *ast.BranchStmt:
234240
children = append(children,
@@ -304,9 +310,12 @@ func childrenOf(n ast.Node) []ast.Node {
304310
// TODO(adonovan): Field.{Doc,Comment,Tag}?
305311

306312
case *ast.FieldList:
307-
children = append(children,
308-
tok(n.Opening, len("(")), // or len("[")
309-
tok(n.Closing, len(")"))) // or len("]")
313+
if n.Opening.IsValid() {
314+
children = append(children, tok(n.Opening, len("(")))
315+
}
316+
if n.Closing.IsValid() {
317+
children = append(children, tok(n.Closing, len(")")))
318+
}
310319

311320
case *ast.File:
312321
// TODO test: Doc

gopls/internal/cache/check.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2070,12 +2070,14 @@ func typeErrorsToDiagnostics(pkg *syntaxPackage, inputs *typeCheckInputs, errs [
20702070
}
20712071
}
20722072
} else {
2073+
// TODO(adonovan): check File(start)==File(end).
2074+
20732075
// debugging golang/go#65960
20742076
if _, err := safetoken.Offset(pgf.Tok, end); err != nil {
20752077
if pkg.hasFixedFiles() {
2076-
bug.Reportf("ReadGo116ErrorData returned invalid end: %v (fixed files)", err)
2078+
bug.Reportf("ErrorCodeStartEnd returned invalid end: %v (fixed files)", err)
20772079
} else {
2078-
bug.Reportf("ReadGo116ErrorData returned invalid end: %v", err)
2080+
bug.Reportf("ErrorCodeStartEnd returned invalid end: %v", err)
20792081
}
20802082
}
20812083
}

gopls/internal/cache/parsego/parse.go

Lines changed: 32 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ func fixAST(n ast.Node, tok *token.File, src []byte) (fixes []FixType) {
210210

211211
// walkASTWithParent walks the AST rooted at n. The semantics are
212212
// similar to ast.Inspect except it does not call f(nil).
213+
// TODO(adonovan): replace with PreorderStack.
213214
func walkASTWithParent(n ast.Node, f func(n ast.Node, parent ast.Node) bool) {
214215
var ancestors []ast.Node
215216
ast.Inspect(n, func(n ast.Node) (recurse bool) {
@@ -422,8 +423,10 @@ func fixEmptySwitch(body *ast.BlockStmt, tok *token.File, src []byte) bool {
422423
return true
423424
}
424425

425-
// fixDanglingSelector inserts real "_" selector expressions in place
426-
// of phantom "_" selectors. For example:
426+
// fixDanglingSelector inserts a real "_" selector expression in place
427+
// of a phantom parser-inserted "_" selector so that the parser will
428+
// not consume the following non-identifier token.
429+
// For example:
427430
//
428431
// func _() {
429432
// x.<>
@@ -453,17 +456,13 @@ func fixDanglingSelector(s *ast.SelectorExpr, tf *token.File, src []byte) []byte
453456
return nil
454457
}
455458

456-
var buf bytes.Buffer
457-
buf.Grow(len(src) + 1)
458-
buf.Write(src[:insertOffset])
459-
buf.WriteByte('_')
460-
buf.Write(src[insertOffset:])
461-
return buf.Bytes()
459+
return slices.Concat(src[:insertOffset], []byte("_"), src[insertOffset:])
462460
}
463461

464-
// fixPhantomSelector tries to fix selector expressions with phantom
465-
// "_" selectors. In particular, we check if the selector is a
466-
// keyword, and if so we swap in an *ast.Ident with the keyword text. For example:
462+
// fixPhantomSelector tries to fix selector expressions whose Sel is a
463+
// phantom (parser-invented) "_". If the text after the '.' is a
464+
// keyword, it updates Sel to a fake ast.Ident of that name. For
465+
// example:
467466
//
468467
// foo.var
469468
//
@@ -498,21 +497,18 @@ func fixPhantomSelector(sel *ast.SelectorExpr, tf *token.File, src []byte) bool
498497
})
499498
}
500499

501-
// isPhantomUnderscore reports whether the given ident is a phantom
502-
// underscore. The parser sometimes inserts phantom underscores when
503-
// it encounters otherwise unparseable situations.
500+
// isPhantomUnderscore reports whether the given ident from a
501+
// SelectorExpr.Sel was invented by the parser and is not present in
502+
// source text. The parser creates a blank "_" identifier when the
503+
// syntax (e.g. a selector) demands one but none is present. The fixer
504+
// also inserts them.
504505
func isPhantomUnderscore(id *ast.Ident, tok *token.File, src []byte) bool {
505-
if id == nil || id.Name != "_" {
506-
return false
506+
switch id.Name {
507+
case "_": // go1.24 parser
508+
offset, err := safetoken.Offset(tok, id.Pos())
509+
return err == nil && offset < len(src) && src[offset] != '_'
507510
}
508-
509-
// Phantom underscore means the underscore is not actually in the
510-
// program text.
511-
offset, err := safetoken.Offset(tok, id.Pos())
512-
if err != nil {
513-
return false
514-
}
515-
return len(src) <= offset || src[offset] != '_'
511+
return false // real
516512
}
517513

518514
// fixInitStmt fixes cases where the parser misinterprets an
@@ -821,11 +817,7 @@ FindTo:
821817
// positions are valid.
822818
func parseStmt(tok *token.File, pos token.Pos, src []byte) (ast.Stmt, error) {
823819
// Wrap our expression to make it a valid Go file we can pass to ParseFile.
824-
fileSrc := bytes.Join([][]byte{
825-
[]byte("package fake;func _(){"),
826-
src,
827-
[]byte("}"),
828-
}, nil)
820+
fileSrc := slices.Concat([]byte("package fake;func _(){"), src, []byte("}"))
829821

830822
// Use ParseFile instead of ParseExpr because ParseFile has
831823
// best-effort behavior, whereas ParseExpr fails hard on any error.
@@ -873,8 +865,8 @@ var tokenPosType = reflect.TypeOf(token.NoPos)
873865

874866
// offsetPositions applies an offset to the positions in an ast.Node.
875867
func offsetPositions(tok *token.File, n ast.Node, offset token.Pos) {
876-
fileBase := int64(tok.Base())
877-
fileEnd := fileBase + int64(tok.Size())
868+
fileBase := token.Pos(tok.Base())
869+
fileEnd := fileBase + token.Pos(tok.Size())
878870
ast.Inspect(n, func(n ast.Node) bool {
879871
if n == nil {
880872
return false
@@ -894,20 +886,21 @@ func offsetPositions(tok *token.File, n ast.Node, offset token.Pos) {
894886
continue
895887
}
896888

889+
pos := token.Pos(f.Int())
890+
897891
// Don't offset invalid positions: they should stay invalid.
898-
if !token.Pos(f.Int()).IsValid() {
892+
if !pos.IsValid() {
899893
continue
900894
}
901895

902896
// Clamp value to valid range; see #64335.
903897
//
904898
// TODO(golang/go#64335): this is a hack, because our fixes should not
905-
// produce positions that overflow (but they do: golang/go#64488).
906-
pos := max(f.Int()+int64(offset), fileBase)
907-
if pos > fileEnd {
908-
pos = fileEnd
909-
}
910-
f.SetInt(pos)
899+
// produce positions that overflow (but they do; see golang/go#64488,
900+
// #73438, #66790, #66683, #67704).
901+
pos = min(max(pos+offset, fileBase), fileEnd)
902+
903+
f.SetInt(int64(pos))
911904
}
912905
}
913906

@@ -950,7 +943,7 @@ func replaceNode(parent, oldChild, newChild ast.Node) bool {
950943

951944
switch f.Kind() {
952945
// Check interface and pointer fields.
953-
case reflect.Interface, reflect.Ptr:
946+
case reflect.Interface, reflect.Pointer:
954947
if tryReplace(f) {
955948
return true
956949
}

gopls/internal/cache/parsego/parse_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,14 +300,14 @@ func TestFixPhantomSelector(t *testing.T) {
300300
// ensure the selector has been converted to underscore by parser.
301301
ensureSource(t, src, func(sel *ast.SelectorExpr) {
302302
if sel.Sel.Name != "_" {
303-
t.Errorf("%s: the input doesn't cause a blank selector after parser", tc.source)
303+
t.Errorf("%s: selector name is %q, want _", tc.source, sel.Sel.Name)
304304
}
305305
})
306306

307307
fset := tokeninternal.FileSetFor(pgf.Tok)
308308
inspect(t, pgf, func(sel *ast.SelectorExpr) {
309309
// the fix should restore the selector as is.
310-
if got, want := fmt.Sprintf("%s", analysisinternal.Format(fset, sel)), tc.source; got != want {
310+
if got, want := analysisinternal.Format(fset, sel), tc.source; got != want {
311311
t.Fatalf("got %v want %v", got, want)
312312
}
313313
})

gopls/internal/cache/snapshot.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1463,10 +1463,11 @@ func orphanedFileDiagnosticRange(ctx context.Context, cache *parseCache, fh file
14631463
return nil, protocol.Range{}, false
14641464
}
14651465
pgf := pgfs[0]
1466-
if !pgf.File.Name.Pos().IsValid() {
1466+
name := pgf.File.Name
1467+
if !name.Pos().IsValid() {
14671468
return nil, protocol.Range{}, false
14681469
}
1469-
rng, err := pgf.PosRange(pgf.File.Name.Pos(), pgf.File.Name.End())
1470+
rng, err := pgf.PosRange(name.Pos(), name.End())
14701471
if err != nil {
14711472
return nil, protocol.Range{}, false
14721473
}

gopls/internal/fuzzy/matcher.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ type Matcher struct {
6161
rolesBuf [MaxInputSize]RuneRole
6262
}
6363

64+
func (m *Matcher) String() string { return m.pattern }
65+
6466
func (m *Matcher) bestK(i, j int) int {
6567
if m.scores[i][j][0].val() < m.scores[i][j][1].val() {
6668
return 1

gopls/internal/golang/addtest.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"fmt"
1414
"go/ast"
1515
"go/format"
16-
"go/token"
1716
"go/types"
1817
"os"
1918
"path/filepath"
@@ -395,25 +394,26 @@ func AddTestForFunc(ctx context.Context, snapshot *cache.Snapshot, loc protocol.
395394
NewText: header.String(),
396395
})
397396
} else { // existing _test.go file.
398-
if testPGF.File.Name == nil || testPGF.File.Name.NamePos == token.NoPos {
397+
file := testPGF.File
398+
if !file.Name.NamePos.IsValid() {
399399
return nil, fmt.Errorf("missing package declaration")
400400
}
401-
switch testPGF.File.Name.Name {
401+
switch file.Name.Name {
402402
case pgf.File.Name.Name:
403403
xtest = false
404404
case pgf.File.Name.Name + "_test":
405405
xtest = true
406406
default:
407-
return nil, fmt.Errorf("invalid package declaration %q in test file %q", testPGF.File.Name, testPGF)
407+
return nil, fmt.Errorf("invalid package declaration %q in test file %q", file.Name, testPGF)
408408
}
409409

410-
eofRange, err = testPGF.PosRange(testPGF.File.FileEnd, testPGF.File.FileEnd)
410+
eofRange, err = testPGF.PosRange(file.FileEnd, file.FileEnd)
411411
if err != nil {
412412
return nil, err
413413
}
414414

415415
// Collect all the imports from the foo_test.go.
416-
if testImports, err = collectImports(testPGF.File); err != nil {
416+
if testImports, err = collectImports(file); err != nil {
417417
return nil, err
418418
}
419419
}

gopls/internal/golang/completion/completion.go

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"context"
1111
"fmt"
1212
"go/ast"
13-
"go/build"
1413
"go/constant"
1514
"go/parser"
1615
"go/printer"
@@ -505,7 +504,9 @@ func Completion(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p
505504
startTime := time.Now()
506505

507506
pkg, pgf, err := golang.NarrowestPackageForFile(ctx, snapshot, fh.URI())
508-
if err != nil || pgf.File.Package == token.NoPos {
507+
if err != nil || !pgf.File.Package.IsValid() {
508+
// Invalid package declaration
509+
//
509510
// If we can't parse this file or find position for the package
510511
// keyword, it may be missing a package declaration. Try offering
511512
// suggestions for the package declaration.
@@ -586,12 +587,6 @@ func Completion(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p
586587
}
587588
scopes = append(scopes, pkg.Types().Scope(), types.Universe)
588589

589-
var goversion string // "" => no version check
590-
// Prior go1.22, the behavior of FileVersion is not useful to us.
591-
if slices.Contains(build.Default.ReleaseTags, "go1.22") {
592-
goversion = versions.FileVersion(info, pgf.File) // may be ""
593-
}
594-
595590
opts := snapshot.Options()
596591
c := &completer{
597592
pkg: pkg,
@@ -605,7 +600,7 @@ func Completion(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p
605600
fh: fh,
606601
filename: fh.URI().Path(),
607602
pgf: pgf,
608-
goversion: goversion,
603+
goversion: versions.FileVersion(info, pgf.File), // may be "" => no version check
609604
path: path,
610605
pos: pos,
611606
seen: make(map[types.Object]bool),
@@ -746,24 +741,30 @@ func (c *completer) collectCompletions(ctx context.Context) error {
746741
if c.pgf.File.Name == n {
747742
return c.packageNameCompletions(ctx, c.fh.URI(), n)
748743
} else if sel, ok := c.path[1].(*ast.SelectorExpr); ok && sel.Sel == n {
749-
// Is this the Sel part of a selector?
744+
// We are in the Sel part of a selector (e.g. x.‸sel or x.sel‸).
750745
return c.selector(ctx, sel)
751746
}
752747
return c.lexical(ctx)
753-
// The function name hasn't been typed yet, but the parens are there:
754-
// recv.‸(arg)
748+
755749
case *ast.TypeAssertExpr:
750+
// The function name hasn't been typed yet, but the parens are there:
751+
// recv.‸(arg)
756752
// Create a fake selector expression.
757-
//
753+
758754
// The name "_" is the convention used by go/parser to represent phantom
759755
// selectors.
760756
sel := &ast.Ident{NamePos: n.X.End() + token.Pos(len(".")), Name: "_"}
761757
return c.selector(ctx, &ast.SelectorExpr{X: n.X, Sel: sel})
758+
762759
case *ast.SelectorExpr:
760+
// We are in the X part of a selector (x‸.sel),
761+
// or after the dot with a fixed/phantom Sel (x.‸_).
763762
return c.selector(ctx, n)
764-
// At the file scope, only keywords are allowed.
763+
765764
case *ast.BadDecl, *ast.File:
765+
// At the file scope, only keywords are allowed.
766766
c.addKeywordCompletions()
767+
767768
default:
768769
// fallback to lexical completions
769770
return c.lexical(ctx)
@@ -823,6 +824,8 @@ func (c *completer) scanToken(contents []byte) (token.Pos, token.Token, string)
823824
tok := c.pkg.FileSet().File(c.pos)
824825

825826
var s scanner.Scanner
827+
// TODO(adonovan): fix! this mutates the token.File borrowed from c.pkg,
828+
// calling AddLine and AddLineColumnInfo. Not sound!
826829
s.Init(tok, contents, nil, 0)
827830
for {
828831
tknPos, tkn, lit := s.Scan()
@@ -1232,6 +1235,9 @@ const (
12321235
)
12331236

12341237
// selector finds completions for the specified selector expression.
1238+
//
1239+
// The caller should ensure that sel.X has type information,
1240+
// even if sel is synthetic.
12351241
func (c *completer) selector(ctx context.Context, sel *ast.SelectorExpr) error {
12361242
c.inference.objChain = objChain(c.pkg.TypesInfo(), sel.X)
12371243

@@ -1283,7 +1289,7 @@ func (c *completer) selector(ctx context.Context, sel *ast.SelectorExpr) error {
12831289
// -- completion of symbols in unimported packages --
12841290

12851291
// use new code for unimported completions, if flag allows it
1286-
if id, ok := sel.X.(*ast.Ident); ok && c.snapshot.Options().ImportsSource == settings.ImportsSourceGopls {
1292+
if c.snapshot.Options().ImportsSource == settings.ImportsSourceGopls {
12871293
// The user might have typed strings.TLower, so id.Name==strings, sel.Sel.Name == TLower,
12881294
// but the cursor might be inside TLower, so adjust the prefix
12891295
prefix := sel.Sel.Name
@@ -2916,9 +2922,7 @@ func objChain(info *types.Info, e ast.Expr) []types.Object {
29162922
}
29172923

29182924
// Reverse order so the layout matches the syntactic order.
2919-
for i := range len(objs) / 2 {
2920-
objs[i], objs[len(objs)-1-i] = objs[len(objs)-1-i], objs[i]
2921-
}
2925+
slices.Reverse(objs)
29222926

29232927
return objs
29242928
}

0 commit comments

Comments
 (0)