Skip to content

Commit fe883a8

Browse files
adonovangopherbot
authored andcommitted
gopls/internal/analysis/unusedvariable: refine bug.Report golang/go#71812
This CL adds assertions to refine the bug reported in golang/go#71812, in which the analyzer reports an invalid SuggestedFix. Updates golang/go#71812 Change-Id: Ie4a9aac9ba3d16974320d7cd4b48bc4cc76fc3c4 Reviewed-on: https://go-review.googlesource.com/c/tools/+/650395 Commit-Queue: Alan Donovan <[email protected]> Reviewed-by: Jonathan Amsterdam <[email protected]> Auto-Submit: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent d115b34 commit fe883a8

File tree

1 file changed

+45
-36
lines changed

1 file changed

+45
-36
lines changed

gopls/internal/analysis/unusedvariable/unusedvariable.go

+45-36
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ import (
1313
"go/token"
1414
"go/types"
1515
"regexp"
16+
"slices"
1617
"strings"
1718

1819
"golang.org/x/tools/go/analysis"
1920
"golang.org/x/tools/go/ast/astutil"
21+
"golang.org/x/tools/gopls/internal/util/bug"
2022
"golang.org/x/tools/gopls/internal/util/safetoken"
2123
)
2224

@@ -165,16 +167,13 @@ func removeVariableFromSpec(pass *analysis.Pass, path []ast.Node, stmt *ast.Valu
165167
// Find parent DeclStmt and delete it
166168
for _, node := range path {
167169
if declStmt, ok := node.(*ast.DeclStmt); ok {
168-
edits := deleteStmtFromBlock(pass.Fset, path, declStmt)
169-
if len(edits) == 0 {
170-
return nil // can this happen?
171-
}
172-
return []analysis.SuggestedFix{
173-
{
170+
if edits := deleteStmtFromBlock(pass.Fset, path, declStmt); len(edits) > 0 {
171+
return []analysis.SuggestedFix{{
174172
Message: suggestedFixMessage(ident.Name),
175173
TextEdits: edits,
176-
},
174+
}}
177175
}
176+
return nil
178177
}
179178
}
180179
}
@@ -222,16 +221,13 @@ func removeVariableFromAssignment(fset *token.FileSet, path []ast.Node, stmt *as
222221
}
223222

224223
// RHS does not have any side effects, delete the whole statement
225-
edits := deleteStmtFromBlock(fset, path, stmt)
226-
if len(edits) == 0 {
227-
return nil // can this happen?
228-
}
229-
return []analysis.SuggestedFix{
230-
{
224+
if edits := deleteStmtFromBlock(fset, path, stmt); len(edits) > 0 {
225+
return []analysis.SuggestedFix{{
231226
Message: suggestedFixMessage(ident.Name),
232227
TextEdits: edits,
233-
},
228+
}}
234229
}
230+
return nil
235231
}
236232

237233
// Otherwise replace ident with `_`
@@ -253,34 +249,48 @@ func suggestedFixMessage(name string) string {
253249
return fmt.Sprintf("Remove variable %s", name)
254250
}
255251

252+
// deleteStmtFromBlock returns the edits to remove stmt if its parent is a BlockStmt.
253+
// (stmt is not necessarily the leaf, path[0].)
254+
//
255+
// It returns nil if the parent is not a block, as in these examples:
256+
//
257+
// switch STMT; {}
258+
// switch { default: STMT }
259+
// select { default: STMT }
260+
//
261+
// TODO(adonovan): handle these cases too.
256262
func deleteStmtFromBlock(fset *token.FileSet, path []ast.Node, stmt ast.Stmt) []analysis.TextEdit {
257-
// Find innermost enclosing BlockStmt.
258-
var block *ast.BlockStmt
259-
for i := range path {
260-
if blockStmt, ok := path[i].(*ast.BlockStmt); ok {
261-
block = blockStmt
262-
break
263-
}
263+
// TODO(adonovan): simplify using Cursor API.
264+
i := slices.Index(path, ast.Node(stmt)) // must be present
265+
block, ok := path[i+1].(*ast.BlockStmt)
266+
if !ok {
267+
return nil // parent is not a BlockStmt
264268
}
265269

266-
nodeIndex := -1
267-
for i, blockStmt := range block.List {
268-
if blockStmt == stmt {
269-
nodeIndex = i
270-
break
271-
}
270+
nodeIndex := slices.Index(block.List, stmt)
271+
if nodeIndex == -1 {
272+
bug.Reportf("%s: Stmt not found in BlockStmt.List", safetoken.StartPosition(fset, stmt.Pos())) // refine #71812
273+
return nil
272274
}
273275

274-
// The statement we need to delete was not found in BlockStmt
275-
if nodeIndex == -1 {
276+
if !stmt.Pos().IsValid() {
277+
bug.Reportf("%s: invalid Stmt.Pos", safetoken.StartPosition(fset, stmt.Pos())) // refine #71812
276278
return nil
277279
}
278280

279281
// Delete until the end of the block unless there is another statement after
280282
// the one we are trying to delete
281283
end := block.Rbrace
284+
if !end.IsValid() {
285+
bug.Reportf("%s: BlockStmt has no Rbrace", safetoken.StartPosition(fset, block.Pos())) // refine #71812
286+
return nil
287+
}
282288
if nodeIndex < len(block.List)-1 {
283289
end = block.List[nodeIndex+1].Pos()
290+
if end < stmt.Pos() {
291+
bug.Reportf("%s: BlockStmt.List[last].Pos > BlockStmt.Rbrace", safetoken.StartPosition(fset, block.Pos())) // refine #71812
292+
return nil
293+
}
284294
}
285295

286296
// Account for comments within the block containing the statement
@@ -298,7 +308,7 @@ outer:
298308
// If a comment exists within the current block, after the unused variable statement,
299309
// and before the next statement, we shouldn't delete it.
300310
if coLine > stmtEndLine {
301-
end = co.Pos()
311+
end = co.Pos() // preserves invariant stmt.Pos <= end (#71812)
302312
break outer
303313
}
304314
if co.Pos() > end {
@@ -308,12 +318,11 @@ outer:
308318
}
309319
}
310320

311-
return []analysis.TextEdit{
312-
{
313-
Pos: stmt.Pos(),
314-
End: end,
315-
},
316-
}
321+
// Delete statement and optional following comment.
322+
return []analysis.TextEdit{{
323+
Pos: stmt.Pos(),
324+
End: end,
325+
}}
317326
}
318327

319328
// exprMayHaveSideEffects reports whether the expression may have side effects

0 commit comments

Comments
 (0)