golang.org/x/tools/gopls@v0.15.3/internal/golang/references.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 // This file defines the 'references' query based on a serializable 8 // index constructed during type checking, thus avoiding the need to 9 // type-check packages at search time. 10 // 11 // See the ./xrefs/ subpackage for the index construction and lookup. 12 // 13 // This implementation does not intermingle objects from distinct 14 // calls to TypeCheck. 15 16 import ( 17 "context" 18 "fmt" 19 "go/ast" 20 "go/token" 21 "go/types" 22 "sort" 23 "strings" 24 "sync" 25 26 "golang.org/x/sync/errgroup" 27 "golang.org/x/tools/go/types/objectpath" 28 "golang.org/x/tools/gopls/internal/cache" 29 "golang.org/x/tools/gopls/internal/cache/metadata" 30 "golang.org/x/tools/gopls/internal/cache/methodsets" 31 "golang.org/x/tools/gopls/internal/file" 32 "golang.org/x/tools/gopls/internal/protocol" 33 "golang.org/x/tools/gopls/internal/util/bug" 34 "golang.org/x/tools/gopls/internal/util/safetoken" 35 "golang.org/x/tools/internal/event" 36 ) 37 38 // References returns a list of all references (sorted with 39 // definitions before uses) to the object denoted by the identifier at 40 // the given file/position, searching the entire workspace. 41 func References(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp protocol.Position, includeDeclaration bool) ([]protocol.Location, error) { 42 references, err := references(ctx, snapshot, fh, pp, includeDeclaration) 43 if err != nil { 44 return nil, err 45 } 46 locations := make([]protocol.Location, len(references)) 47 for i, ref := range references { 48 locations[i] = ref.location 49 } 50 return locations, nil 51 } 52 53 // A reference describes an identifier that refers to the same 54 // object as the subject of a References query. 55 type reference struct { 56 isDeclaration bool 57 location protocol.Location 58 pkgPath PackagePath // of declaring package (same for all elements of the slice) 59 } 60 61 // references returns a list of all references (sorted with 62 // definitions before uses) to the object denoted by the identifier at 63 // the given file/position, searching the entire workspace. 64 func references(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp protocol.Position, includeDeclaration bool) ([]reference, error) { 65 ctx, done := event.Start(ctx, "golang.references") 66 defer done() 67 68 // Is the cursor within the package name declaration? 69 _, inPackageName, err := parsePackageNameDecl(ctx, snapshot, f, pp) 70 if err != nil { 71 return nil, err 72 } 73 74 var refs []reference 75 if inPackageName { 76 refs, err = packageReferences(ctx, snapshot, f.URI()) 77 } else { 78 refs, err = ordinaryReferences(ctx, snapshot, f.URI(), pp) 79 } 80 if err != nil { 81 return nil, err 82 } 83 84 sort.Slice(refs, func(i, j int) bool { 85 x, y := refs[i], refs[j] 86 if x.isDeclaration != y.isDeclaration { 87 return x.isDeclaration // decls < refs 88 } 89 return protocol.CompareLocation(x.location, y.location) < 0 90 }) 91 92 // De-duplicate by location, and optionally remove declarations. 93 out := refs[:0] 94 for _, ref := range refs { 95 if !includeDeclaration && ref.isDeclaration { 96 continue 97 } 98 if len(out) == 0 || out[len(out)-1].location != ref.location { 99 out = append(out, ref) 100 } 101 } 102 refs = out 103 104 return refs, nil 105 } 106 107 // packageReferences returns a list of references to the package 108 // declaration of the specified name and uri by searching among the 109 // import declarations of all packages that directly import the target 110 // package. 111 func packageReferences(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI) ([]reference, error) { 112 metas, err := snapshot.MetadataForFile(ctx, uri) 113 if err != nil { 114 return nil, err 115 } 116 if len(metas) == 0 { 117 return nil, fmt.Errorf("found no package containing %s", uri) 118 } 119 120 var refs []reference 121 122 // Find external references to the package declaration 123 // from each direct import of the package. 124 // 125 // The narrowest package is the most broadly imported, 126 // so we choose it for the external references. 127 // 128 // But if the file ends with _test.go then we need to 129 // find the package it is testing; there's no direct way 130 // to do that, so pick a file from the same package that 131 // doesn't end in _test.go and start over. 132 narrowest := metas[0] 133 if narrowest.ForTest != "" && strings.HasSuffix(string(uri), "_test.go") { 134 for _, f := range narrowest.CompiledGoFiles { 135 if !strings.HasSuffix(string(f), "_test.go") { 136 return packageReferences(ctx, snapshot, f) 137 } 138 } 139 // This package has no non-test files. 140 // Skip the search for external references. 141 // (Conceivably one could blank-import an empty package, but why?) 142 } else { 143 rdeps, err := snapshot.ReverseDependencies(ctx, narrowest.ID, false) // direct 144 if err != nil { 145 return nil, err 146 } 147 148 // Restrict search to workspace packages. 149 workspace, err := snapshot.WorkspaceMetadata(ctx) 150 if err != nil { 151 return nil, err 152 } 153 workspaceMap := make(map[PackageID]*metadata.Package, len(workspace)) 154 for _, mp := range workspace { 155 workspaceMap[mp.ID] = mp 156 } 157 158 for _, rdep := range rdeps { 159 if _, ok := workspaceMap[rdep.ID]; !ok { 160 continue 161 } 162 for _, uri := range rdep.CompiledGoFiles { 163 fh, err := snapshot.ReadFile(ctx, uri) 164 if err != nil { 165 return nil, err 166 } 167 f, err := snapshot.ParseGo(ctx, fh, ParseHeader) 168 if err != nil { 169 return nil, err 170 } 171 for _, imp := range f.File.Imports { 172 if rdep.DepsByImpPath[metadata.UnquoteImportPath(imp)] == narrowest.ID { 173 refs = append(refs, reference{ 174 isDeclaration: false, 175 location: mustLocation(f, imp), 176 pkgPath: narrowest.PkgPath, 177 }) 178 } 179 } 180 } 181 } 182 } 183 184 // Find internal "references" to the package from 185 // of each package declaration in the target package itself. 186 // 187 // The widest package (possibly a test variant) has the 188 // greatest number of files and thus we choose it for the 189 // "internal" references. 190 widest := metas[len(metas)-1] // may include _test.go files 191 for _, uri := range widest.CompiledGoFiles { 192 fh, err := snapshot.ReadFile(ctx, uri) 193 if err != nil { 194 return nil, err 195 } 196 f, err := snapshot.ParseGo(ctx, fh, ParseHeader) 197 if err != nil { 198 return nil, err 199 } 200 // golang/go#66250: don't crash if the package file lacks a name. 201 if f.File.Name.Pos().IsValid() { 202 refs = append(refs, reference{ 203 isDeclaration: true, // (one of many) 204 location: mustLocation(f, f.File.Name), 205 pkgPath: widest.PkgPath, 206 }) 207 } 208 } 209 210 return refs, nil 211 } 212 213 // ordinaryReferences computes references for all ordinary objects (not package declarations). 214 func ordinaryReferences(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI, pp protocol.Position) ([]reference, error) { 215 // Strategy: use the reference information computed by the 216 // type checker to find the declaration. First type-check this 217 // package to find the declaration, then type check the 218 // declaring package (which may be different), plus variants, 219 // to find local (in-package) references. 220 // Global references are satisfied by the index. 221 222 // Strictly speaking, a wider package could provide a different 223 // declaration (e.g. because the _test.go files can change the 224 // meaning of a field or method selection), but the narrower 225 // package reports the more broadly referenced object. 226 pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, uri) 227 if err != nil { 228 return nil, err 229 } 230 231 // Find the selected object (declaration or reference). 232 // For struct{T}, we choose the field (Def) over the type (Use). 233 pos, err := pgf.PositionPos(pp) 234 if err != nil { 235 return nil, err 236 } 237 candidates, _, err := objectsAt(pkg.GetTypesInfo(), pgf.File, pos) 238 if err != nil { 239 return nil, err 240 } 241 242 // Pick first object arbitrarily. 243 // The case variables of a type switch have different 244 // types but that difference is immaterial here. 245 var obj types.Object 246 for obj = range candidates { 247 break 248 } 249 if obj == nil { 250 return nil, ErrNoIdentFound // can't happen 251 } 252 253 // nil, error, error.Error, iota, or other built-in? 254 if obj.Pkg() == nil { 255 return nil, fmt.Errorf("references to builtin %q are not supported", obj.Name()) 256 } 257 if !obj.Pos().IsValid() { 258 if obj.Pkg().Path() != "unsafe" { 259 bug.Reportf("references: object %v has no position", obj) 260 } 261 return nil, fmt.Errorf("references to unsafe.%s are not supported", obj.Name()) 262 } 263 264 // Find metadata of all packages containing the object's defining file. 265 // This may include the query pkg, and possibly other variants. 266 declPosn := safetoken.StartPosition(pkg.FileSet(), obj.Pos()) 267 declURI := protocol.URIFromPath(declPosn.Filename) 268 variants, err := snapshot.MetadataForFile(ctx, declURI) 269 if err != nil { 270 return nil, err 271 } 272 if len(variants) == 0 { 273 return nil, fmt.Errorf("no packages for file %q", declURI) // can't happen 274 } 275 // (variants must include ITVs for reverse dependency computation below.) 276 277 // Is object exported? 278 // If so, compute scope and targets of the global search. 279 var ( 280 globalScope = make(map[PackageID]*metadata.Package) // (excludes ITVs) 281 globalTargets map[PackagePath]map[objectpath.Path]unit 282 expansions = make(map[PackageID]unit) // packages that caused search expansion 283 ) 284 // TODO(adonovan): what about generic functions? Need to consider both 285 // uninstantiated and instantiated. The latter have no objectpath. Use Origin? 286 if path, err := objectpath.For(obj); err == nil && obj.Exported() { 287 pkgPath := variants[0].PkgPath // (all variants have same package path) 288 globalTargets = map[PackagePath]map[objectpath.Path]unit{ 289 pkgPath: {path: {}}, // primary target 290 } 291 292 // Compute set of (non-ITV) workspace packages. 293 // We restrict references to this subset. 294 workspace, err := snapshot.WorkspaceMetadata(ctx) 295 if err != nil { 296 return nil, err 297 } 298 workspaceMap := make(map[PackageID]*metadata.Package, len(workspace)) 299 workspaceIDs := make([]PackageID, 0, len(workspace)) 300 for _, mp := range workspace { 301 workspaceMap[mp.ID] = mp 302 workspaceIDs = append(workspaceIDs, mp.ID) 303 } 304 305 // addRdeps expands the global scope to include the 306 // reverse dependencies of the specified package. 307 addRdeps := func(id PackageID, transitive bool) error { 308 rdeps, err := snapshot.ReverseDependencies(ctx, id, transitive) 309 if err != nil { 310 return err 311 } 312 for rdepID, rdep := range rdeps { 313 // Skip non-workspace packages. 314 // 315 // This means we also skip any expansion of the 316 // search that might be caused by a non-workspace 317 // package, possibly causing us to miss references 318 // to the expanded target set from workspace packages. 319 // 320 // TODO(adonovan): don't skip those expansions. 321 // The challenge is how to so without type-checking 322 // a lot of non-workspace packages not covered by 323 // the initial workspace load. 324 if _, ok := workspaceMap[rdepID]; !ok { 325 continue 326 } 327 328 globalScope[rdepID] = rdep 329 } 330 return nil 331 } 332 333 // How far need we search? 334 // For package-level objects, we need only search the direct importers. 335 // For fields and methods, we must search transitively. 336 transitive := obj.Pkg().Scope().Lookup(obj.Name()) != obj 337 338 // The scope is the union of rdeps of each variant. 339 // (Each set is disjoint so there's no benefit to 340 // combining the metadata graph traversals.) 341 for _, mp := range variants { 342 if err := addRdeps(mp.ID, transitive); err != nil { 343 return nil, err 344 } 345 } 346 347 // Is object a method? 348 // 349 // If so, expand the search so that the targets include 350 // all methods that correspond to it through interface 351 // satisfaction, and the scope includes the rdeps of 352 // the package that declares each corresponding type. 353 // 354 // 'expansions' records the packages that declared 355 // such types. 356 if recv := effectiveReceiver(obj); recv != nil { 357 if err := expandMethodSearch(ctx, snapshot, workspaceIDs, obj.(*types.Func), recv, addRdeps, globalTargets, expansions); err != nil { 358 return nil, err 359 } 360 } 361 } 362 363 // The search functions will call report(loc) for each hit. 364 var ( 365 refsMu sync.Mutex 366 refs []reference 367 ) 368 report := func(loc protocol.Location, isDecl bool) { 369 ref := reference{ 370 isDeclaration: isDecl, 371 location: loc, 372 pkgPath: pkg.Metadata().PkgPath, 373 } 374 refsMu.Lock() 375 refs = append(refs, ref) 376 refsMu.Unlock() 377 } 378 379 // Loop over the variants of the declaring package, 380 // and perform both the local (in-package) and global 381 // (cross-package) searches, in parallel. 382 // 383 // TODO(adonovan): opt: support LSP reference streaming. See: 384 // - https://github.com/microsoft/vscode-languageserver-node/pull/164 385 // - https://github.com/microsoft/language-server-protocol/pull/182 386 // 387 // Careful: this goroutine must not return before group.Wait. 388 var group errgroup.Group 389 390 // Compute local references for each variant. 391 // The target objects are identified by (URI, offset). 392 for _, mp := range variants { 393 // We want the ordinary importable package, 394 // plus any test-augmented variants, since 395 // declarations in _test.go files may change 396 // the reference of a selection, or even a 397 // field into a method or vice versa. 398 // 399 // But we don't need intermediate test variants, 400 // as their local references will be covered 401 // already by other variants. 402 if mp.IsIntermediateTestVariant() { 403 continue 404 } 405 mp := mp 406 group.Go(func() error { 407 // TODO(adonovan): opt: batch these TypeChecks. 408 pkgs, err := snapshot.TypeCheck(ctx, mp.ID) 409 if err != nil { 410 return err 411 } 412 pkg := pkgs[0] 413 414 // Find the declaration of the corresponding 415 // object in this package based on (URI, offset). 416 pgf, err := pkg.File(declURI) 417 if err != nil { 418 return err 419 } 420 pos, err := safetoken.Pos(pgf.Tok, declPosn.Offset) 421 if err != nil { 422 return err 423 } 424 objects, _, err := objectsAt(pkg.GetTypesInfo(), pgf.File, pos) 425 if err != nil { 426 return err // unreachable? (probably caught earlier) 427 } 428 429 // Report the locations of the declaration(s). 430 // TODO(adonovan): what about for corresponding methods? Add tests. 431 for _, node := range objects { 432 report(mustLocation(pgf, node), true) 433 } 434 435 // Convert targets map to set. 436 targets := make(map[types.Object]bool) 437 for obj := range objects { 438 targets[obj] = true 439 } 440 441 return localReferences(pkg, targets, true, report) 442 }) 443 } 444 445 // Also compute local references within packages that declare 446 // corresponding methods (see above), which expand the global search. 447 // The target objects are identified by (PkgPath, objectpath). 448 for id := range expansions { 449 id := id 450 group.Go(func() error { 451 // TODO(adonovan): opt: batch these TypeChecks. 452 pkgs, err := snapshot.TypeCheck(ctx, id) 453 if err != nil { 454 return err 455 } 456 pkg := pkgs[0] 457 458 targets := make(map[types.Object]bool) 459 for objpath := range globalTargets[pkg.Metadata().PkgPath] { 460 obj, err := objectpath.Object(pkg.GetTypes(), objpath) 461 if err != nil { 462 // No such object, because it was 463 // declared only in the test variant. 464 continue 465 } 466 targets[obj] = true 467 } 468 469 // Don't include corresponding types or methods 470 // since expansions did that already, and we don't 471 // want (e.g.) concrete -> interface -> concrete. 472 const correspond = false 473 return localReferences(pkg, targets, correspond, report) 474 }) 475 } 476 477 // Compute global references for selected reverse dependencies. 478 group.Go(func() error { 479 var globalIDs []PackageID 480 for id := range globalScope { 481 globalIDs = append(globalIDs, id) 482 } 483 indexes, err := snapshot.References(ctx, globalIDs...) 484 if err != nil { 485 return err 486 } 487 for _, index := range indexes { 488 for _, loc := range index.Lookup(globalTargets) { 489 report(loc, false) 490 } 491 } 492 return nil 493 }) 494 495 if err := group.Wait(); err != nil { 496 return nil, err 497 } 498 return refs, nil 499 } 500 501 // expandMethodSearch expands the scope and targets of a global search 502 // for an exported method to include all methods in the workspace 503 // that correspond to it through interface satisfaction. 504 // 505 // Each package that declares a corresponding type is added to 506 // expansions so that we can also find local references to the type 507 // within the package, which of course requires type checking. 508 // 509 // The scope is expanded by a sequence of calls (not concurrent) to addRdeps. 510 // 511 // recv is the method's effective receiver type, for method-set computations. 512 func expandMethodSearch(ctx context.Context, snapshot *cache.Snapshot, workspaceIDs []PackageID, method *types.Func, recv types.Type, addRdeps func(id PackageID, transitive bool) error, targets map[PackagePath]map[objectpath.Path]unit, expansions map[PackageID]unit) error { 513 // Compute the method-set fingerprint used as a key to the global search. 514 key, hasMethods := methodsets.KeyOf(recv) 515 if !hasMethods { 516 return bug.Errorf("KeyOf(%s)={} yet %s is a method", recv, method) 517 } 518 // Search the methodset index of each package in the workspace. 519 indexes, err := snapshot.MethodSets(ctx, workspaceIDs...) 520 if err != nil { 521 return err 522 } 523 var mu sync.Mutex // guards addRdeps, targets, expansions 524 var group errgroup.Group 525 for i, index := range indexes { 526 i := i 527 index := index 528 group.Go(func() error { 529 // Consult index for matching methods. 530 results := index.Search(key, method.Name()) 531 if len(results) == 0 { 532 return nil 533 } 534 535 // We have discovered one or more corresponding types. 536 id := workspaceIDs[i] 537 538 mu.Lock() 539 defer mu.Unlock() 540 541 // Expand global search scope to include rdeps of this pkg. 542 if err := addRdeps(id, true); err != nil { 543 return err 544 } 545 546 // Mark this package so that we search within it for 547 // local references to the additional types/methods. 548 expansions[id] = unit{} 549 550 // Add each corresponding method the to set of global search targets. 551 for _, res := range results { 552 methodPkg := PackagePath(res.PkgPath) 553 opaths, ok := targets[methodPkg] 554 if !ok { 555 opaths = make(map[objectpath.Path]unit) 556 targets[methodPkg] = opaths 557 } 558 opaths[res.ObjectPath] = unit{} 559 } 560 return nil 561 }) 562 } 563 return group.Wait() 564 } 565 566 // localReferences traverses syntax and reports each reference to one 567 // of the target objects, or (if correspond is set) an object that 568 // corresponds to one of them via interface satisfaction. 569 func localReferences(pkg *cache.Package, targets map[types.Object]bool, correspond bool, report func(loc protocol.Location, isDecl bool)) error { 570 // If we're searching for references to a method optionally 571 // broaden the search to include references to corresponding 572 // methods of mutually assignable receiver types. 573 // (We use a slice, but objectsAt never returns >1 methods.) 574 var methodRecvs []types.Type 575 var methodName string // name of an arbitrary target, iff a method 576 if correspond { 577 for obj := range targets { 578 if t := effectiveReceiver(obj); t != nil { 579 methodRecvs = append(methodRecvs, t) 580 methodName = obj.Name() 581 } 582 } 583 } 584 585 // matches reports whether obj either is or corresponds to a target. 586 // (Correspondence is defined as usual for interface methods.) 587 matches := func(obj types.Object) bool { 588 if containsOrigin(targets, obj) { 589 return true 590 } 591 if methodRecvs != nil && obj.Name() == methodName { 592 if orecv := effectiveReceiver(obj); orecv != nil { 593 for _, mrecv := range methodRecvs { 594 if concreteImplementsIntf(orecv, mrecv) { 595 return true 596 } 597 } 598 } 599 } 600 return false 601 } 602 603 // Scan through syntax looking for uses of one of the target objects. 604 for _, pgf := range pkg.CompiledGoFiles() { 605 ast.Inspect(pgf.File, func(n ast.Node) bool { 606 if id, ok := n.(*ast.Ident); ok { 607 if obj, ok := pkg.GetTypesInfo().Uses[id]; ok && matches(obj) { 608 report(mustLocation(pgf, id), false) 609 } 610 } 611 return true 612 }) 613 } 614 return nil 615 } 616 617 // effectiveReceiver returns the effective receiver type for method-set 618 // comparisons for obj, if it is a method, or nil otherwise. 619 func effectiveReceiver(obj types.Object) types.Type { 620 if fn, ok := obj.(*types.Func); ok { 621 if recv := fn.Type().(*types.Signature).Recv(); recv != nil { 622 return methodsets.EnsurePointer(recv.Type()) 623 } 624 } 625 return nil 626 } 627 628 // objectsAt returns the non-empty set of objects denoted (def or use) 629 // by the specified position within a file syntax tree, or an error if 630 // none were found. 631 // 632 // The result may contain more than one element because all case 633 // variables of a type switch appear to be declared at the same 634 // position. 635 // 636 // Each object is mapped to the syntax node that was treated as an 637 // identifier, which is not always an ast.Ident. The second component 638 // of the result is the innermost node enclosing pos. 639 // 640 // TODO(adonovan): factor in common with referencedObject. 641 func objectsAt(info *types.Info, file *ast.File, pos token.Pos) (map[types.Object]ast.Node, ast.Node, error) { 642 path := pathEnclosingObjNode(file, pos) 643 if path == nil { 644 return nil, nil, ErrNoIdentFound 645 } 646 647 targets := make(map[types.Object]ast.Node) 648 649 switch leaf := path[0].(type) { 650 case *ast.Ident: 651 // If leaf represents an implicit type switch object or the type 652 // switch "assign" variable, expand to all of the type switch's 653 // implicit objects. 654 if implicits, _ := typeSwitchImplicits(info, path); len(implicits) > 0 { 655 for _, obj := range implicits { 656 targets[obj] = leaf 657 } 658 } else { 659 // Note: prior to go1.21, go/types issue #60372 causes the position 660 // a field Var T created for struct{*p.T} to be recorded at the 661 // start of the field type ("*") not the location of the T. 662 // This affects references and other gopls operations (issue #60369). 663 // TODO(adonovan): delete this comment when we drop support for go1.20. 664 665 // For struct{T}, we prefer the defined field Var over the used TypeName. 666 obj := info.ObjectOf(leaf) 667 if obj == nil { 668 return nil, nil, fmt.Errorf("%w for %q", errNoObjectFound, leaf.Name) 669 } 670 targets[obj] = leaf 671 } 672 case *ast.ImportSpec: 673 // Look up the implicit *types.PkgName. 674 obj := info.Implicits[leaf] 675 if obj == nil { 676 return nil, nil, fmt.Errorf("%w for import %s", errNoObjectFound, metadata.UnquoteImportPath(leaf)) 677 } 678 targets[obj] = leaf 679 } 680 681 if len(targets) == 0 { 682 return nil, nil, fmt.Errorf("objectAt: internal error: no targets") // can't happen 683 } 684 return targets, path[0], nil 685 } 686 687 // mustLocation reports the location interval a syntax node, 688 // which must belong to m.File. 689 // 690 // Safe for use only by references and implementations. 691 func mustLocation(pgf *ParsedGoFile, n ast.Node) protocol.Location { 692 loc, err := pgf.NodeLocation(n) 693 if err != nil { 694 panic(err) // can't happen in references or implementations 695 } 696 return loc 697 }