Unused parameters in Go code
27 June 2018
Daniel MartÃ
Daniel MartÃ
Sometimes parameters were never needed, or are no longer needed.
func NewTemplate(name, body, footer string) Template {
return Template{name: name, body: body}
}--
Sometimes a parameter should be used but isn't.
func (t Template) Execute(w io.Writer) error {
_, err := fmt.Fprintf(os.Stdout, "%s %s", t.name, t.body)
return err
}x/tools/go/loader to load the packages and Go filesgo/ast to inspect the funcsEnter our first tricky case:
func NewTemplate(name, body string) Template {
body = sampleBody
return Template{name: name, body: body}
}
go/ast is just the syntax, it has no semantics.
First algorithm in pseudo-Go:
for _, fn := range allFuncs {
for _, param := range fn.Params {
used := false
for _, node := range fn.NodesRead() {
if types.Is(node, param) {
used = true
break
}
}
if !used {
warnf("unused param: %v", param)
}
}
}fn.NodesRead is extremely complexEnter our first tricky case, again:
func NewTemplate(name, body string) Template {
body = sampleBody // not a use of the body variable!
return Template{name: name, body: body}
}Too many ways to define a variable:
var (t Template)
t.body = someValue
t := Template{}
t.body = someValue
t := Template{body: someValue}New algorithm in pseudo-Go:
for _, param := range fn.Params {
if len(param.Referrers()) == 0 {
warnf("unused param: %v", param)
}
}Because of SSA, our first tricky case becomes something like:
func NewTemplate(name, body string) Template {
body2 := sampleBody
return Template{name: name, body: body2}
}Dummy or stub functions:
func (d devNull) WriteString(s string) error {
return nil
}
func (s storeImpl) CreateFoo(f Foo) error {
return fmt.Errorf("unimplemented")
}Public API with compatibility guarantees:
func NewClient(ctx context.Context, timeout time.Duration) Client {
// no longer using timeout, use context.WithTimeout instead
return Client{ctx: ctx, ...}
}Funcs that satisfy an interface or func signature:
func pingHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong"))
}
--
os.Expand(s, func(name string) string {
return "SameValue"
})Variance in time cost and accuracy:
A linter must be conservative and reasonably fast, so let's choose CHA.
9
anyStrongCall will inspect if any of the calls to fn "locks in" its signature.
if anyStrongCall(graph.Inbound[fn]) {
continue
}
for _, param := range fn.Params {
if len(param.Referrers()) == 0 {
warnf("unused param: %v", param)
}
}Examples of these calls:
// any func passed as handler cannot change signature
func HandleRequest(handler Handler, req Request) {
handler(req)
}
// we'd need to change otherFn too, which may not be possible
fn(otherFn())go/ssa to inspect each func's codego/callgraph to find how each func is usedYou can try the end result:
go get -u mvdan.cc/unparam
Same idea, just with more code to handle endless edge cases.
11