Skip to content

Commit d115b34

Browse files
adonovangopherbot
authored andcommitted
gopls/internal/analysis: simplify type-error analyzers with Cursor
This CL makes a number of simplifications to the type-error analyzers: - Nodes are found using Cursor.FindPos from the error position, which is very fast; - Error position information is read from types.Error instead of formatting the ast.File (!) then invoking the dubious heuristics of analysisinternal.TypeErrorEndPos, which scans the text (!!) assuming well-formattedness (!!!). - plus various minor cleanups. - rename typesinternal.ReadGo116ErrorData to ErrorCodeStartEnd. Updates golang/go#65966 Updates golang/go#71803 Change-Id: Ie4561144040b001b957ef6a3c3631328297d5001 Reviewed-on: https://go-review.googlesource.com/c/tools/+/650217 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Jonathan Amsterdam <[email protected]> Auto-Submit: Alan Donovan <[email protected]> Commit-Queue: Alan Donovan <[email protected]>
1 parent c18bffa commit d115b34

File tree

8 files changed

+130
-177
lines changed

8 files changed

+130
-177
lines changed

gopls/internal/analysis/fillreturns/fillreturns.go

+59-88
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ import (
1515
"strings"
1616

1717
"golang.org/x/tools/go/analysis"
18-
"golang.org/x/tools/go/ast/astutil"
18+
"golang.org/x/tools/go/analysis/passes/inspect"
19+
"golang.org/x/tools/go/ast/inspector"
1920
"golang.org/x/tools/gopls/internal/fuzzy"
21+
"golang.org/x/tools/gopls/internal/util/moreiters"
2022
"golang.org/x/tools/internal/analysisinternal"
23+
"golang.org/x/tools/internal/astutil/cursor"
2124
"golang.org/x/tools/internal/typesinternal"
2225
)
2326

@@ -27,105 +30,41 @@ var doc string
2730
var Analyzer = &analysis.Analyzer{
2831
Name: "fillreturns",
2932
Doc: analysisinternal.MustExtractDoc(doc, "fillreturns"),
33+
Requires: []*analysis.Analyzer{inspect.Analyzer},
3034
Run: run,
3135
RunDespiteErrors: true,
3236
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/fillreturns",
3337
}
3438

3539
func run(pass *analysis.Pass) (interface{}, error) {
40+
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
3641
info := pass.TypesInfo
37-
if info == nil {
38-
return nil, fmt.Errorf("nil TypeInfo")
39-
}
4042

4143
outer:
4244
for _, typeErr := range pass.TypeErrors {
43-
// Filter out the errors that are not relevant to this analyzer.
44-
if !FixesError(typeErr) {
45-
continue
46-
}
47-
var file *ast.File
48-
for _, f := range pass.Files {
49-
if f.FileStart <= typeErr.Pos && typeErr.Pos <= f.FileEnd {
50-
file = f
51-
break
52-
}
53-
}
54-
if file == nil {
55-
continue
56-
}
57-
58-
// Get the end position of the error.
59-
// (This heuristic assumes that the buffer is formatted,
60-
// at least up to the end position of the error.)
61-
var buf bytes.Buffer
62-
if err := format.Node(&buf, pass.Fset, file); err != nil {
63-
continue
45+
if !fixesError(typeErr) {
46+
continue // irrelevant type error
6447
}
65-
typeErrEndPos := analysisinternal.TypeErrorEndPos(pass.Fset, buf.Bytes(), typeErr.Pos)
66-
67-
// TODO(rfindley): much of the error handling code below returns, when it
68-
// should probably continue.
69-
70-
// Get the path for the relevant range.
71-
path, _ := astutil.PathEnclosingInterval(file, typeErr.Pos, typeErrEndPos)
72-
if len(path) == 0 {
73-
return nil, nil
74-
}
75-
76-
// Find the enclosing return statement.
77-
var ret *ast.ReturnStmt
78-
var retIdx int
79-
for i, n := range path {
80-
if r, ok := n.(*ast.ReturnStmt); ok {
81-
ret = r
82-
retIdx = i
83-
break
84-
}
48+
_, start, end, ok := typesinternal.ErrorCodeStartEnd(typeErr)
49+
if !ok {
50+
continue // no position information
8551
}
86-
if ret == nil {
87-
return nil, nil
52+
curErr, ok := cursor.Root(inspect).FindPos(start, end)
53+
if !ok {
54+
continue // can't find node
8855
}
8956

90-
// Get the function type that encloses the ReturnStmt.
91-
var enclosingFunc *ast.FuncType
92-
for _, n := range path[retIdx+1:] {
93-
switch node := n.(type) {
94-
case *ast.FuncLit:
95-
enclosingFunc = node.Type
96-
case *ast.FuncDecl:
97-
enclosingFunc = node.Type
98-
}
99-
if enclosingFunc != nil {
100-
break
101-
}
102-
}
103-
if enclosingFunc == nil || enclosingFunc.Results == nil {
104-
continue
105-
}
106-
107-
// Skip any generic enclosing functions, since type parameters don't
108-
// have 0 values.
109-
// TODO(rfindley): We should be able to handle this if the return
110-
// values are all concrete types.
111-
if tparams := enclosingFunc.TypeParams; tparams != nil && tparams.NumFields() > 0 {
112-
return nil, nil
113-
}
114-
115-
// Find the function declaration that encloses the ReturnStmt.
116-
var outer *ast.FuncDecl
117-
for _, p := range path {
118-
if p, ok := p.(*ast.FuncDecl); ok {
119-
outer = p
120-
break
57+
// Find cursor for enclosing return statement (which may be curErr itself).
58+
curRet := curErr
59+
if _, ok := curRet.Node().(*ast.ReturnStmt); !ok {
60+
curRet, ok = moreiters.First(curErr.Ancestors((*ast.ReturnStmt)(nil)))
61+
if !ok {
62+
continue // no enclosing return
12163
}
12264
}
123-
if outer == nil {
124-
return nil, nil
125-
}
65+
ret := curRet.Node().(*ast.ReturnStmt)
12666

127-
// Skip any return statements that contain function calls with multiple
128-
// return values.
67+
// Skip if any return argument is a tuple-valued function call.
12968
for _, expr := range ret.Results {
13069
e, ok := expr.(*ast.CallExpr)
13170
if !ok {
@@ -136,24 +75,47 @@ outer:
13675
}
13776
}
13877

78+
// Get type of innermost enclosing function.
79+
var funcType *ast.FuncType
80+
curFunc, _ := enclosingFunc(curRet) // can't fail
81+
switch fn := curFunc.Node().(type) {
82+
case *ast.FuncLit:
83+
funcType = fn.Type
84+
case *ast.FuncDecl:
85+
funcType = fn.Type
86+
87+
// Skip generic functions since type parameters don't have zero values.
88+
// TODO(rfindley): We should be able to handle this if the return
89+
// values are all concrete types.
90+
if funcType.TypeParams.NumFields() > 0 {
91+
continue
92+
}
93+
}
94+
if funcType.Results == nil {
95+
continue
96+
}
97+
13998
// Duplicate the return values to track which values have been matched.
14099
remaining := make([]ast.Expr, len(ret.Results))
141100
copy(remaining, ret.Results)
142101

143-
fixed := make([]ast.Expr, len(enclosingFunc.Results.List))
102+
fixed := make([]ast.Expr, len(funcType.Results.List))
144103

145104
// For each value in the return function declaration, find the leftmost element
146105
// in the return statement that has the desired type. If no such element exists,
147106
// fill in the missing value with the appropriate "zero" value.
148107
// Beware that type information may be incomplete.
149108
var retTyps []types.Type
150-
for _, ret := range enclosingFunc.Results.List {
109+
for _, ret := range funcType.Results.List {
151110
retTyp := info.TypeOf(ret.Type)
152111
if retTyp == nil {
153112
return nil, nil
154113
}
155114
retTyps = append(retTyps, retTyp)
156115
}
116+
117+
curFile, _ := moreiters.First(curRet.Ancestors((*ast.File)(nil)))
118+
file := curFile.Node().(*ast.File)
157119
matches := analysisinternal.MatchingIdents(retTyps, file, ret.Pos(), info, pass.Pkg)
158120
qual := typesinternal.FileQualifier(file, pass.Pkg)
159121
for i, retTyp := range retTyps {
@@ -215,8 +177,8 @@ outer:
215177
}
216178

217179
pass.Report(analysis.Diagnostic{
218-
Pos: typeErr.Pos,
219-
End: typeErrEndPos,
180+
Pos: start,
181+
End: end,
220182
Message: typeErr.Msg,
221183
SuggestedFixes: []analysis.SuggestedFix{{
222184
Message: "Fill in return values",
@@ -255,7 +217,7 @@ var wrongReturnNumRegexes = []*regexp.Regexp{
255217
regexp.MustCompile(`not enough return values`),
256218
}
257219

258-
func FixesError(err types.Error) bool {
220+
func fixesError(err types.Error) bool {
259221
msg := strings.TrimSpace(err.Msg)
260222
for _, rx := range wrongReturnNumRegexes {
261223
if rx.MatchString(msg) {
@@ -264,3 +226,12 @@ func FixesError(err types.Error) bool {
264226
}
265227
return false
266228
}
229+
230+
// enclosingFunc returns the cursor for the innermost Func{Decl,Lit}
231+
// that encloses c, if any.
232+
func enclosingFunc(c cursor.Cursor) (cursor.Cursor, bool) {
233+
for curAncestor := range c.Ancestors((*ast.FuncDecl)(nil), (*ast.FuncLit)(nil)) {
234+
return curAncestor, true
235+
}
236+
return cursor.Cursor{}, false
237+
}

gopls/internal/analysis/nonewvars/nonewvars.go

+35-46
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,17 @@
77
package nonewvars
88

99
import (
10-
"bytes"
1110
_ "embed"
1211
"go/ast"
13-
"go/format"
1412
"go/token"
1513

1614
"golang.org/x/tools/go/analysis"
1715
"golang.org/x/tools/go/analysis/passes/inspect"
1816
"golang.org/x/tools/go/ast/inspector"
17+
"golang.org/x/tools/gopls/internal/util/moreiters"
1918
"golang.org/x/tools/internal/analysisinternal"
19+
"golang.org/x/tools/internal/astutil/cursor"
20+
"golang.org/x/tools/internal/typesinternal"
2021
)
2122

2223
//go:embed doc.go
@@ -33,57 +34,45 @@ var Analyzer = &analysis.Analyzer{
3334

3435
func run(pass *analysis.Pass) (interface{}, error) {
3536
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
36-
if len(pass.TypeErrors) == 0 {
37-
return nil, nil
38-
}
3937

40-
nodeFilter := []ast.Node{(*ast.AssignStmt)(nil)}
41-
inspect.Preorder(nodeFilter, func(n ast.Node) {
42-
assignStmt, _ := n.(*ast.AssignStmt)
43-
// We only care about ":=".
44-
if assignStmt.Tok != token.DEFINE {
45-
return
38+
for _, typeErr := range pass.TypeErrors {
39+
if typeErr.Msg != "no new variables on left side of :=" {
40+
continue // irrelevant error
41+
}
42+
_, start, end, ok := typesinternal.ErrorCodeStartEnd(typeErr)
43+
if !ok {
44+
continue // can't get position info
45+
}
46+
curErr, ok := cursor.Root(inspect).FindPos(start, end)
47+
if !ok {
48+
continue // can't find errant node
4649
}
4750

48-
var file *ast.File
49-
for _, f := range pass.Files {
50-
if f.FileStart <= assignStmt.Pos() && assignStmt.Pos() < f.FileEnd {
51-
file = f
52-
break
51+
// Find enclosing assignment (which may be curErr itself).
52+
assign, ok := curErr.Node().(*ast.AssignStmt)
53+
if !ok {
54+
cur, ok := moreiters.First(curErr.Ancestors((*ast.AssignStmt)(nil)))
55+
if !ok {
56+
continue // no enclosing assignment
5357
}
58+
assign = cur.Node().(*ast.AssignStmt)
5459
}
55-
if file == nil {
56-
return
60+
if assign.Tok != token.DEFINE {
61+
continue // not a := statement
5762
}
5863

59-
for _, err := range pass.TypeErrors {
60-
if !FixesError(err.Msg) {
61-
continue
62-
}
63-
if assignStmt.Pos() > err.Pos || err.Pos >= assignStmt.End() {
64-
continue
65-
}
66-
var buf bytes.Buffer
67-
if err := format.Node(&buf, pass.Fset, file); err != nil {
68-
continue
69-
}
70-
pass.Report(analysis.Diagnostic{
71-
Pos: err.Pos,
72-
End: analysisinternal.TypeErrorEndPos(pass.Fset, buf.Bytes(), err.Pos),
73-
Message: err.Msg,
74-
SuggestedFixes: []analysis.SuggestedFix{{
75-
Message: "Change ':=' to '='",
76-
TextEdits: []analysis.TextEdit{{
77-
Pos: err.Pos,
78-
End: err.Pos + 1,
79-
}},
64+
pass.Report(analysis.Diagnostic{
65+
Pos: assign.TokPos,
66+
End: assign.TokPos + token.Pos(len(":=")),
67+
Message: typeErr.Msg,
68+
SuggestedFixes: []analysis.SuggestedFix{{
69+
Message: "Change ':=' to '='",
70+
TextEdits: []analysis.TextEdit{{
71+
Pos: assign.TokPos,
72+
End: assign.TokPos + token.Pos(len(":")),
8073
}},
81-
})
82-
}
83-
})
74+
}},
75+
})
76+
}
8477
return nil, nil
8578
}
86-
87-
func FixesError(msg string) bool {
88-
return msg == "no new variables on left side of :="
89-
}

0 commit comments

Comments
 (0)