golang.org/x/tools/gopls@v0.15.3/internal/analysis/unusedparams/unusedparams.go (about) 1 // Copyright 2020 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package unusedparams 6 7 import ( 8 _ "embed" 9 "fmt" 10 "go/ast" 11 "go/types" 12 13 "golang.org/x/tools/go/analysis" 14 "golang.org/x/tools/go/analysis/passes/inspect" 15 "golang.org/x/tools/go/ast/inspector" 16 "golang.org/x/tools/gopls/internal/util/slices" 17 "golang.org/x/tools/internal/analysisinternal" 18 ) 19 20 //go:embed doc.go 21 var doc string 22 23 var Analyzer = &analysis.Analyzer{ 24 Name: "unusedparams", 25 Doc: analysisinternal.MustExtractDoc(doc, "unusedparams"), 26 Requires: []*analysis.Analyzer{inspect.Analyzer}, 27 Run: run, 28 URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedparams", 29 } 30 31 const FixCategory = "unusedparam" // recognized by gopls ApplyFix 32 33 func run(pass *analysis.Pass) (any, error) { 34 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 35 36 // First find all "address-taken" functions. 37 // We must conservatively assume that their parameters 38 // are all required to conform to some signature. 39 // 40 // A named function is address-taken if it is somewhere 41 // used not in call position: 42 // 43 // f(...) // not address-taken 44 // use(f) // address-taken 45 // 46 // A literal function is address-taken if it is not 47 // immediately bound to a variable, or if that variable is 48 // used not in call position: 49 // 50 // f := func() { ... }; f() used only in call position 51 // var f func(); f = func() { ...f()... }; f() ditto 52 // use(func() { ... }) address-taken 53 // 54 55 // Note: this algorithm relies on the assumption that the 56 // analyzer is called only for the "widest" package for a 57 // given file: that is, p_test in preference to p, if both 58 // exist. Analyzing only package p may produce diagnostics 59 // that would be falsified based on declarations in p_test.go 60 // files. The gopls analysis driver does this, but most 61 // drivers to not, so running this command in, say, 62 // unitchecker or multichecker may produce incorrect results. 63 64 // Gather global information: 65 // - uses of functions not in call position 66 // - unexported interface methods 67 // - all referenced variables 68 69 usesOutsideCall := make(map[types.Object][]*ast.Ident) 70 unexportedIMethodNames := make(map[string]bool) 71 { 72 callPosn := make(map[*ast.Ident]bool) // all idents f appearing in f() calls 73 filter := []ast.Node{ 74 (*ast.CallExpr)(nil), 75 (*ast.InterfaceType)(nil), 76 } 77 inspect.Preorder(filter, func(n ast.Node) { 78 switch n := n.(type) { 79 case *ast.CallExpr: 80 // Strip off any generic instantiation. 81 fun := n.Fun 82 switch fun_ := fun.(type) { 83 case *ast.IndexExpr: 84 fun = fun_.X // f[T]() (funcs[i]() is rejected below) 85 case *ast.IndexListExpr: 86 fun = fun_.X // f[K, V]() 87 } 88 89 // Find object: 90 // record non-exported function, method, or func-typed var. 91 var id *ast.Ident 92 switch fun := fun.(type) { 93 case *ast.Ident: 94 id = fun 95 case *ast.SelectorExpr: 96 id = fun.Sel 97 } 98 if id != nil && !id.IsExported() { 99 switch pass.TypesInfo.Uses[id].(type) { 100 case *types.Func, *types.Var: 101 callPosn[id] = true 102 } 103 } 104 105 case *ast.InterfaceType: 106 // Record the set of names of unexported interface methods. 107 // (It would be more precise to record signatures but 108 // generics makes it tricky, and this conservative 109 // heuristic is close enough.) 110 t := pass.TypesInfo.TypeOf(n).(*types.Interface) 111 for i := 0; i < t.NumExplicitMethods(); i++ { 112 m := t.ExplicitMethod(i) 113 if !m.Exported() && m.Name() != "_" { 114 unexportedIMethodNames[m.Name()] = true 115 } 116 } 117 } 118 }) 119 120 for id, obj := range pass.TypesInfo.Uses { 121 if !callPosn[id] { 122 // This includes "f = func() {...}", which we deal with below. 123 usesOutsideCall[obj] = append(usesOutsideCall[obj], id) 124 } 125 } 126 } 127 128 // Find all vars (notably parameters) that are used. 129 usedVars := make(map[*types.Var]bool) 130 for _, obj := range pass.TypesInfo.Uses { 131 if v, ok := obj.(*types.Var); ok { 132 if v.IsField() { 133 continue // no point gathering these 134 } 135 usedVars[v] = true 136 } 137 } 138 139 // Check each non-address-taken function's parameters are all used. 140 filter := []ast.Node{ 141 (*ast.FuncDecl)(nil), 142 (*ast.FuncLit)(nil), 143 } 144 inspect.WithStack(filter, func(n ast.Node, push bool, stack []ast.Node) bool { 145 // (We always return true so that we visit nested FuncLits.) 146 147 if !push { 148 return true 149 } 150 151 var ( 152 fn types.Object // function symbol (*Func, possibly *Var for a FuncLit) 153 ftype *ast.FuncType 154 body *ast.BlockStmt 155 ) 156 switch n := n.(type) { 157 case *ast.FuncDecl: 158 // We can't analyze non-Go functions. 159 if n.Body == nil { 160 return true 161 } 162 163 // Ignore exported functions and methods: we 164 // must assume they may be address-taken in 165 // another package. 166 if n.Name.IsExported() { 167 return true 168 } 169 170 // Ignore methods that match the name of any 171 // interface method declared in this package, 172 // as the method's signature may need to conform 173 // to the interface. 174 if n.Recv != nil && unexportedIMethodNames[n.Name.Name] { 175 return true 176 } 177 178 fn = pass.TypesInfo.Defs[n.Name].(*types.Func) 179 ftype, body = n.Type, n.Body 180 181 case *ast.FuncLit: 182 // Find the symbol for the variable (if any) 183 // to which the FuncLit is bound. 184 // (We don't bother to allow ParenExprs.) 185 switch parent := stack[len(stack)-2].(type) { 186 case *ast.AssignStmt: 187 // f = func() {...} 188 // f := func() {...} 189 for i, rhs := range parent.Rhs { 190 if rhs == n { 191 if id, ok := parent.Lhs[i].(*ast.Ident); ok { 192 fn = pass.TypesInfo.ObjectOf(id) 193 194 // Edge case: f = func() {...} 195 // should not count as a use. 196 if pass.TypesInfo.Uses[id] != nil { 197 usesOutsideCall[fn] = slices.Remove(usesOutsideCall[fn], id) 198 } 199 200 if fn == nil && id.Name == "_" { 201 // Edge case: _ = func() {...} 202 // has no var. Fake one. 203 fn = types.NewVar(id.Pos(), pass.Pkg, id.Name, pass.TypesInfo.TypeOf(n)) 204 } 205 } 206 break 207 } 208 } 209 210 case *ast.ValueSpec: 211 // var f = func() { ... } 212 // (unless f is an exported package-level var) 213 for i, val := range parent.Values { 214 if val == n { 215 v := pass.TypesInfo.Defs[parent.Names[i]] 216 if !(v.Parent() == pass.Pkg.Scope() && v.Exported()) { 217 fn = v 218 } 219 break 220 } 221 } 222 } 223 224 ftype, body = n.Type, n.Body 225 } 226 227 // Ignore address-taken functions and methods: unused 228 // parameters may be needed to conform to a func type. 229 if fn == nil || len(usesOutsideCall[fn]) > 0 { 230 return true 231 } 232 233 // If there are no parameters, there are no unused parameters. 234 if ftype.Params.NumFields() == 0 { 235 return true 236 } 237 238 // To reduce false positives, ignore functions with an 239 // empty or panic body. 240 // 241 // We choose not to ignore functions whose body is a 242 // single return statement (as earlier versions did) 243 // func f() { return } 244 // func f() { return g(...) } 245 // as we suspect that was just heuristic to reduce 246 // false positives in the earlier unsound algorithm. 247 switch len(body.List) { 248 case 0: 249 // Empty body. Although the parameter is 250 // unnecessary, it's pretty obvious to the 251 // reader that that's the case, so we allow it. 252 return true // func f() {} 253 case 1: 254 if stmt, ok := body.List[0].(*ast.ExprStmt); ok { 255 // We allow a panic body, as it is often a 256 // placeholder for a future implementation: 257 // func f() { panic(...) } 258 if call, ok := stmt.X.(*ast.CallExpr); ok { 259 if fun, ok := call.Fun.(*ast.Ident); ok && fun.Name == "panic" { 260 return true 261 } 262 } 263 } 264 } 265 266 // Report each unused parameter. 267 for _, field := range ftype.Params.List { 268 for _, id := range field.Names { 269 if id.Name == "_" { 270 continue 271 } 272 param := pass.TypesInfo.Defs[id].(*types.Var) 273 if !usedVars[param] { 274 start, end := field.Pos(), field.End() 275 if len(field.Names) > 1 { 276 start, end = id.Pos(), id.End() 277 } 278 // This diagnostic carries both an edit-based fix to 279 // rename the unused parameter, and a command-based fix 280 // to remove it (see golang.RemoveUnusedParameter). 281 pass.Report(analysis.Diagnostic{ 282 Pos: start, 283 End: end, 284 Message: fmt.Sprintf("unused parameter: %s", id.Name), 285 Category: FixCategory, 286 SuggestedFixes: []analysis.SuggestedFix{ 287 { 288 Message: `Rename parameter to "_"`, 289 TextEdits: []analysis.TextEdit{{ 290 Pos: id.Pos(), 291 End: id.End(), 292 NewText: []byte("_"), 293 }}, 294 }, 295 { 296 Message: fmt.Sprintf("Remove unused parameter %q", id.Name), 297 // No TextEdits => computed by gopls command 298 }, 299 }, 300 }) 301 } 302 } 303 } 304 305 return true 306 }) 307 return nil, nil 308 }