golang.org/x/tools/gopls@v0.15.3/internal/analysis/stubmethods/stubmethods.go (about) 1 // Copyright 2022 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 stubmethods 6 7 import ( 8 "bytes" 9 _ "embed" 10 "fmt" 11 "go/ast" 12 "go/format" 13 "go/token" 14 "go/types" 15 "strings" 16 17 "golang.org/x/tools/go/analysis" 18 "golang.org/x/tools/go/ast/astutil" 19 "golang.org/x/tools/gopls/internal/util/typesutil" 20 "golang.org/x/tools/internal/analysisinternal" 21 "golang.org/x/tools/internal/typesinternal" 22 ) 23 24 //go:embed doc.go 25 var doc string 26 27 var Analyzer = &analysis.Analyzer{ 28 Name: "stubmethods", 29 Doc: analysisinternal.MustExtractDoc(doc, "stubmethods"), 30 Run: run, 31 RunDespiteErrors: true, 32 URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/stubmethods", 33 } 34 35 // TODO(rfindley): remove this thin wrapper around the stubmethods refactoring, 36 // and eliminate the stubmethods analyzer. 37 // 38 // Previous iterations used the analysis framework for computing refactorings, 39 // which proved inefficient. 40 func run(pass *analysis.Pass) (interface{}, error) { 41 for _, err := range pass.TypeErrors { 42 var file *ast.File 43 for _, f := range pass.Files { 44 if f.Pos() <= err.Pos && err.Pos < f.End() { 45 file = f 46 break 47 } 48 } 49 // Get the end position of the error. 50 _, _, end, ok := typesinternal.ReadGo116ErrorData(err) 51 if !ok { 52 var buf bytes.Buffer 53 if err := format.Node(&buf, pass.Fset, file); err != nil { 54 continue 55 } 56 end = analysisinternal.TypeErrorEndPos(pass.Fset, buf.Bytes(), err.Pos) 57 } 58 if diag, ok := DiagnosticForError(pass.Fset, file, err.Pos, end, err.Msg, pass.TypesInfo); ok { 59 pass.Report(diag) 60 } 61 } 62 63 return nil, nil 64 } 65 66 // MatchesMessage reports whether msg matches the error message sought after by 67 // the stubmethods fix. 68 func MatchesMessage(msg string) bool { 69 return strings.Contains(msg, "missing method") || strings.HasPrefix(msg, "cannot convert") || strings.Contains(msg, "not implement") 70 } 71 72 // DiagnosticForError computes a diagnostic suggesting to implement an 73 // interface to fix the type checking error defined by (start, end, msg). 74 // 75 // If no such fix is possible, the second result is false. 76 func DiagnosticForError(fset *token.FileSet, file *ast.File, start, end token.Pos, msg string, info *types.Info) (analysis.Diagnostic, bool) { 77 if !MatchesMessage(msg) { 78 return analysis.Diagnostic{}, false 79 } 80 81 path, _ := astutil.PathEnclosingInterval(file, start, end) 82 si := GetStubInfo(fset, info, path, start) 83 if si == nil { 84 return analysis.Diagnostic{}, false 85 } 86 qf := typesutil.FileQualifier(file, si.Concrete.Obj().Pkg(), info) 87 iface := types.TypeString(si.Interface.Type(), qf) 88 return analysis.Diagnostic{ 89 Pos: start, 90 End: end, 91 Message: msg, 92 Category: FixCategory, 93 SuggestedFixes: []analysis.SuggestedFix{{ 94 Message: fmt.Sprintf("Declare missing methods of %s", iface), 95 // No TextEdits => computed later by gopls. 96 }}, 97 }, true 98 } 99 100 const FixCategory = "stubmethods" // recognized by gopls ApplyFix 101 102 // StubInfo represents a concrete type 103 // that wants to stub out an interface type 104 type StubInfo struct { 105 // Interface is the interface that the client wants to implement. 106 // When the interface is defined, the underlying object will be a TypeName. 107 // Note that we keep track of types.Object instead of types.Type in order 108 // to keep a reference to the declaring object's package and the ast file 109 // in the case where the concrete type file requires a new import that happens to be renamed 110 // in the interface file. 111 // TODO(marwan-at-work): implement interface literals. 112 Fset *token.FileSet // the FileSet used to type-check the types below 113 Interface *types.TypeName 114 Concrete *types.Named 115 Pointer bool 116 } 117 118 // GetStubInfo determines whether the "missing method error" 119 // can be used to deduced what the concrete and interface types are. 120 // 121 // TODO(adonovan): this function (and its following 5 helpers) tries 122 // to deduce a pair of (concrete, interface) types that are related by 123 // an assignment, either explicitly or through a return statement or 124 // function call. This is essentially what the refactor/satisfy does, 125 // more generally. Refactor to share logic, after auditing 'satisfy' 126 // for safety on ill-typed code. 127 func GetStubInfo(fset *token.FileSet, info *types.Info, path []ast.Node, pos token.Pos) *StubInfo { 128 for _, n := range path { 129 switch n := n.(type) { 130 case *ast.ValueSpec: 131 return fromValueSpec(fset, info, n, pos) 132 case *ast.ReturnStmt: 133 // An error here may not indicate a real error the user should know about, but it may. 134 // Therefore, it would be best to log it out for debugging/reporting purposes instead of ignoring 135 // it. However, event.Log takes a context which is not passed via the analysis package. 136 // TODO(marwan-at-work): properly log this error. 137 si, _ := fromReturnStmt(fset, info, pos, path, n) 138 return si 139 case *ast.AssignStmt: 140 return fromAssignStmt(fset, info, n, pos) 141 case *ast.CallExpr: 142 // Note that some call expressions don't carry the interface type 143 // because they don't point to a function or method declaration elsewhere. 144 // For eaxmple, "var Interface = (*Concrete)(nil)". In that case, continue 145 // this loop to encounter other possibilities such as *ast.ValueSpec or others. 146 si := fromCallExpr(fset, info, pos, n) 147 if si != nil { 148 return si 149 } 150 } 151 } 152 return nil 153 } 154 155 // fromCallExpr tries to find an *ast.CallExpr's function declaration and 156 // analyzes a function call's signature against the passed in parameter to deduce 157 // the concrete and interface types. 158 func fromCallExpr(fset *token.FileSet, info *types.Info, pos token.Pos, call *ast.CallExpr) *StubInfo { 159 // Find argument containing pos. 160 argIdx := -1 161 var arg ast.Expr 162 for i, callArg := range call.Args { 163 if callArg.Pos() <= pos && pos <= callArg.End() { 164 argIdx = i 165 arg = callArg 166 break 167 } 168 } 169 if arg == nil { 170 return nil 171 } 172 173 concType, pointer := concreteType(arg, info) 174 if concType == nil || concType.Obj().Pkg() == nil { 175 return nil 176 } 177 tv, ok := info.Types[call.Fun] 178 if !ok { 179 return nil 180 } 181 sig, ok := tv.Type.(*types.Signature) 182 if !ok { 183 return nil 184 } 185 var paramType types.Type 186 if sig.Variadic() && argIdx >= sig.Params().Len()-1 { 187 v := sig.Params().At(sig.Params().Len() - 1) 188 if s, _ := v.Type().(*types.Slice); s != nil { 189 paramType = s.Elem() 190 } 191 } else if argIdx < sig.Params().Len() { 192 paramType = sig.Params().At(argIdx).Type() 193 } 194 if paramType == nil { 195 return nil // A type error prevents us from determining the param type. 196 } 197 iface := ifaceObjFromType(paramType) 198 if iface == nil { 199 return nil 200 } 201 return &StubInfo{ 202 Fset: fset, 203 Concrete: concType, 204 Pointer: pointer, 205 Interface: iface, 206 } 207 } 208 209 // fromReturnStmt analyzes a "return" statement to extract 210 // a concrete type that is trying to be returned as an interface type. 211 // 212 // For example, func() io.Writer { return myType{} } 213 // would return StubInfo with the interface being io.Writer and the concrete type being myType{}. 214 func fromReturnStmt(fset *token.FileSet, info *types.Info, pos token.Pos, path []ast.Node, ret *ast.ReturnStmt) (*StubInfo, error) { 215 // Find return operand containing pos. 216 returnIdx := -1 217 for i, r := range ret.Results { 218 if r.Pos() <= pos && pos <= r.End() { 219 returnIdx = i 220 break 221 } 222 } 223 if returnIdx == -1 { 224 return nil, fmt.Errorf("pos %d not within return statement bounds: [%d-%d]", pos, ret.Pos(), ret.End()) 225 } 226 227 concType, pointer := concreteType(ret.Results[returnIdx], info) 228 if concType == nil || concType.Obj().Pkg() == nil { 229 return nil, nil 230 } 231 funcType := enclosingFunction(path, info) 232 if funcType == nil { 233 return nil, fmt.Errorf("could not find the enclosing function of the return statement") 234 } 235 if len(funcType.Results.List) != len(ret.Results) { 236 return nil, fmt.Errorf("%d-operand return statement in %d-result function", 237 len(ret.Results), 238 len(funcType.Results.List)) 239 } 240 iface := ifaceType(funcType.Results.List[returnIdx].Type, info) 241 if iface == nil { 242 return nil, nil 243 } 244 return &StubInfo{ 245 Fset: fset, 246 Concrete: concType, 247 Pointer: pointer, 248 Interface: iface, 249 }, nil 250 } 251 252 // fromValueSpec returns *StubInfo from a variable declaration such as 253 // var x io.Writer = &T{} 254 func fromValueSpec(fset *token.FileSet, info *types.Info, spec *ast.ValueSpec, pos token.Pos) *StubInfo { 255 // Find RHS element containing pos. 256 var rhs ast.Expr 257 for _, r := range spec.Values { 258 if r.Pos() <= pos && pos <= r.End() { 259 rhs = r 260 break 261 } 262 } 263 if rhs == nil { 264 return nil // e.g. pos was on the LHS (#64545) 265 } 266 267 // Possible implicit/explicit conversion to interface type? 268 ifaceNode := spec.Type // var _ myInterface = ... 269 if call, ok := rhs.(*ast.CallExpr); ok && ifaceNode == nil && len(call.Args) == 1 { 270 // var _ = myInterface(v) 271 ifaceNode = call.Fun 272 rhs = call.Args[0] 273 } 274 concType, pointer := concreteType(rhs, info) 275 if concType == nil || concType.Obj().Pkg() == nil { 276 return nil 277 } 278 ifaceObj := ifaceType(ifaceNode, info) 279 if ifaceObj == nil { 280 return nil 281 } 282 return &StubInfo{ 283 Fset: fset, 284 Concrete: concType, 285 Interface: ifaceObj, 286 Pointer: pointer, 287 } 288 } 289 290 // fromAssignStmt returns *StubInfo from a variable assignment such as 291 // var x io.Writer 292 // x = &T{} 293 func fromAssignStmt(fset *token.FileSet, info *types.Info, assign *ast.AssignStmt, pos token.Pos) *StubInfo { 294 // The interface conversion error in an assignment is against the RHS: 295 // 296 // var x io.Writer 297 // x = &T{} // error: missing method 298 // ^^^^ 299 // 300 // Find RHS element containing pos. 301 var lhs, rhs ast.Expr 302 for i, r := range assign.Rhs { 303 if r.Pos() <= pos && pos <= r.End() { 304 if i >= len(assign.Lhs) { 305 // This should never happen as we would get a 306 // "cannot assign N values to M variables" 307 // before we get an interface conversion error. 308 // But be defensive. 309 return nil 310 } 311 lhs = assign.Lhs[i] 312 rhs = r 313 break 314 } 315 } 316 if lhs == nil || rhs == nil { 317 return nil 318 } 319 320 ifaceObj := ifaceType(lhs, info) 321 if ifaceObj == nil { 322 return nil 323 } 324 concType, pointer := concreteType(rhs, info) 325 if concType == nil || concType.Obj().Pkg() == nil { 326 return nil 327 } 328 return &StubInfo{ 329 Fset: fset, 330 Concrete: concType, 331 Interface: ifaceObj, 332 Pointer: pointer, 333 } 334 } 335 336 // ifaceType returns the named interface type to which e refers, if any. 337 func ifaceType(e ast.Expr, info *types.Info) *types.TypeName { 338 tv, ok := info.Types[e] 339 if !ok { 340 return nil 341 } 342 return ifaceObjFromType(tv.Type) 343 } 344 345 func ifaceObjFromType(t types.Type) *types.TypeName { 346 named, ok := t.(*types.Named) 347 if !ok { 348 return nil 349 } 350 if !types.IsInterface(named) { 351 return nil 352 } 353 // Interfaces defined in the "builtin" package return nil a Pkg(). 354 // But they are still real interfaces that we need to make a special case for. 355 // Therefore, protect gopls from panicking if a new interface type was added in the future. 356 if named.Obj().Pkg() == nil && named.Obj().Name() != "error" { 357 return nil 358 } 359 return named.Obj() 360 } 361 362 // concreteType tries to extract the *types.Named that defines 363 // the concrete type given the ast.Expr where the "missing method" 364 // or "conversion" errors happened. If the concrete type is something 365 // that cannot have methods defined on it (such as basic types), this 366 // method will return a nil *types.Named. The second return parameter 367 // is a boolean that indicates whether the concreteType was defined as a 368 // pointer or value. 369 func concreteType(e ast.Expr, info *types.Info) (*types.Named, bool) { 370 tv, ok := info.Types[e] 371 if !ok { 372 return nil, false 373 } 374 typ := tv.Type 375 ptr, isPtr := typ.(*types.Pointer) 376 if isPtr { 377 typ = ptr.Elem() 378 } 379 named, ok := typ.(*types.Named) 380 if !ok { 381 return nil, false 382 } 383 return named, isPtr 384 } 385 386 // enclosingFunction returns the signature and type of the function 387 // enclosing the given position. 388 func enclosingFunction(path []ast.Node, info *types.Info) *ast.FuncType { 389 for _, node := range path { 390 switch t := node.(type) { 391 case *ast.FuncDecl: 392 if _, ok := info.Defs[t.Name]; ok { 393 return t.Type 394 } 395 case *ast.FuncLit: 396 if _, ok := info.Types[t]; ok { 397 return t.Type 398 } 399 } 400 } 401 return nil 402 }