golang.org/x/tools/gopls@v0.15.3/internal/cache/metadata/metadata.go (about)

     1  // Copyright 2023 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  // The metadata package defines types and functions for working with package
     6  // metadata, which describes Go packages and their relationships.
     7  //
     8  // Package metadata is loaded by gopls using go/packages, and the [Package]
     9  // type is itself a projection and translation of data from
    10  // go/packages.Package.
    11  //
    12  // Packages are assembled into an immutable [Graph]
    13  package metadata
    14  
    15  import (
    16  	"go/ast"
    17  	"go/types"
    18  	"sort"
    19  	"strconv"
    20  	"strings"
    21  
    22  	"golang.org/x/tools/go/packages"
    23  	"golang.org/x/tools/gopls/internal/protocol"
    24  	"golang.org/x/tools/internal/packagesinternal"
    25  )
    26  
    27  // Declare explicit types for package paths, names, and IDs to ensure that we
    28  // never use an ID where a path belongs, and vice versa. If we confused these,
    29  // it would result in confusing errors because package IDs often look like
    30  // package paths.
    31  type (
    32  	PackageID   string // go list's unique identifier for a package (e.g. "vendor/example.com/foo [vendor/example.com/bar.test]")
    33  	PackagePath string // name used to prefix linker symbols (e.g. "vendor/example.com/foo")
    34  	PackageName string // identifier in 'package' declaration (e.g. "foo")
    35  	ImportPath  string // path that appears in an import declaration (e.g. "example.com/foo")
    36  )
    37  
    38  // Package represents package metadata retrieved from go/packages.
    39  // The DepsBy{Imp,Pkg}Path maps do not contain self-import edges.
    40  //
    41  // An ad-hoc package (without go.mod or GOPATH) has its ID, PkgPath,
    42  // and LoadDir equal to the absolute path of its directory.
    43  type Package struct {
    44  	ID      PackageID
    45  	PkgPath PackagePath
    46  	Name    PackageName
    47  
    48  	// these three fields are as defined by go/packages.Package
    49  	GoFiles         []protocol.DocumentURI
    50  	CompiledGoFiles []protocol.DocumentURI
    51  	IgnoredFiles    []protocol.DocumentURI
    52  
    53  	ForTest       PackagePath // q in a "p [q.test]" package, else ""
    54  	TypesSizes    types.Sizes
    55  	Errors        []packages.Error          // must be set for packages in import cycles
    56  	DepsByImpPath map[ImportPath]PackageID  // may contain dups; empty ID => missing
    57  	DepsByPkgPath map[PackagePath]PackageID // values are unique and non-empty
    58  	Module        *packages.Module
    59  	DepsErrors    []*packagesinternal.PackageError
    60  	LoadDir       string // directory from which go/packages was run
    61  	Standalone    bool   // package synthesized for a standalone file (e.g. ignore-tagged)
    62  }
    63  
    64  func (mp *Package) String() string { return string(mp.ID) }
    65  
    66  // IsIntermediateTestVariant reports whether the given package is an
    67  // intermediate test variant (ITV), e.g. "net/http [net/url.test]".
    68  //
    69  // An ITV has identical syntax to the regular variant, but different
    70  // import metadata (DepsBy{Imp,Pkg}Path).
    71  //
    72  // Such test variants arise when an x_test package (in this case net/url_test)
    73  // imports a package (in this case net/http) that itself imports the
    74  // non-x_test package (in this case net/url).
    75  //
    76  // This is done so that the forward transitive closure of net/url_test has
    77  // only one package for the "net/url" import.
    78  // The ITV exists to hold the test variant import:
    79  //
    80  // net/url_test [net/url.test]
    81  //
    82  //	| "net/http" -> net/http [net/url.test]
    83  //	| "net/url" -> net/url [net/url.test]
    84  //	| ...
    85  //
    86  // net/http [net/url.test]
    87  //
    88  //	| "net/url" -> net/url [net/url.test]
    89  //	| ...
    90  //
    91  // This restriction propagates throughout the import graph of net/http: for
    92  // every package imported by net/http that imports net/url, there must be an
    93  // intermediate test variant that instead imports "net/url [net/url.test]".
    94  //
    95  // As one can see from the example of net/url and net/http, intermediate test
    96  // variants can result in many additional packages that are essentially (but
    97  // not quite) identical. For this reason, we filter these variants wherever
    98  // possible.
    99  //
   100  // # Why we mostly ignore intermediate test variants
   101  //
   102  // In projects with complicated tests, there may be a very large
   103  // number of ITVs--asymptotically more than the number of ordinary
   104  // variants. Since they have identical syntax, it is fine in most
   105  // cases to ignore them since the results of analyzing the ordinary
   106  // variant suffice. However, this is not entirely sound.
   107  //
   108  // Consider this package:
   109  //
   110  //	// p/p.go -- in all variants of p
   111  //	package p
   112  //	type T struct { io.Closer }
   113  //
   114  //	// p/p_test.go -- in test variant of p
   115  //	package p
   116  //	func (T) Close() error { ... }
   117  //
   118  // The ordinary variant "p" defines T with a Close method promoted
   119  // from io.Closer. But its test variant "p [p.test]" defines a type T
   120  // with a Close method from p_test.go.
   121  //
   122  // Now consider a package q that imports p, perhaps indirectly. Within
   123  // it, T.Close will resolve to the first Close method:
   124  //
   125  //	// q/q.go -- in all variants of q
   126  //	package q
   127  //	import "p"
   128  //	var _ = new(p.T).Close
   129  //
   130  // Let's assume p also contains this file defining an external test (xtest):
   131  //
   132  //	// p/p_x_test.go -- external test of p
   133  //	package p_test
   134  //	import ( "q"; "testing" )
   135  //	func Test(t *testing.T) { ... }
   136  //
   137  // Note that q imports p, but p's xtest imports q. Now, in "q
   138  // [p.test]", the intermediate test variant of q built for p's
   139  // external test, T.Close resolves not to the io.Closer.Close
   140  // interface method, but to the concrete method of T.Close
   141  // declared in p_test.go.
   142  //
   143  // If we now request all references to the T.Close declaration in
   144  // p_test.go, the result should include the reference from q's ITV.
   145  // (It's not just methods that can be affected; fields can too, though
   146  // it requires bizarre code to achieve.)
   147  //
   148  // As a matter of policy, gopls mostly ignores this subtlety,
   149  // because to account for it would require that we type-check every
   150  // intermediate test variant of p, of which there could be many.
   151  // Good code doesn't rely on such trickery.
   152  //
   153  // Most callers of MetadataForFile call RemoveIntermediateTestVariants
   154  // to discard them before requesting type checking, or the products of
   155  // type-checking such as the cross-reference index or method set index.
   156  //
   157  // MetadataForFile doesn't do this filtering itself becaused in some
   158  // cases we need to make a reverse dependency query on the metadata
   159  // graph, and it's important to include the rdeps of ITVs in that
   160  // query. But the filtering of ITVs should be applied after that step,
   161  // before type checking.
   162  //
   163  // In general, we should never type check an ITV.
   164  func (mp *Package) IsIntermediateTestVariant() bool {
   165  	return mp.ForTest != "" && mp.ForTest != mp.PkgPath && mp.ForTest+"_test" != mp.PkgPath
   166  }
   167  
   168  // A Source maps package IDs to metadata for the packages.
   169  //
   170  // TODO(rfindley): replace this with a concrete metadata graph, once it is
   171  // exposed from the snapshot.
   172  type Source interface {
   173  	// Metadata returns the [Package] for the given package ID, or nil if it does
   174  	// not exist.
   175  	// TODO(rfindley): consider returning (*Metadata, bool)
   176  	// TODO(rfindley): consider renaming this method.
   177  	Metadata(PackageID) *Package
   178  }
   179  
   180  // TODO(rfindley): move the utility functions below to a util.go file.
   181  
   182  // IsCommandLineArguments reports whether a given value denotes
   183  // "command-line-arguments" package, which is a package with an unknown ID
   184  // created by the go command. It can have a test variant, which is why callers
   185  // should not check that a value equals "command-line-arguments" directly.
   186  func IsCommandLineArguments(id PackageID) bool {
   187  	return strings.Contains(string(id), "command-line-arguments")
   188  }
   189  
   190  // SortPostOrder sorts the IDs so that if x depends on y, then y appears before x.
   191  func SortPostOrder(meta Source, ids []PackageID) {
   192  	postorder := make(map[PackageID]int)
   193  	order := 0
   194  	var visit func(PackageID)
   195  	visit = func(id PackageID) {
   196  		if _, ok := postorder[id]; !ok {
   197  			postorder[id] = -1 // break recursion
   198  			if mp := meta.Metadata(id); mp != nil {
   199  				for _, depID := range mp.DepsByPkgPath {
   200  					visit(depID)
   201  				}
   202  			}
   203  			order++
   204  			postorder[id] = order
   205  		}
   206  	}
   207  	for _, id := range ids {
   208  		visit(id)
   209  	}
   210  	sort.Slice(ids, func(i, j int) bool {
   211  		return postorder[ids[i]] < postorder[ids[j]]
   212  	})
   213  }
   214  
   215  // UnquoteImportPath returns the unquoted import path of s,
   216  // or "" if the path is not properly quoted.
   217  func UnquoteImportPath(spec *ast.ImportSpec) ImportPath {
   218  	path, err := strconv.Unquote(spec.Path.Value)
   219  	if err != nil {
   220  		return ""
   221  	}
   222  	return ImportPath(path)
   223  }
   224  
   225  // RemoveIntermediateTestVariants removes intermediate test variants, modifying
   226  // the array. We use a pointer to a slice make it impossible to forget to use
   227  // the result.
   228  func RemoveIntermediateTestVariants(pmetas *[]*Package) {
   229  	metas := *pmetas
   230  	res := metas[:0]
   231  	for _, mp := range metas {
   232  		if !mp.IsIntermediateTestVariant() {
   233  			res = append(res, mp)
   234  		}
   235  	}
   236  	*pmetas = res
   237  }
   238  
   239  // IsValidImport returns whether importPkgPath is importable
   240  // by pkgPath.
   241  func IsValidImport(pkgPath, importPkgPath PackagePath) bool {
   242  	i := strings.LastIndex(string(importPkgPath), "/internal/")
   243  	if i == -1 {
   244  		return true
   245  	}
   246  	// TODO(rfindley): this looks wrong: IsCommandLineArguments is meant to
   247  	// operate on package IDs, not package paths.
   248  	if IsCommandLineArguments(PackageID(pkgPath)) {
   249  		return true
   250  	}
   251  	// TODO(rfindley): this is wrong. mod.testx/p should not be able to
   252  	// import mod.test/internal: https://go.dev/play/p/-Ca6P-E4V4q
   253  	return strings.HasPrefix(string(pkgPath), string(importPkgPath[:i]))
   254  }