Skip to content

Commit 09c21a9

Browse files
committed
gopls/internal/analysis/unusedfunc: remove warnings for unused enum consts
Currently, we do not produce "unused" warnings for enums - groups of consts that use iota. This CL modifies the definition of enums in the unusedfunc analyzer to be const groups that all share the same type where at least one of the consts is used. This allows for code that may need to implement an existing protocol with defined consts that aren't consecutive (so can't use iota) but for which we want to avoid producing "unused" warnings. Fixes golang/go#76924 Change-Id: I916e1ef974d106b142609f20fda429e0f74c0bb7 Reviewed-on: https://go-review.googlesource.com/c/tools/+/734180 Reviewed-by: Alan Donovan <adonovan@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent 03cb455 commit 09c21a9

File tree

3 files changed

+123
-38
lines changed

3 files changed

+123
-38
lines changed

gopls/internal/analysis/unusedfunc/testdata/basic.txtar

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,33 @@ func (g[T]) method() {} // want `method "method" is unused`
7171
const unusedConst = 1 // want `const "unusedConst" is unused`
7272

7373
const (
74-
unusedEnum = iota
74+
unusedEnum = iota // want `const "unusedEnum" is unused`
7575
)
7676

77+
const constOne = 1
78+
const unusedConstTwo = constOne // want `const "unusedConstTwo" is unused`
79+
const _, unusedConstThree = 0, 3 // want `const "unusedConstThree" is unused`
80+
const unusedConstFour, _ = 4, 0 // want `const "unusedConstFour" is unused`
81+
const unusedConstFive, UsedConst6 = 4, 6 // want `const "unusedConstFive" is unused`
82+
83+
// This test verifies the fix for golang/go#76924.
84+
// We no longer produce unused variable warnings for a group
85+
// of unused constants if all constants have the same type and
86+
// at least one is used.
87+
7788
const (
78-
constOne = 1
79-
unusedConstTwo = constOne // want `const "unusedConstTwo" is unused`
80-
_, unusedConstThree = 0, 3 // want `const "unusedConstThree" is unused`
81-
unusedConstFour, _ = 4, 0 // want `const "unusedConstFour" is unused`
82-
unusedConstFive, UsedConst6 = 4, 6 // want `const "unusedConstFive" is unused`
89+
a = "a"
90+
b = "b"
91+
c = "c"
92+
)
93+
94+
func _() {
95+
print(a)
96+
}
97+
98+
const ( // want `all values in this set of constants are unused`
99+
d = "d"
100+
e = "e"
83101
)
84102

85103
// -- vars --
@@ -141,15 +159,25 @@ type g[T any] int
141159

142160
// -- constants --
143161

144-
const (
145-
unusedEnum = iota
146-
)
162+
const constOne = 1
163+
164+
const UsedConst6 = 6 // want `const "unusedConstFive" is unused`
165+
166+
// This test verifies the fix for golang/go#76924.
167+
// We no longer produce unused variable warnings for a group
168+
// of unused constants if all constants have the same type and
169+
// at least one is used.
147170

148171
const (
149-
constOne = 1
150-
UsedConst6 = 6 // want `const "unusedConstFive" is unused`
172+
a = "a"
173+
b = "b"
174+
c = "c"
151175
)
152176

177+
func _() {
178+
print(a)
179+
}
180+
153181
// -- vars --
154182

155183
var (

gopls/internal/analysis/unusedfunc/unusedfunc.go

Lines changed: 81 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,11 @@ import (
5454
// for unexported identifiers.)
5555
//
5656
// Types (sans methods), constants, and vars are more straightforward.
57-
// For now we ignore enums (const decls using iota) since it is
58-
// common for at least some values to be unused when they are added
59-
// for symmetry, future use, or to conform to some external pattern.
57+
//
58+
// For enums (defined here as const decls where all consts have the same type),
59+
// we only require that one of the names in the group is used, since it is
60+
// common for at least some values to be unused when they are added for
61+
// symmetry, future use, or to conform to some external pattern.
6062

6163
//go:embed doc.go
6264
var doc string
@@ -97,27 +99,36 @@ func run(pass *analysis.Pass) (any, error) {
9799
}
98100
})
99101

100-
// checkUnused reports a diagnostic if the object declared at id
101-
// is unexported and unused. References within curSelf are ignored.
102-
checkUnused := func(noun string, id *ast.Ident, curSelf inspector.Cursor, delete func() []analysis.TextEdit) {
102+
// used reports whether the object declared at id is (potentially) used.
103+
// References within curSelf are ignored.
104+
used := func(id *ast.Ident, curSelf inspector.Cursor) bool {
103105
// Exported functions may be called from other packages.
104106
if id.IsExported() {
105-
return
107+
return true
106108
}
107109

108110
// Blank functions are exempt from diagnostics.
109111
if id.Name == "_" {
110-
return
112+
return true
111113
}
112114

113115
// Check for uses (including selections).
114116
obj := pass.TypesInfo.Defs[id]
115117
for curId := range index.Uses(obj) {
116118
// Ignore self references.
117119
if !curSelf.Contains(curId) {
118-
return // symbol is referenced
120+
return true // symbol is referenced
119121
}
120122
}
123+
return false
124+
}
125+
126+
// checkUnused reports a diagnostic if the object declared at id
127+
// is unexported and unused. References within curSelf are ignored.
128+
checkUnused := func(noun string, id *ast.Ident, curSelf inspector.Cursor, delete func() []analysis.TextEdit) {
129+
if used(id, curSelf) {
130+
return
131+
}
121132

122133
pass.Report(analysis.Diagnostic{
123134
Pos: id.Pos(),
@@ -130,13 +141,25 @@ func run(pass *analysis.Pass) (any, error) {
130141
})
131142
}
132143

133-
// Gather the set of enums (const GenDecls that use iota).
134-
enums := make(map[inspector.Cursor]bool)
135-
for curId := range index.Uses(types.Universe.Lookup("iota")) {
136-
for curDecl := range curId.Enclosing((*ast.GenDecl)(nil)) {
137-
enums[curDecl] = true
138-
break
144+
// isEnum returns true if the decl curGenDecl is a const decl with more than one
145+
// spec where all consts are the same type.
146+
isEnum := func(curGenDecl inspector.Cursor) bool {
147+
decl := curGenDecl.Node().(*ast.GenDecl)
148+
if decl.Tok != token.CONST || len(decl.Specs) < 2 {
149+
return false
150+
}
151+
var prevType types.Type
152+
for _, spec := range decl.Specs {
153+
spec := spec.(*ast.ValueSpec)
154+
for _, id := range spec.Names {
155+
curType := pass.TypesInfo.TypeOf(id)
156+
if prevType != nil && !types.Identical(curType, prevType) {
157+
return false
158+
}
159+
prevType = curType
160+
}
139161
}
162+
return true
140163
}
141164

142165
// Check each package-level declaration (and method) for uses.
@@ -203,20 +226,51 @@ func run(pass *analysis.Pass) (any, error) {
203226
}
204227

205228
case token.CONST, token.VAR:
206-
// Skip enums: values are often unused.
207-
if enums[curDecl] {
208-
continue
209-
}
210-
for i, spec := range decl.Specs {
211-
spec := spec.(*ast.ValueSpec)
212-
curSpec := curDecl.ChildAt(edge.GenDecl_Specs, i)
213-
214-
for j, id := range spec.Names {
215-
checkUnused(decl.Tok.String(), id, curSpec, func() []analysis.TextEdit {
216-
curId := curSpec.ChildAt(edge.ValueSpec_Names, j)
217-
return refactor.DeleteVar(tokFile, pass.TypesInfo, curId)
229+
if isEnum(curDecl) {
230+
// For enum-like constants, a use of any
231+
// name acts as a use of the whole.
232+
// TODO(mkalil): This results in false negatives, for example:
233+
// const (
234+
// a = 0
235+
// b = a + 1
236+
// )
237+
// We report that a is "used" even though is not used outside the const
238+
// decl, and since we do not report errors in enums that have at least one
239+
// used name, we do not report a diagnostic at all.
240+
allUnused := true
241+
for i, spec := range decl.Specs {
242+
curSpec := curDecl.ChildAt(edge.GenDecl_Specs, i)
243+
for _, id := range spec.(*ast.ValueSpec).Names {
244+
if used(id, curSpec) {
245+
allUnused = false
246+
break
247+
}
248+
}
249+
}
250+
if allUnused {
251+
edits := refactor.DeleteDecl(tokFile, curDecl)
252+
pass.Report(analysis.Diagnostic{
253+
Pos: decl.Pos(),
254+
End: decl.End(),
255+
Message: "all values in this set of constants are unused",
256+
SuggestedFixes: []analysis.SuggestedFix{{
257+
Message: "Delete the constants declaration",
258+
TextEdits: edits,
259+
}},
218260
})
219261
}
262+
} else {
263+
for i, spec := range decl.Specs {
264+
spec := spec.(*ast.ValueSpec)
265+
curSpec := curDecl.ChildAt(edge.GenDecl_Specs, i)
266+
267+
for j, id := range spec.Names {
268+
checkUnused(decl.Tok.String(), id, curSpec, func() []analysis.TextEdit {
269+
curId := curSpec.ChildAt(edge.ValueSpec_Names, j)
270+
return refactor.DeleteVar(tokFile, pass.TypesInfo, curId)
271+
})
272+
}
273+
}
220274
}
221275
}
222276
}

gopls/internal/test/marker/testdata/workspacesymbol/workspacesymbol.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ request.
33

44
TODO(rfindley): add a test for the legacy 'fuzzy' symbol matcher using setting ("symbolMatcher": "fuzzy"). This test uses the default matcher ("fastFuzzy").
55

6+
-- flags --
7+
-ignore_extra_diags
8+
69
-- go.mod --
710
module mod.test/symbols
811

0 commit comments

Comments
 (0)