Skip to content

Commit bf9874e

Browse files
committed
add missing column
1 parent 9387a39 commit bf9874e

File tree

4 files changed

+218
-62
lines changed

4 files changed

+218
-62
lines changed

Diff for: gopls/doc/features/diagnostics.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -213,12 +213,13 @@ func main() {
213213
}
214214
```
215215

216-
The quick fix would be:
216+
The quick fix would insert a declration with a default
217+
value inferring its type from the context:
217218

218219
```go
219220
func main() {
220221
x := 42
221-
y :=
222+
y := 0
222223
min(x, y)
223224
}
224225
```

Diff for: gopls/internal/golang/undeclared.go

+107-18
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import (
1616

1717
"golang.org/x/tools/go/analysis"
1818
"golang.org/x/tools/go/ast/astutil"
19-
"golang.org/x/tools/gopls/internal/util/safetoken"
2019
"golang.org/x/tools/gopls/internal/util/typesutil"
2120
"golang.org/x/tools/internal/analysisinternal"
2221
)
@@ -86,33 +85,123 @@ func CreateUndeclared(fset *token.FileSet, start, end token.Pos, content []byte,
8685
if isCallPosition(path) {
8786
return newFunctionDeclaration(path, file, pkg, info, fset)
8887
}
88+
var (
89+
firstOccur *ast.Ident // We should insert the new declaration before the first occurrence of the undefined ident.
90+
assignTokPos token.Pos
91+
funcDecl = path[len(path)-2].(*ast.FuncDecl) // This is already ensured by [undeclaredFixTitle].
92+
parent = ast.Node(funcDecl)
93+
canAddColon = false
94+
)
95+
// Search from enclosing FuncDecl to path[0], since we can not use := syntax outside function.
96+
// Adds the missing colon after the first undefined symbol
97+
// when it sits in lhs of an AssignStmt.
98+
ast.Inspect(funcDecl, func(n ast.Node) bool {
99+
if n == nil || firstOccur != nil {
100+
return false
101+
}
102+
if n, ok := n.(*ast.Ident); ok && n.Name == ident.Name && info.ObjectOf(n) == nil {
103+
if firstOccur == nil {
104+
firstOccur = n
105+
// Only consider adding colon at the first occurrence.
106+
if pos, ok := assignTokPosition(info, n, parent); ok {
107+
assignTokPos = pos
108+
canAddColon = true
109+
return false
110+
}
111+
}
112+
}
113+
parent = n
114+
return true
115+
})
116+
if canAddColon {
117+
return fset, &analysis.SuggestedFix{
118+
TextEdits: []analysis.TextEdit{{
119+
Pos: assignTokPos,
120+
End: assignTokPos,
121+
NewText: []byte(":"),
122+
}},
123+
}, nil
124+
}
89125

90-
// Get the place to insert the new statement.
91-
insertBeforeStmt := analysisinternal.StmtToInsertVarBefore(path)
126+
// FirstOccur should never be nil, at least one ident at cursor position should be found,
127+
// but be defensive.
128+
if firstOccur == nil {
129+
return nil, nil, fmt.Errorf("no identifier found")
130+
}
131+
p, _ := astutil.PathEnclosingInterval(file, firstOccur.Pos(), firstOccur.Pos())
132+
insertBeforeStmt := analysisinternal.StmtToInsertVarBefore(p)
92133
if insertBeforeStmt == nil {
93134
return nil, nil, fmt.Errorf("could not locate insertion point")
94135
}
95-
96-
insertBefore := safetoken.StartPosition(fset, insertBeforeStmt.Pos()).Offset
97-
98-
// Get the indent to add on the line after the new statement.
99-
// Since this will have a parse error, we can not use format.Source().
100-
contentBeforeStmt, indent := content[:insertBefore], "\n"
101-
if nl := bytes.LastIndex(contentBeforeStmt, []byte("\n")); nl != -1 {
102-
indent = string(contentBeforeStmt[nl:])
136+
indent, err := calculateIndentation(content, fset.File(file.FileStart), insertBeforeStmt)
137+
if err != nil {
138+
return nil, nil, err
139+
}
140+
typs := typesutil.TypesFromContext(info, path, start)
141+
if typs == nil {
142+
// Defualt to 0.
143+
typs = []types.Type{types.Typ[types.Int]}
144+
}
145+
assignStmt := &ast.AssignStmt{
146+
Lhs: []ast.Expr{ast.NewIdent(ident.Name)},
147+
Tok: token.DEFINE,
148+
Rhs: []ast.Expr{analysisinternal.ZeroValue(file, pkg, typs[0])},
149+
}
150+
var buf bytes.Buffer
151+
if err := format.Node(&buf, fset, assignStmt); err != nil {
152+
return nil, nil, err
103153
}
154+
newLineIndent := "\n" + indent
155+
assignment := strings.ReplaceAll(buf.String(), "\n", newLineIndent) + newLineIndent
104156

105-
// Create the new local variable statement.
106-
newStmt := fmt.Sprintf("%s := %s", ident.Name, indent)
107157
return fset, &analysis.SuggestedFix{
108-
TextEdits: []analysis.TextEdit{{
109-
Pos: insertBeforeStmt.Pos(),
110-
End: insertBeforeStmt.Pos(),
111-
NewText: []byte(newStmt),
112-
}},
158+
TextEdits: []analysis.TextEdit{
159+
{
160+
Pos: insertBeforeStmt.Pos(),
161+
End: insertBeforeStmt.Pos(),
162+
NewText: []byte(assignment),
163+
},
164+
},
113165
}, nil
114166
}
115167

168+
// assignTokPosition returns position of token.ASSIGN if ident meets the following conditions:
169+
// 1) parent node must be an *ast.AssignStmt with Tok set to token.ASSIGN.
170+
// 2) ident must not be self assignment.
171+
//
172+
// For example, we should not add a colon when
173+
// a = a + 1
174+
// ^ ^ cursor here
175+
func assignTokPosition(info *types.Info, ident *ast.Ident, parent ast.Node) (token.Pos, bool) {
176+
var pos token.Pos
177+
if assign, ok := parent.(*ast.AssignStmt); ok && assign.Tok == token.ASSIGN {
178+
for _, rhs := range assign.Rhs {
179+
if referencesIdent(info, rhs, ident) {
180+
return pos, false
181+
}
182+
}
183+
pos = assign.TokPos
184+
return pos, true
185+
}
186+
return pos, false
187+
}
188+
189+
// referencesIdent checks whether the given undefined ident appears in the given expression.
190+
func referencesIdent(info *types.Info, expr ast.Expr, ident *ast.Ident) bool {
191+
var hasIdent bool
192+
ast.Inspect(expr, func(n ast.Node) bool {
193+
if n == nil {
194+
return false
195+
}
196+
if i, ok := n.(*ast.Ident); ok && i.Name == ident.Name && info.ObjectOf(i) == nil {
197+
hasIdent = true
198+
return false
199+
}
200+
return true
201+
})
202+
return hasIdent
203+
}
204+
116205
func newFunctionDeclaration(path []ast.Node, file *ast.File, pkg *types.Package, info *types.Info, fset *token.FileSet) (*token.FileSet, *analysis.SuggestedFix, error) {
117206
if len(path) < 3 {
118207
return nil, nil, fmt.Errorf("unexpected set of enclosing nodes: %v", path)

Diff for: gopls/internal/test/marker/testdata/quickfix/undeclared.txt

-42
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
Tests of suggested fixes for "undeclared name" diagnostics,
2+
which are of ("compiler", "error") type.
3+
4+
-- flags --
5+
-ignore_extra_diags
6+
7+
-- go.mod --
8+
module example.com
9+
go 1.12
10+
11+
-- a.go --
12+
package undeclared_var
13+
14+
func a() {
15+
z, _ := 1+y, 11 //@quickfix("y", re"(undeclared name|undefined): y", a)
16+
_ = z
17+
}
18+
19+
-- @a/a.go --
20+
@@ -4 +4 @@
21+
+ y := 0
22+
-- b.go --
23+
package undeclared_var
24+
25+
func b() {
26+
if 100 < 90 {
27+
} else if 100 > n+2 { //@quickfix("n", re"(undeclared name|undefined): n", b)
28+
}
29+
}
30+
31+
-- @b/b.go --
32+
@@ -4 +4 @@
33+
+ n := 0
34+
-- c.go --
35+
package undeclared_var
36+
37+
func c() {
38+
for i < 200 { //@quickfix("i", re"(undeclared name|undefined): i", c)
39+
}
40+
r() //@diag("r", re"(undeclared name|undefined): r")
41+
}
42+
43+
-- @c/c.go --
44+
@@ -4 +4 @@
45+
+ i := 0
46+
-- add_colon.go --
47+
package undeclared_var
48+
49+
func addColon() {
50+
ac = 1 //@quickfix("ac", re"(undeclared name|undefined): ac", add_colon)
51+
}
52+
53+
-- @add_colon/add_colon.go --
54+
@@ -4 +4 @@
55+
- ac = 1 //@quickfix("ac", re"(undeclared name|undefined): ac", add_colon)
56+
+ ac := 1 //@quickfix("ac", re"(undeclared name|undefined): ac", add_colon)
57+
-- add_colon_first.go --
58+
package undeclared_var
59+
60+
func addColonAtFirstStmt() {
61+
ac = 1
62+
ac = 2
63+
ac = 3
64+
b := ac //@quickfix("ac", re"(undeclared name|undefined): ac", add_colon_first)
65+
}
66+
67+
-- @add_colon_first/add_colon_first.go --
68+
@@ -4 +4 @@
69+
- ac = 1
70+
+ ac := 1
71+
-- self_assign.go --
72+
package undeclared_var
73+
74+
func selfAssign() {
75+
ac = ac + 1
76+
ac = ac + 2 //@quickfix("ac", re"(undeclared name|undefined): ac", lhs)
77+
ac = ac + 3 //@quickfix("ac + 3", re"(undeclared name|undefined): ac", rhs)
78+
}
79+
80+
-- @lhs/self_assign.go --
81+
@@ -4 +4 @@
82+
+ ac := nil
83+
-- @rhs/self_assign.go --
84+
@@ -4 +4 @@
85+
+ ac := 0
86+
-- correct_type.go --
87+
package undeclared_var
88+
import "fmt"
89+
func selfAssign() {
90+
fmt.Printf(ac) //@quickfix("ac", re"(undeclared name|undefined): ac", string)
91+
}
92+
-- @string/correct_type.go --
93+
@@ -4 +4 @@
94+
+ ac := ""
95+
-- ignore.go --
96+
package undeclared_var
97+
import "fmt"
98+
type Foo struct {
99+
bar int
100+
}
101+
func selfAssign() {
102+
f := Foo{}
103+
b = f.bar
104+
c := bar //@quickfix("bar", re"(undeclared name|undefined): bar", ignore)
105+
}
106+
-- @ignore/ignore.go --
107+
@@ -9 +9 @@
108+
+ bar := nil

0 commit comments

Comments
 (0)