golang.org/x/tools/gopls@v0.15.3/internal/golang/implementation.go (about) 1 // Copyright 2019 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 golang 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "go/ast" 12 "go/token" 13 "go/types" 14 "reflect" 15 "sort" 16 "strings" 17 "sync" 18 19 "golang.org/x/sync/errgroup" 20 "golang.org/x/tools/gopls/internal/cache" 21 "golang.org/x/tools/gopls/internal/cache/metadata" 22 "golang.org/x/tools/gopls/internal/cache/methodsets" 23 "golang.org/x/tools/gopls/internal/file" 24 "golang.org/x/tools/gopls/internal/protocol" 25 "golang.org/x/tools/gopls/internal/util/bug" 26 "golang.org/x/tools/gopls/internal/util/safetoken" 27 "golang.org/x/tools/internal/event" 28 ) 29 30 // This file defines the new implementation of the 'implementation' 31 // operator that does not require type-checker data structures for an 32 // unbounded number of packages. 33 // 34 // TODO(adonovan): 35 // - Audit to ensure robustness in face of type errors. 36 // - Eliminate false positives due to 'tricky' cases of the global algorithm. 37 // - Ensure we have test coverage of: 38 // type aliases 39 // nil, PkgName, Builtin (all errors) 40 // any (empty result) 41 // method of unnamed interface type (e.g. var x interface { f() }) 42 // (the global algorithm may find implementations of this type 43 // but will not include it in the index.) 44 45 // Implementation returns a new sorted array of locations of 46 // declarations of types that implement (or are implemented by) the 47 // type referred to at the given position. 48 // 49 // If the position denotes a method, the computation is applied to its 50 // receiver type and then its corresponding methods are returned. 51 func Implementation(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp protocol.Position) ([]protocol.Location, error) { 52 ctx, done := event.Start(ctx, "golang.Implementation") 53 defer done() 54 55 locs, err := implementations(ctx, snapshot, f, pp) 56 if err != nil { 57 return nil, err 58 } 59 60 // Sort and de-duplicate locations. 61 sort.Slice(locs, func(i, j int) bool { 62 return protocol.CompareLocation(locs[i], locs[j]) < 0 63 }) 64 out := locs[:0] 65 for _, loc := range locs { 66 if len(out) == 0 || out[len(out)-1] != loc { 67 out = append(out, loc) 68 } 69 } 70 locs = out 71 72 return locs, nil 73 } 74 75 func implementations(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp protocol.Position) ([]protocol.Location, error) { 76 obj, pkg, err := implementsObj(ctx, snapshot, fh.URI(), pp) 77 if err != nil { 78 return nil, err 79 } 80 81 var localPkgs []*cache.Package 82 if obj.Pos().IsValid() { // no local package for error or error.Error 83 declPosn := safetoken.StartPosition(pkg.FileSet(), obj.Pos()) 84 // Type-check the declaring package (incl. variants) for use 85 // by the "local" search, which uses type information to 86 // enumerate all types within the package that satisfy the 87 // query type, even those defined local to a function. 88 declURI := protocol.URIFromPath(declPosn.Filename) 89 declMPs, err := snapshot.MetadataForFile(ctx, declURI) 90 if err != nil { 91 return nil, err 92 } 93 metadata.RemoveIntermediateTestVariants(&declMPs) 94 if len(declMPs) == 0 { 95 return nil, fmt.Errorf("no packages for file %s", declURI) 96 } 97 ids := make([]PackageID, len(declMPs)) 98 for i, mp := range declMPs { 99 ids[i] = mp.ID 100 } 101 localPkgs, err = snapshot.TypeCheck(ctx, ids...) 102 if err != nil { 103 return nil, err 104 } 105 } 106 107 // Is the selected identifier a type name or method? 108 // (For methods, report the corresponding method names.) 109 var queryType types.Type 110 var queryMethodID string 111 switch obj := obj.(type) { 112 case *types.TypeName: 113 queryType = obj.Type() 114 case *types.Func: 115 // For methods, use the receiver type, which may be anonymous. 116 if recv := obj.Type().(*types.Signature).Recv(); recv != nil { 117 queryType = recv.Type() 118 queryMethodID = obj.Id() 119 } 120 } 121 if queryType == nil { 122 return nil, bug.Errorf("%s is not a type or method", obj.Name()) // should have been handled by implementsObj 123 } 124 125 // Compute the method-set fingerprint used as a key to the global search. 126 key, hasMethods := methodsets.KeyOf(queryType) 127 if !hasMethods { 128 // A type with no methods yields an empty result. 129 // (No point reporting that every type satisfies 'any'.) 130 return nil, nil 131 } 132 133 // The global search needs to look at every package in the 134 // forward transitive closure of the workspace; see package 135 // ./methodsets. 136 // 137 // For now we do all the type checking before beginning the search. 138 // TODO(adonovan): opt: search in parallel topological order 139 // so that we can overlap index lookup with typechecking. 140 // I suspect a number of algorithms on the result of TypeCheck could 141 // be optimized by being applied as soon as each package is available. 142 globalMetas, err := snapshot.AllMetadata(ctx) 143 if err != nil { 144 return nil, err 145 } 146 metadata.RemoveIntermediateTestVariants(&globalMetas) 147 globalIDs := make([]PackageID, 0, len(globalMetas)) 148 149 var pkgPath PackagePath 150 if obj.Pkg() != nil { // nil for error 151 pkgPath = PackagePath(obj.Pkg().Path()) 152 } 153 for _, mp := range globalMetas { 154 if mp.PkgPath == pkgPath { 155 continue // declaring package is handled by local implementation 156 } 157 globalIDs = append(globalIDs, mp.ID) 158 } 159 indexes, err := snapshot.MethodSets(ctx, globalIDs...) 160 if err != nil { 161 return nil, fmt.Errorf("querying method sets: %v", err) 162 } 163 164 // Search local and global packages in parallel. 165 var ( 166 group errgroup.Group 167 locsMu sync.Mutex 168 locs []protocol.Location 169 ) 170 // local search 171 for _, localPkg := range localPkgs { 172 localPkg := localPkg 173 group.Go(func() error { 174 localLocs, err := localImplementations(ctx, snapshot, localPkg, queryType, queryMethodID) 175 if err != nil { 176 return err 177 } 178 locsMu.Lock() 179 locs = append(locs, localLocs...) 180 locsMu.Unlock() 181 return nil 182 }) 183 } 184 // global search 185 for _, index := range indexes { 186 index := index 187 group.Go(func() error { 188 for _, res := range index.Search(key, queryMethodID) { 189 loc := res.Location 190 // Map offsets to protocol.Locations in parallel (may involve I/O). 191 group.Go(func() error { 192 ploc, err := offsetToLocation(ctx, snapshot, loc.Filename, loc.Start, loc.End) 193 if err != nil { 194 return err 195 } 196 locsMu.Lock() 197 locs = append(locs, ploc) 198 locsMu.Unlock() 199 return nil 200 }) 201 } 202 return nil 203 }) 204 } 205 if err := group.Wait(); err != nil { 206 return nil, err 207 } 208 209 return locs, nil 210 } 211 212 // offsetToLocation converts an offset-based position to a protocol.Location, 213 // which requires reading the file. 214 func offsetToLocation(ctx context.Context, snapshot *cache.Snapshot, filename string, start, end int) (protocol.Location, error) { 215 uri := protocol.URIFromPath(filename) 216 fh, err := snapshot.ReadFile(ctx, uri) 217 if err != nil { 218 return protocol.Location{}, err // cancelled, perhaps 219 } 220 content, err := fh.Content() 221 if err != nil { 222 return protocol.Location{}, err // nonexistent or deleted ("can't happen") 223 } 224 m := protocol.NewMapper(uri, content) 225 return m.OffsetLocation(start, end) 226 } 227 228 // implementsObj returns the object to query for implementations, which is a 229 // type name or method. 230 // 231 // The returned Package is the narrowest package containing ppos, which is the 232 // package using the resulting obj but not necessarily the declaring package. 233 func implementsObj(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI, ppos protocol.Position) (types.Object, *cache.Package, error) { 234 pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, uri) 235 if err != nil { 236 return nil, nil, err 237 } 238 pos, err := pgf.PositionPos(ppos) 239 if err != nil { 240 return nil, nil, err 241 } 242 243 // This function inherits the limitation of its predecessor in 244 // requiring the selection to be an identifier (of a type or 245 // method). But there's no fundamental reason why one could 246 // not pose this query about any selected piece of syntax that 247 // has a type and thus a method set. 248 // (If LSP was more thorough about passing text selections as 249 // intervals to queries, you could ask about the method set of a 250 // subexpression such as x.f().) 251 252 // TODO(adonovan): simplify: use objectsAt? 253 path := pathEnclosingObjNode(pgf.File, pos) 254 if path == nil { 255 return nil, nil, ErrNoIdentFound 256 } 257 id, ok := path[0].(*ast.Ident) 258 if !ok { 259 return nil, nil, ErrNoIdentFound 260 } 261 262 // Is the object a type or method? Reject other kinds. 263 obj := pkg.GetTypesInfo().Uses[id] 264 if obj == nil { 265 // Check uses first (unlike ObjectOf) so that T in 266 // struct{T} is treated as a reference to a type, 267 // not a declaration of a field. 268 obj = pkg.GetTypesInfo().Defs[id] 269 } 270 switch obj := obj.(type) { 271 case *types.TypeName: 272 // ok 273 case *types.Func: 274 if obj.Type().(*types.Signature).Recv() == nil { 275 return nil, nil, fmt.Errorf("%s is a function, not a method", id.Name) 276 } 277 case nil: 278 return nil, nil, fmt.Errorf("%s denotes unknown object", id.Name) 279 default: 280 // e.g. *types.Var -> "var". 281 kind := strings.ToLower(strings.TrimPrefix(reflect.TypeOf(obj).String(), "*types.")) 282 return nil, nil, fmt.Errorf("%s is a %s, not a type", id.Name, kind) 283 } 284 285 return obj, pkg, nil 286 } 287 288 // localImplementations searches within pkg for declarations of all 289 // types that are assignable to/from the query type, and returns a new 290 // unordered array of their locations. 291 // 292 // If methodID is non-empty, the function instead returns the location 293 // of each type's method (if any) of that ID. 294 // 295 // ("Local" refers to the search within the same package, but this 296 // function's results may include type declarations that are local to 297 // a function body. The global search index excludes such types 298 // because reliably naming such types is hard.) 299 func localImplementations(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, queryType types.Type, methodID string) ([]protocol.Location, error) { 300 queryType = methodsets.EnsurePointer(queryType) 301 302 // Scan through all type declarations in the syntax. 303 var locs []protocol.Location 304 var methodLocs []methodsets.Location 305 for _, pgf := range pkg.CompiledGoFiles() { 306 ast.Inspect(pgf.File, func(n ast.Node) bool { 307 spec, ok := n.(*ast.TypeSpec) 308 if !ok { 309 return true // not a type declaration 310 } 311 def := pkg.GetTypesInfo().Defs[spec.Name] 312 if def == nil { 313 return true // "can't happen" for types 314 } 315 if def.(*types.TypeName).IsAlias() { 316 return true // skip type aliases to avoid duplicate reporting 317 } 318 candidateType := methodsets.EnsurePointer(def.Type()) 319 320 // The historical behavior enshrined by this 321 // function rejects cases where both are 322 // (nontrivial) interface types? 323 // That seems like useful information. 324 // TODO(adonovan): UX: report I/I pairs too? 325 // The same question appears in the global algorithm (methodsets). 326 if !concreteImplementsIntf(candidateType, queryType) { 327 return true // not assignable 328 } 329 330 // Ignore types with empty method sets. 331 // (No point reporting that every type satisfies 'any'.) 332 mset := types.NewMethodSet(candidateType) 333 if mset.Len() == 0 { 334 return true 335 } 336 337 if methodID == "" { 338 // Found matching type. 339 locs = append(locs, mustLocation(pgf, spec.Name)) 340 return true 341 } 342 343 // Find corresponding method. 344 // 345 // We can't use LookupFieldOrMethod because it requires 346 // the methodID's types.Package, which we don't know. 347 // We could recursively search pkg.Imports for it, 348 // but it's easier to walk the method set. 349 for i := 0; i < mset.Len(); i++ { 350 method := mset.At(i).Obj() 351 if method.Id() == methodID { 352 posn := safetoken.StartPosition(pkg.FileSet(), method.Pos()) 353 methodLocs = append(methodLocs, methodsets.Location{ 354 Filename: posn.Filename, 355 Start: posn.Offset, 356 End: posn.Offset + len(method.Name()), 357 }) 358 break 359 } 360 } 361 return true 362 }) 363 } 364 365 // Finally convert method positions to protocol form by reading the files. 366 for _, mloc := range methodLocs { 367 loc, err := offsetToLocation(ctx, snapshot, mloc.Filename, mloc.Start, mloc.End) 368 if err != nil { 369 return nil, err 370 } 371 locs = append(locs, loc) 372 } 373 374 // Special case: for types that satisfy error, report builtin.go (see #59527). 375 if types.Implements(queryType, errorInterfaceType) { 376 loc, err := errorLocation(ctx, snapshot) 377 if err != nil { 378 return nil, err 379 } 380 locs = append(locs, loc) 381 } 382 383 return locs, nil 384 } 385 386 var errorInterfaceType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) 387 388 // errorLocation returns the location of the 'error' type in builtin.go. 389 func errorLocation(ctx context.Context, snapshot *cache.Snapshot) (protocol.Location, error) { 390 pgf, err := snapshot.BuiltinFile(ctx) 391 if err != nil { 392 return protocol.Location{}, err 393 } 394 for _, decl := range pgf.File.Decls { 395 if decl, ok := decl.(*ast.GenDecl); ok { 396 for _, spec := range decl.Specs { 397 if spec, ok := spec.(*ast.TypeSpec); ok && spec.Name.Name == "error" { 398 return pgf.NodeLocation(spec.Name) 399 } 400 } 401 } 402 } 403 return protocol.Location{}, fmt.Errorf("built-in error type not found") 404 } 405 406 // concreteImplementsIntf returns true if a is an interface type implemented by 407 // concrete type b, or vice versa. 408 func concreteImplementsIntf(a, b types.Type) bool { 409 aIsIntf, bIsIntf := types.IsInterface(a), types.IsInterface(b) 410 411 // Make sure exactly one is an interface type. 412 if aIsIntf == bIsIntf { 413 return false 414 } 415 416 // Rearrange if needed so "a" is the concrete type. 417 if aIsIntf { 418 a, b = b, a 419 } 420 421 // TODO(adonovan): this should really use GenericAssignableTo 422 // to report (e.g.) "ArrayList[T] implements List[T]", but 423 // GenericAssignableTo doesn't work correctly on pointers to 424 // generic named types. Thus the legacy implementation and the 425 // "local" part of implementations fail to report generics. 426 // The global algorithm based on subsets does the right thing. 427 return types.AssignableTo(a, b) 428 } 429 430 var ( 431 // TODO(adonovan): why do various RPC handlers related to 432 // IncomingCalls return (nil, nil) on the protocol in response 433 // to this error? That seems like a violation of the protocol. 434 // Is it perhaps a workaround for VSCode behavior? 435 errNoObjectFound = errors.New("no object found") 436 ) 437 438 // pathEnclosingObjNode returns the AST path to the object-defining 439 // node associated with pos. "Object-defining" means either an 440 // *ast.Ident mapped directly to a types.Object or an ast.Node mapped 441 // implicitly to a types.Object. 442 func pathEnclosingObjNode(f *ast.File, pos token.Pos) []ast.Node { 443 var ( 444 path []ast.Node 445 found bool 446 ) 447 448 ast.Inspect(f, func(n ast.Node) bool { 449 if found { 450 return false 451 } 452 453 if n == nil { 454 path = path[:len(path)-1] 455 return false 456 } 457 458 path = append(path, n) 459 460 switch n := n.(type) { 461 case *ast.Ident: 462 // Include the position directly after identifier. This handles 463 // the common case where the cursor is right after the 464 // identifier the user is currently typing. Previously we 465 // handled this by calling astutil.PathEnclosingInterval twice, 466 // once for "pos" and once for "pos-1". 467 found = n.Pos() <= pos && pos <= n.End() 468 case *ast.ImportSpec: 469 if n.Path.Pos() <= pos && pos < n.Path.End() { 470 found = true 471 // If import spec has a name, add name to path even though 472 // position isn't in the name. 473 if n.Name != nil { 474 path = append(path, n.Name) 475 } 476 } 477 case *ast.StarExpr: 478 // Follow star expressions to the inner identifier. 479 if pos == n.Star { 480 pos = n.X.Pos() 481 } 482 } 483 484 return !found 485 }) 486 487 if len(path) == 0 { 488 return nil 489 } 490 491 // Reverse path so leaf is first element. 492 for i := 0; i < len(path)/2; i++ { 493 path[i], path[len(path)-1-i] = path[len(path)-1-i], path[i] 494 } 495 496 return path 497 }