Skip to content

Commit 470afda

Browse files
committed
gopls/internal/analysis/unusedparams: eliminate false positives
This change is a substantial rewrite of the unusedparams analyzer designed to eliminate essentially all false positives, based on the rule that unless we can see all uses of a function, we can't tell whether it is safe to alter its signature. The new algorithm computes the set of address-taken functions, that is, functions that are referenced other than in the CallExpr.Fun position of a function call. For soundness we must assume that such values may be required to conform to some func type, so we cannot alter their signature. Exported functions and methods must also be treated conservatively, as they may be address-taken in another package. And unexported methods are ignored if they match the name of an interface method declared in the same package, since they may be required to conform to it. Anonymous functions that are immediately assigned to a var are treated like named functions, with the var symbol standing for the function name. Since the false positive rate is now close to zero, this change also enables the analyzer by default in gopls. Number of findings: Repo Before After Comments x/tools 86 26 100% true positives k8s 2457 1987 Only 14% are in non-generated files, and of those, all that I sampled were true positives. Updates golang/go#64753 Change-Id: I35dfc116a97bb5e98a2b098047fce176f2c1a9d6 Reviewed-on: https://go-review.googlesource.com/c/tools/+/550355 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent 592d9e1 commit 470afda

File tree

18 files changed

+418
-164
lines changed

18 files changed

+418
-164
lines changed

gopls/doc/analyzers.md

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -763,15 +763,29 @@ unusedparams: check for unused parameters of functions
763763
The unusedparams analyzer checks functions to see if there are
764764
any parameters that are not being used.
765765

766-
To reduce false positives it ignores:
767-
- methods
768-
- parameters that do not have a name or have the name '_' (the blank identifier)
769-
- functions in test files
770-
- functions with empty bodies or those with just a return stmt
766+
To ensure soundness, it ignores:
767+
- "address-taken" functions, that is, functions that are used as
768+
a value rather than being called directly; their signatures may
769+
be required to conform to a func type.
770+
- exported functions or methods, since they may be address-taken
771+
in another package.
772+
- unexported methods whose name matches an interface method
773+
declared in the same package, since the method's signature
774+
may be required to conform to the interface type.
775+
- functions with empty bodies, or containing just a call to panic.
776+
- parameters that are unnamed, or named "_", the blank identifier.
777+
778+
The analyzer suggests a fix of replacing the parameter name by "_",
779+
but in such cases a deeper fix can be obtained by invoking the
780+
"Refactor: remove unused parameter" code action, which will
781+
eliminate the parameter entirely, along with all corresponding
782+
arguments at call sites, while taking care to preserve any side
783+
effects in the argument expressions; see
784+
https://github.com/golang/tools/releases/tag/gopls%2Fv0.14.
771785

772786
[Full documentation](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedparams)
773787

774-
**Disabled by default. Enable it by setting `"analyses": {"unusedparams": true}`.**
788+
**Enabled by default.**
775789

776790
## **unusedresult**
777791

gopls/doc/settings.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ Example Usage:
286286
...
287287
"analyses": {
288288
"unreachable": false, // Disable the unreachable analyzer.
289-
"unusedparams": true // Enable the unusedparams analyzer.
289+
"unusedvariable": true // Enable the unusedvariable analyzer.
290290
}
291291
...
292292
```

gopls/internal/analysis/unusedparams/cmd/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
// The stringintconv command runs the stringintconv analyzer.
5+
// The unusedparams command runs the unusedparams analyzer.
66
package main
77

88
import (

gopls/internal/analysis/unusedparams/doc.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,23 @@
1212
// The unusedparams analyzer checks functions to see if there are
1313
// any parameters that are not being used.
1414
//
15-
// To reduce false positives it ignores:
16-
// - methods
17-
// - parameters that do not have a name or have the name '_' (the blank identifier)
18-
// - functions in test files
19-
// - functions with empty bodies or those with just a return stmt
15+
// To ensure soundness, it ignores:
16+
// - "address-taken" functions, that is, functions that are used as
17+
// a value rather than being called directly; their signatures may
18+
// be required to conform to a func type.
19+
// - exported functions or methods, since they may be address-taken
20+
// in another package.
21+
// - unexported methods whose name matches an interface method
22+
// declared in the same package, since the method's signature
23+
// may be required to conform to the interface type.
24+
// - functions with empty bodies, or containing just a call to panic.
25+
// - parameters that are unnamed, or named "_", the blank identifier.
26+
//
27+
// The analyzer suggests a fix of replacing the parameter name by "_",
28+
// but in such cases a deeper fix can be obtained by invoking the
29+
// "Refactor: remove unused parameter" code action, which will
30+
// eliminate the parameter entirely, along with all corresponding
31+
// arguments at call sites, while taking care to preserve any side
32+
// effects in the argument expressions; see
33+
// https://github.com/golang/tools/releases/tag/gopls%2Fv0.14.
2034
package unusedparams

gopls/internal/analysis/unusedparams/testdata/src/a/a.go

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,32 +24,64 @@ func (y *yuh) n(f bool) {
2424
}
2525
}
2626

27-
func a(i1 int, i2 int, i3 int) int { // want "potentially unused parameter: 'i2'"
27+
func a(i1 int, i2 int, i3 int) int { // want "unused parameter: i2"
2828
i3 += i1
29-
_ = func(z int) int { // want "potentially unused parameter: 'z'"
29+
_ = func(z int) int { // want "unused parameter: z"
3030
_ = 1
3131
return 1
3232
}
3333
return i3
3434
}
3535

36-
func b(c bytes.Buffer) { // want "potentially unused parameter: 'c'"
36+
func b(c bytes.Buffer) { // want "unused parameter: c"
3737
_ = 1
3838
}
3939

40-
func z(h http.ResponseWriter, _ *http.Request) { // want "potentially unused parameter: 'h'"
40+
func z(h http.ResponseWriter, _ *http.Request) { // no report: func z is address-taken
4141
fmt.Println("Before")
4242
}
4343

44-
func l(h http.Handler) http.Handler {
44+
func l(h http.Handler) http.Handler { // want "unused parameter: h"
4545
return http.HandlerFunc(z)
4646
}
4747

48-
func mult(a, b int) int { // want "potentially unused parameter: 'b'"
48+
func mult(a, b int) int { // want "unused parameter: b"
4949
a += 1
5050
return a
5151
}
5252

5353
func y(a int) {
5454
panic("yo")
5555
}
56+
57+
var _ = func(x int) {} // empty body: no diagnostic
58+
59+
var _ = func(x int) { println() } // want "unused parameter: x"
60+
61+
var (
62+
calledGlobal = func(x int) { println() } // want "unused parameter: x"
63+
addressTakenGlobal = func(x int) { println() } // no report: function is address-taken
64+
)
65+
66+
func _() {
67+
calledGlobal(1)
68+
println(addressTakenGlobal)
69+
}
70+
71+
func Exported(unused int) {} // no finding: an exported function may be address-taken
72+
73+
type T int
74+
75+
func (T) m(f bool) { println() } // want "unused parameter: f"
76+
func (T) n(f bool) { println() } // no finding: n may match the interface method parent.n
77+
78+
func _() {
79+
var fib func(x, y int) int
80+
fib = func(x, y int) int { // want "unused parameter: y"
81+
if x < 2 {
82+
return x
83+
}
84+
return fib(x-1, 123) + fib(x-2, 456)
85+
}
86+
fib(10, 42)
87+
}

gopls/internal/analysis/unusedparams/testdata/src/a/a.go.golden

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,32 +24,64 @@ func (y *yuh) n(f bool) {
2424
}
2525
}
2626

27-
func a(i1 int, _ int, i3 int) int { // want "potentially unused parameter: 'i2'"
27+
func a(i1 int, _ int, i3 int) int { // want "unused parameter: i2"
2828
i3 += i1
29-
_ = func(_ int) int { // want "potentially unused parameter: 'z'"
29+
_ = func(_ int) int { // want "unused parameter: z"
3030
_ = 1
3131
return 1
3232
}
3333
return i3
3434
}
3535

36-
func b(_ bytes.Buffer) { // want "potentially unused parameter: 'c'"
36+
func b(_ bytes.Buffer) { // want "unused parameter: c"
3737
_ = 1
3838
}
3939

40-
func z(_ http.ResponseWriter, _ *http.Request) { // want "potentially unused parameter: 'h'"
40+
func z(h http.ResponseWriter, _ *http.Request) { // no report: func z is address-taken
4141
fmt.Println("Before")
4242
}
4343

44-
func l(h http.Handler) http.Handler {
44+
func l(_ http.Handler) http.Handler { // want "unused parameter: h"
4545
return http.HandlerFunc(z)
4646
}
4747

48-
func mult(a, _ int) int { // want "potentially unused parameter: 'b'"
48+
func mult(a, _ int) int { // want "unused parameter: b"
4949
a += 1
5050
return a
5151
}
5252

5353
func y(a int) {
5454
panic("yo")
5555
}
56+
57+
var _ = func(x int) {} // empty body: no diagnostic
58+
59+
var _ = func(_ int) { println() } // want "unused parameter: x"
60+
61+
var (
62+
calledGlobal = func(_ int) { println() } // want "unused parameter: x"
63+
addressTakenGlobal = func(x int) { println() } // no report: function is address-taken
64+
)
65+
66+
func _() {
67+
calledGlobal(1)
68+
println(addressTakenGlobal)
69+
}
70+
71+
func Exported(unused int) {} // no finding: an exported function may be address-taken
72+
73+
type T int
74+
75+
func (T) m(_ bool) { println() } // want "unused parameter: f"
76+
func (T) n(f bool) { println() } // no finding: n may match the interface method parent.n
77+
78+
func _() {
79+
var fib func(x, y int) int
80+
fib = func(x, _ int) int { // want "unused parameter: y"
81+
if x < 2 {
82+
return x
83+
}
84+
return fib(x-1, 123) + fib(x-2, 456)
85+
}
86+
fib(10, 42)
87+
}

gopls/internal/analysis/unusedparams/testdata/src/typeparams/typeparams.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,28 @@ func (y *yuh[int]) n(f bool) {
2424
}
2525
}
2626

27-
func a[T comparable](i1 int, i2 T, i3 int) int { // want "potentially unused parameter: 'i2'"
27+
func a[T comparable](i1 int, i2 T, i3 int) int { // want "unused parameter: i2"
2828
i3 += i1
29-
_ = func(z int) int { // want "potentially unused parameter: 'z'"
29+
_ = func(z int) int { // want "unused parameter: z"
3030
_ = 1
3131
return 1
3232
}
3333
return i3
3434
}
3535

36-
func b[T any](c bytes.Buffer) { // want "potentially unused parameter: 'c'"
36+
func b[T any](c bytes.Buffer) { // want "unused parameter: c"
3737
_ = 1
3838
}
3939

40-
func z[T http.ResponseWriter](h T, _ *http.Request) { // want "potentially unused parameter: 'h'"
40+
func z[T http.ResponseWriter](h T, _ *http.Request) { // no report: func z is address-taken
4141
fmt.Println("Before")
4242
}
4343

44-
func l(h http.Handler) http.Handler {
44+
func l(h http.Handler) http.Handler { // want "unused parameter: h"
4545
return http.HandlerFunc(z[http.ResponseWriter])
4646
}
4747

48-
func mult(a, b int) int { // want "potentially unused parameter: 'b'"
48+
func mult(a, b int) int { // want "unused parameter: b"
4949
a += 1
5050
return a
5151
}

gopls/internal/analysis/unusedparams/testdata/src/typeparams/typeparams.go.golden

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,28 @@ func (y *yuh[int]) n(f bool) {
2424
}
2525
}
2626

27-
func a[T comparable](i1 int, _ T, i3 int) int { // want "potentially unused parameter: 'i2'"
27+
func a[T comparable](i1 int, _ T, i3 int) int { // want "unused parameter: i2"
2828
i3 += i1
29-
_ = func(_ int) int { // want "potentially unused parameter: 'z'"
29+
_ = func(_ int) int { // want "unused parameter: z"
3030
_ = 1
3131
return 1
3232
}
3333
return i3
3434
}
3535

36-
func b[T any](_ bytes.Buffer) { // want "potentially unused parameter: 'c'"
36+
func b[T any](_ bytes.Buffer) { // want "unused parameter: c"
3737
_ = 1
3838
}
3939

40-
func z[T http.ResponseWriter](_ T, _ *http.Request) { // want "potentially unused parameter: 'h'"
40+
func z[T http.ResponseWriter](h T, _ *http.Request) { // no report: func z is address-taken
4141
fmt.Println("Before")
4242
}
4343

44-
func l(h http.Handler) http.Handler {
44+
func l(_ http.Handler) http.Handler { // want "unused parameter: h"
4545
return http.HandlerFunc(z[http.ResponseWriter])
4646
}
4747

48-
func mult(a, _ int) int { // want "potentially unused parameter: 'b'"
48+
func mult(a, _ int) int { // want "unused parameter: b"
4949
a += 1
5050
return a
5151
}

0 commit comments

Comments
 (0)