golang.org/x/tools/gopls@v0.15.3/internal/cache/xrefs/xrefs.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 xrefs defines the serializable index of cross-package
     6  // references that is computed during type checking.
     7  //
     8  // See ../references.go for the 'references' query.
     9  package xrefs
    10  
    11  import (
    12  	"go/ast"
    13  	"go/types"
    14  	"sort"
    15  
    16  	"golang.org/x/tools/go/types/objectpath"
    17  	"golang.org/x/tools/gopls/internal/cache/metadata"
    18  	"golang.org/x/tools/gopls/internal/cache/parsego"
    19  	"golang.org/x/tools/gopls/internal/protocol"
    20  	"golang.org/x/tools/gopls/internal/util/frob"
    21  	"golang.org/x/tools/gopls/internal/util/typesutil"
    22  	"golang.org/x/tools/internal/typeparams"
    23  )
    24  
    25  // Index constructs a serializable index of outbound cross-references
    26  // for the specified type-checked package.
    27  func Index(files []*parsego.File, pkg *types.Package, info *types.Info) []byte {
    28  	// pkgObjects maps each referenced package Q to a mapping:
    29  	// from each referenced symbol in Q to the ordered list
    30  	// of references to that symbol from this package.
    31  	// A nil types.Object indicates a reference
    32  	// to the package as a whole: an import.
    33  	pkgObjects := make(map[*types.Package]map[types.Object]*gobObject)
    34  
    35  	// getObjects returns the object-to-references mapping for a package.
    36  	getObjects := func(pkg *types.Package) map[types.Object]*gobObject {
    37  		objects, ok := pkgObjects[pkg]
    38  		if !ok {
    39  			objects = make(map[types.Object]*gobObject)
    40  			pkgObjects[pkg] = objects
    41  		}
    42  		return objects
    43  	}
    44  
    45  	objectpathFor := new(objectpath.Encoder).For
    46  
    47  	for fileIndex, pgf := range files {
    48  
    49  		nodeRange := func(n ast.Node) protocol.Range {
    50  			rng, err := pgf.PosRange(n.Pos(), n.End())
    51  			if err != nil {
    52  				panic(err) // can't fail
    53  			}
    54  			return rng
    55  		}
    56  
    57  		ast.Inspect(pgf.File, func(n ast.Node) bool {
    58  			switch n := n.(type) {
    59  			case *ast.Ident:
    60  				// Report a reference for each identifier that
    61  				// uses a symbol exported from another package.
    62  				// (The built-in error.Error method has no package.)
    63  				if n.IsExported() {
    64  					if obj, ok := info.Uses[n]; ok &&
    65  						obj.Pkg() != nil &&
    66  						obj.Pkg() != pkg {
    67  
    68  						// For instantiations of generic methods,
    69  						// use the generic object (see issue #60622).
    70  						if fn, ok := obj.(*types.Func); ok {
    71  							obj = typeparams.OriginMethod(fn)
    72  						}
    73  
    74  						objects := getObjects(obj.Pkg())
    75  						gobObj, ok := objects[obj]
    76  						if !ok {
    77  							path, err := objectpathFor(obj)
    78  							if err != nil {
    79  								// Capitalized but not exported
    80  								// (e.g. local const/var/type).
    81  								return true
    82  							}
    83  							gobObj = &gobObject{Path: path}
    84  							objects[obj] = gobObj
    85  						}
    86  
    87  						gobObj.Refs = append(gobObj.Refs, gobRef{
    88  							FileIndex: fileIndex,
    89  							Range:     nodeRange(n),
    90  						})
    91  					}
    92  				}
    93  
    94  			case *ast.ImportSpec:
    95  				// Report a reference from each import path
    96  				// string to the imported package.
    97  				pkgname, ok := typesutil.ImportedPkgName(info, n)
    98  				if !ok {
    99  					return true // missing import
   100  				}
   101  				objects := getObjects(pkgname.Imported())
   102  				gobObj, ok := objects[nil]
   103  				if !ok {
   104  					gobObj = &gobObject{Path: ""}
   105  					objects[nil] = gobObj
   106  				}
   107  				gobObj.Refs = append(gobObj.Refs, gobRef{
   108  					FileIndex: fileIndex,
   109  					Range:     nodeRange(n.Path),
   110  				})
   111  			}
   112  			return true
   113  		})
   114  	}
   115  
   116  	// Flatten the maps into slices, and sort for determinism.
   117  	var packages []*gobPackage
   118  	for p := range pkgObjects {
   119  		objects := pkgObjects[p]
   120  		gp := &gobPackage{
   121  			PkgPath: metadata.PackagePath(p.Path()),
   122  			Objects: make([]*gobObject, 0, len(objects)),
   123  		}
   124  		for _, gobObj := range objects {
   125  			gp.Objects = append(gp.Objects, gobObj)
   126  		}
   127  		sort.Slice(gp.Objects, func(i, j int) bool {
   128  			return gp.Objects[i].Path < gp.Objects[j].Path
   129  		})
   130  		packages = append(packages, gp)
   131  	}
   132  	sort.Slice(packages, func(i, j int) bool {
   133  		return packages[i].PkgPath < packages[j].PkgPath
   134  	})
   135  
   136  	return packageCodec.Encode(packages)
   137  }
   138  
   139  // Lookup searches a serialized index produced by an indexPackage
   140  // operation on m, and returns the locations of all references from m
   141  // to any object in the target set. Each object is denoted by a pair
   142  // of (package path, object path).
   143  func Lookup(mp *metadata.Package, data []byte, targets map[metadata.PackagePath]map[objectpath.Path]struct{}) (locs []protocol.Location) {
   144  	var packages []*gobPackage
   145  	packageCodec.Decode(data, &packages)
   146  	for _, gp := range packages {
   147  		if objectSet, ok := targets[gp.PkgPath]; ok {
   148  			for _, gobObj := range gp.Objects {
   149  				if _, ok := objectSet[gobObj.Path]; ok {
   150  					for _, ref := range gobObj.Refs {
   151  						uri := mp.CompiledGoFiles[ref.FileIndex]
   152  						locs = append(locs, protocol.Location{
   153  							URI:   uri,
   154  							Range: ref.Range,
   155  						})
   156  					}
   157  				}
   158  			}
   159  		}
   160  	}
   161  
   162  	return locs
   163  }
   164  
   165  // -- serialized representation --
   166  
   167  // The cross-reference index records the location of all references
   168  // from one package to symbols defined in other packages
   169  // (dependencies). It does not record within-package references.
   170  // The index for package P consists of a list of gopPackage records,
   171  // each enumerating references to symbols defined a single dependency, Q.
   172  
   173  // TODO(adonovan): opt: choose a more compact encoding.
   174  // The gobRef.Range field is the obvious place to begin.
   175  
   176  // (The name says gob but in fact we use frob.)
   177  var packageCodec = frob.CodecFor[[]*gobPackage]()
   178  
   179  // A gobPackage records the set of outgoing references from the index
   180  // package to symbols defined in a dependency package.
   181  type gobPackage struct {
   182  	PkgPath metadata.PackagePath // defining package (Q)
   183  	Objects []*gobObject         // set of Q objects referenced by P
   184  }
   185  
   186  // A gobObject records all references to a particular symbol.
   187  type gobObject struct {
   188  	Path objectpath.Path // symbol name within package; "" => import of package itself
   189  	Refs []gobRef        // locations of references within P, in lexical order
   190  }
   191  
   192  type gobRef struct {
   193  	FileIndex int            // index of enclosing file within P's CompiledGoFiles
   194  	Range     protocol.Range // source range of reference
   195  }