golang.org/x/tools/gopls@v0.15.3/internal/cache/load.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 cache
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"errors"
    11  	"fmt"
    12  	"path/filepath"
    13  	"sort"
    14  	"strings"
    15  	"sync/atomic"
    16  	"time"
    17  
    18  	"golang.org/x/tools/go/packages"
    19  	"golang.org/x/tools/gopls/internal/cache/metadata"
    20  	"golang.org/x/tools/gopls/internal/file"
    21  	"golang.org/x/tools/gopls/internal/protocol"
    22  	"golang.org/x/tools/gopls/internal/util/bug"
    23  	"golang.org/x/tools/gopls/internal/util/immutable"
    24  	"golang.org/x/tools/gopls/internal/util/pathutil"
    25  	"golang.org/x/tools/gopls/internal/util/slices"
    26  	"golang.org/x/tools/internal/event"
    27  	"golang.org/x/tools/internal/event/tag"
    28  	"golang.org/x/tools/internal/gocommand"
    29  	"golang.org/x/tools/internal/packagesinternal"
    30  	"golang.org/x/tools/internal/xcontext"
    31  )
    32  
    33  var loadID uint64 // atomic identifier for loads
    34  
    35  // errNoPackages indicates that a load query matched no packages.
    36  var errNoPackages = errors.New("no packages returned")
    37  
    38  // load calls packages.Load for the given scopes, updating package metadata,
    39  // import graph, and mapped files with the result.
    40  //
    41  // The resulting error may wrap the moduleErrorMap error type, representing
    42  // errors associated with specific modules.
    43  //
    44  // If scopes contains a file scope there must be exactly one scope.
    45  func (s *Snapshot) load(ctx context.Context, allowNetwork bool, scopes ...loadScope) (err error) {
    46  	id := atomic.AddUint64(&loadID, 1)
    47  	eventName := fmt.Sprintf("go/packages.Load #%d", id) // unique name for logging
    48  
    49  	var query []string
    50  	var containsDir bool // for logging
    51  	var standalone bool  // whether this is a load of a standalone file
    52  
    53  	// Keep track of module query -> module path so that we can later correlate query
    54  	// errors with errors.
    55  	moduleQueries := make(map[string]string)
    56  	for _, scope := range scopes {
    57  		switch scope := scope.(type) {
    58  		case packageLoadScope:
    59  			// The only time we pass package paths is when we're doing a
    60  			// partial workspace load. In those cases, the paths came back from
    61  			// go list and should already be GOPATH-vendorized when appropriate.
    62  			query = append(query, string(scope))
    63  
    64  		case fileLoadScope:
    65  			// Given multiple scopes, the resulting load might contain inaccurate
    66  			// information. For example go/packages returns at most one command-line
    67  			// arguments package, and does not handle a combination of standalone
    68  			// files and packages.
    69  			uri := protocol.DocumentURI(scope)
    70  			if len(scopes) > 1 {
    71  				panic(fmt.Sprintf("internal error: load called with multiple scopes when a file scope is present (file: %s)", uri))
    72  			}
    73  			fh := s.FindFile(uri)
    74  			if fh == nil || s.FileKind(fh) != file.Go {
    75  				// Don't try to load a file that doesn't exist, or isn't a go file.
    76  				continue
    77  			}
    78  			contents, err := fh.Content()
    79  			if err != nil {
    80  				continue
    81  			}
    82  			if isStandaloneFile(contents, s.Options().StandaloneTags) {
    83  				standalone = true
    84  				query = append(query, uri.Path())
    85  			} else {
    86  				query = append(query, fmt.Sprintf("file=%s", uri.Path()))
    87  			}
    88  
    89  		case moduleLoadScope:
    90  			modQuery := fmt.Sprintf("%s%c...", scope.dir, filepath.Separator)
    91  			query = append(query, modQuery)
    92  			moduleQueries[modQuery] = scope.modulePath
    93  
    94  		case viewLoadScope:
    95  			// If we are outside of GOPATH, a module, or some other known
    96  			// build system, don't load subdirectories.
    97  			if s.view.typ == AdHocView {
    98  				query = append(query, "./")
    99  			} else {
   100  				query = append(query, "./...")
   101  			}
   102  
   103  		default:
   104  			panic(fmt.Sprintf("unknown scope type %T", scope))
   105  		}
   106  		switch scope.(type) {
   107  		case viewLoadScope, moduleLoadScope:
   108  			containsDir = true
   109  		}
   110  	}
   111  	if len(query) == 0 {
   112  		return nil
   113  	}
   114  	sort.Strings(query) // for determinism
   115  
   116  	ctx, done := event.Start(ctx, "cache.snapshot.load", tag.Query.Of(query))
   117  	defer done()
   118  
   119  	flags := LoadWorkspace
   120  	if allowNetwork {
   121  		flags |= AllowNetwork
   122  	}
   123  	_, inv, cleanup, err := s.goCommandInvocation(ctx, flags, &gocommand.Invocation{
   124  		WorkingDir: s.view.root.Path(),
   125  	})
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	// Set a last resort deadline on packages.Load since it calls the go
   131  	// command, which may hang indefinitely if it has a bug. golang/go#42132
   132  	// and golang/go#42255 have more context.
   133  	ctx, cancel := context.WithTimeout(ctx, 10*time.Minute)
   134  	defer cancel()
   135  
   136  	cfg := s.config(ctx, inv)
   137  	pkgs, err := packages.Load(cfg, query...)
   138  	cleanup()
   139  
   140  	// If the context was canceled, return early. Otherwise, we might be
   141  	// type-checking an incomplete result. Check the context directly,
   142  	// because go/packages adds extra information to the error.
   143  	if ctx.Err() != nil {
   144  		return ctx.Err()
   145  	}
   146  
   147  	// This log message is sought for by TestReloadOnlyOnce.
   148  	labels := append(s.Labels(), tag.Query.Of(query), tag.PackageCount.Of(len(pkgs)))
   149  	if err != nil {
   150  		event.Error(ctx, eventName, err, labels...)
   151  	} else {
   152  		event.Log(ctx, eventName, labels...)
   153  	}
   154  
   155  	if standalone {
   156  		// Handle standalone package result.
   157  		//
   158  		// In general, this should just be a single "command-line-arguments"
   159  		// package containing the requested file. However, if the file is a test
   160  		// file, go/packages may return test variants of the command-line-arguments
   161  		// package. We don't support this; theoretically we could, but it seems
   162  		// unnecessarily complicated.
   163  		//
   164  		// Prior to golang/go#64233 we just assumed that we'd get exactly one
   165  		// package here. The categorization of bug reports below may be a bit
   166  		// verbose, but anticipates that perhaps we don't fully understand
   167  		// possible failure modes.
   168  		errorf := bug.Errorf
   169  		if s.view.typ == GoPackagesDriverView {
   170  			errorf = fmt.Errorf // all bets are off
   171  		}
   172  
   173  		var standalonePkg *packages.Package
   174  		for _, pkg := range pkgs {
   175  			if pkg.ID == "command-line-arguments" {
   176  				if standalonePkg != nil {
   177  					return errorf("internal error: go/packages returned multiple standalone packages")
   178  				}
   179  				standalonePkg = pkg
   180  			} else if packagesinternal.GetForTest(pkg) == "" && !strings.HasSuffix(pkg.ID, ".test") {
   181  				return errorf("internal error: go/packages returned unexpected package %q for standalone file", pkg.ID)
   182  			}
   183  		}
   184  		if standalonePkg == nil {
   185  			return errorf("internal error: go/packages failed to return non-test standalone package")
   186  		}
   187  		if len(standalonePkg.CompiledGoFiles) > 0 {
   188  			pkgs = []*packages.Package{standalonePkg}
   189  		} else {
   190  			pkgs = nil
   191  		}
   192  	}
   193  
   194  	if len(pkgs) == 0 {
   195  		if err == nil {
   196  			err = errNoPackages
   197  		}
   198  		return fmt.Errorf("packages.Load error: %w", err)
   199  	}
   200  
   201  	moduleErrs := make(map[string][]packages.Error) // module path -> errors
   202  	filterFunc := s.view.filterFunc()
   203  	newMetadata := make(map[PackageID]*metadata.Package)
   204  	for _, pkg := range pkgs {
   205  		// The Go command returns synthetic list results for module queries that
   206  		// encountered module errors.
   207  		//
   208  		// For example, given a module path a.mod, we'll query for "a.mod/..." and
   209  		// the go command will return a package named "a.mod/..." holding this
   210  		// error. Save it for later interpretation.
   211  		//
   212  		// See golang/go#50862 for more details.
   213  		if mod := moduleQueries[pkg.PkgPath]; mod != "" { // a synthetic result for the unloadable module
   214  			if len(pkg.Errors) > 0 {
   215  				moduleErrs[mod] = pkg.Errors
   216  			}
   217  			continue
   218  		}
   219  
   220  		if !containsDir || s.Options().VerboseOutput {
   221  			event.Log(ctx, eventName, append(
   222  				s.Labels(),
   223  				tag.Package.Of(pkg.ID),
   224  				tag.Files.Of(pkg.CompiledGoFiles))...)
   225  		}
   226  
   227  		// Ignore packages with no sources, since we will never be able to
   228  		// correctly invalidate that metadata.
   229  		if len(pkg.GoFiles) == 0 && len(pkg.CompiledGoFiles) == 0 {
   230  			continue
   231  		}
   232  		// Special case for the builtin package, as it has no dependencies.
   233  		if pkg.PkgPath == "builtin" {
   234  			if len(pkg.GoFiles) != 1 {
   235  				return fmt.Errorf("only expected 1 file for builtin, got %v", len(pkg.GoFiles))
   236  			}
   237  			s.setBuiltin(pkg.GoFiles[0])
   238  			continue
   239  		}
   240  		// Skip test main packages.
   241  		if isTestMain(pkg, s.view.folder.Env.GOCACHE) {
   242  			continue
   243  		}
   244  		// Skip filtered packages. They may be added anyway if they're
   245  		// dependencies of non-filtered packages.
   246  		//
   247  		// TODO(rfindley): why exclude metadata arbitrarily here? It should be safe
   248  		// to capture all metadata.
   249  		// TODO(rfindley): what about compiled go files?
   250  		if allFilesExcluded(pkg.GoFiles, filterFunc) {
   251  			continue
   252  		}
   253  		buildMetadata(newMetadata, pkg, cfg.Dir, standalone)
   254  	}
   255  
   256  	s.mu.Lock()
   257  
   258  	// Assert the invariant s.packages.Get(id).m == s.meta.metadata[id].
   259  	s.packages.Range(func(id PackageID, ph *packageHandle) {
   260  		if s.meta.Packages[id] != ph.mp {
   261  			panic("inconsistent metadata")
   262  		}
   263  	})
   264  
   265  	// Compute the minimal metadata updates (for Clone)
   266  	// required to preserve the above invariant.
   267  	var files []protocol.DocumentURI // files to preload
   268  	seenFiles := make(map[protocol.DocumentURI]bool)
   269  	updates := make(map[PackageID]*metadata.Package)
   270  	for _, mp := range newMetadata {
   271  		if existing := s.meta.Packages[mp.ID]; existing == nil {
   272  			// Record any new files we should pre-load.
   273  			for _, uri := range mp.CompiledGoFiles {
   274  				if !seenFiles[uri] {
   275  					seenFiles[uri] = true
   276  					files = append(files, uri)
   277  				}
   278  			}
   279  			updates[mp.ID] = mp
   280  			s.shouldLoad.Delete(mp.ID)
   281  		}
   282  	}
   283  
   284  	event.Log(ctx, fmt.Sprintf("%s: updating metadata for %d packages", eventName, len(updates)))
   285  
   286  	meta := s.meta.Update(updates)
   287  	workspacePackages := computeWorkspacePackagesLocked(ctx, s, meta)
   288  	s.meta = meta
   289  	s.workspacePackages = workspacePackages
   290  	s.resetActivePackagesLocked()
   291  
   292  	s.mu.Unlock()
   293  
   294  	// Opt: preLoad files in parallel.
   295  	//
   296  	// Requesting files in batch optimizes the underlying filesystem reads.
   297  	// However, this is also currently necessary for correctness: populating all
   298  	// files in the snapshot is necessary for certain operations that rely on the
   299  	// completeness of the file map, e.g. computing the set of directories to
   300  	// watch.
   301  	//
   302  	// TODO(rfindley, golang/go#57558): determine the set of directories based on
   303  	// loaded packages, so that reading files here is not necessary for
   304  	// correctness.
   305  	s.preloadFiles(ctx, files)
   306  
   307  	if len(moduleErrs) > 0 {
   308  		return &moduleErrorMap{moduleErrs}
   309  	}
   310  
   311  	return nil
   312  }
   313  
   314  type moduleErrorMap struct {
   315  	errs map[string][]packages.Error // module path -> errors
   316  }
   317  
   318  func (m *moduleErrorMap) Error() string {
   319  	var paths []string // sort for stability
   320  	for path, errs := range m.errs {
   321  		if len(errs) > 0 { // should always be true, but be cautious
   322  			paths = append(paths, path)
   323  		}
   324  	}
   325  	sort.Strings(paths)
   326  
   327  	var buf bytes.Buffer
   328  	fmt.Fprintf(&buf, "%d modules have errors:\n", len(paths))
   329  	for _, path := range paths {
   330  		fmt.Fprintf(&buf, "\t%s:%s\n", path, m.errs[path][0].Msg)
   331  	}
   332  
   333  	return buf.String()
   334  }
   335  
   336  // buildMetadata populates the updates map with metadata updates to
   337  // apply, based on the given pkg. It recurs through pkg.Imports to ensure that
   338  // metadata exists for all dependencies.
   339  //
   340  // Returns the metadata.Package that was built (or which was already present in
   341  // updates), or nil if the package could not be built. Notably, the resulting
   342  // metadata.Package may have an ID that differs from pkg.ID.
   343  func buildMetadata(updates map[PackageID]*metadata.Package, pkg *packages.Package, loadDir string, standalone bool) *metadata.Package {
   344  	// Allow for multiple ad-hoc packages in the workspace (see #47584).
   345  	pkgPath := PackagePath(pkg.PkgPath)
   346  	id := PackageID(pkg.ID)
   347  
   348  	if metadata.IsCommandLineArguments(id) {
   349  		var f string // file to use as disambiguating suffix
   350  		if len(pkg.CompiledGoFiles) > 0 {
   351  			f = pkg.CompiledGoFiles[0]
   352  
   353  			// If there are multiple files,
   354  			// we can't use only the first.
   355  			// (Can this happen? #64557)
   356  			if len(pkg.CompiledGoFiles) > 1 {
   357  				bug.Reportf("unexpected files in command-line-arguments package: %v", pkg.CompiledGoFiles)
   358  				return nil
   359  			}
   360  		} else if len(pkg.IgnoredFiles) > 0 {
   361  			// A file=empty.go query results in IgnoredFiles=[empty.go].
   362  			f = pkg.IgnoredFiles[0]
   363  		} else {
   364  			bug.Reportf("command-line-arguments package has neither CompiledGoFiles nor IgnoredFiles: %#v", "") //*pkg.Metadata)
   365  			return nil
   366  		}
   367  		id = PackageID(pkg.ID + f)
   368  		pkgPath = PackagePath(pkg.PkgPath + f)
   369  	}
   370  
   371  	// Duplicate?
   372  	if existing, ok := updates[id]; ok {
   373  		// A package was encountered twice due to shared
   374  		// subgraphs (common) or cycles (rare). Although "go
   375  		// list" usually breaks cycles, we don't rely on it.
   376  		// breakImportCycles in metadataGraph.Clone takes care
   377  		// of it later.
   378  		return existing
   379  	}
   380  
   381  	if pkg.TypesSizes == nil {
   382  		panic(id + ".TypeSizes is nil")
   383  	}
   384  
   385  	// Recreate the metadata rather than reusing it to avoid locking.
   386  	mp := &metadata.Package{
   387  		ID:         id,
   388  		PkgPath:    pkgPath,
   389  		Name:       PackageName(pkg.Name),
   390  		ForTest:    PackagePath(packagesinternal.GetForTest(pkg)),
   391  		TypesSizes: pkg.TypesSizes,
   392  		LoadDir:    loadDir,
   393  		Module:     pkg.Module,
   394  		Errors:     pkg.Errors,
   395  		DepsErrors: packagesinternal.GetDepsErrors(pkg),
   396  		Standalone: standalone,
   397  	}
   398  
   399  	updates[id] = mp
   400  
   401  	for _, filename := range pkg.CompiledGoFiles {
   402  		uri := protocol.URIFromPath(filename)
   403  		mp.CompiledGoFiles = append(mp.CompiledGoFiles, uri)
   404  	}
   405  	for _, filename := range pkg.GoFiles {
   406  		uri := protocol.URIFromPath(filename)
   407  		mp.GoFiles = append(mp.GoFiles, uri)
   408  	}
   409  	for _, filename := range pkg.IgnoredFiles {
   410  		uri := protocol.URIFromPath(filename)
   411  		mp.IgnoredFiles = append(mp.IgnoredFiles, uri)
   412  	}
   413  
   414  	depsByImpPath := make(map[ImportPath]PackageID)
   415  	depsByPkgPath := make(map[PackagePath]PackageID)
   416  	for importPath, imported := range pkg.Imports {
   417  		importPath := ImportPath(importPath)
   418  
   419  		// It is not an invariant that importPath == imported.PkgPath.
   420  		// For example, package "net" imports "golang.org/x/net/dns/dnsmessage"
   421  		// which refers to the package whose ID and PkgPath are both
   422  		// "vendor/golang.org/x/net/dns/dnsmessage". Notice the ImportMap,
   423  		// which maps ImportPaths to PackagePaths:
   424  		//
   425  		// $ go list -json net vendor/golang.org/x/net/dns/dnsmessage
   426  		// {
   427  		// 	"ImportPath": "net",
   428  		// 	"Name": "net",
   429  		// 	"Imports": [
   430  		// 		"C",
   431  		// 		"vendor/golang.org/x/net/dns/dnsmessage",
   432  		// 		"vendor/golang.org/x/net/route",
   433  		// 		...
   434  		// 	],
   435  		// 	"ImportMap": {
   436  		// 		"golang.org/x/net/dns/dnsmessage": "vendor/golang.org/x/net/dns/dnsmessage",
   437  		// 		"golang.org/x/net/route": "vendor/golang.org/x/net/route"
   438  		// 	},
   439  		//      ...
   440  		// }
   441  		// {
   442  		// 	"ImportPath": "vendor/golang.org/x/net/dns/dnsmessage",
   443  		// 	"Name": "dnsmessage",
   444  		//      ...
   445  		// }
   446  		//
   447  		// (Beware that, for historical reasons, go list uses
   448  		// the JSON field "ImportPath" for the package's
   449  		// path--effectively the linker symbol prefix.)
   450  		//
   451  		// The example above is slightly special to go list
   452  		// because it's in the std module.  Otherwise,
   453  		// vendored modules are simply modules whose directory
   454  		// is vendor/ instead of GOMODCACHE, and the
   455  		// import path equals the package path.
   456  		//
   457  		// But in GOPATH (non-module) mode, it's possible for
   458  		// package vendoring to cause a non-identity ImportMap,
   459  		// as in this example:
   460  		//
   461  		// $ cd $HOME/src
   462  		// $ find . -type f
   463  		// ./b/b.go
   464  		// ./vendor/example.com/a/a.go
   465  		// $ cat ./b/b.go
   466  		// package b
   467  		// import _ "example.com/a"
   468  		// $ cat ./vendor/example.com/a/a.go
   469  		// package a
   470  		// $ GOPATH=$HOME GO111MODULE=off go list -json ./b | grep -A2 ImportMap
   471  		//     "ImportMap": {
   472  		//         "example.com/a": "vendor/example.com/a"
   473  		//     },
   474  
   475  		// Don't remember any imports with significant errors.
   476  		//
   477  		// The len=0 condition is a heuristic check for imports of
   478  		// non-existent packages (for which go/packages will create
   479  		// an edge to a synthesized node). The heuristic is unsound
   480  		// because some valid packages have zero files, for example,
   481  		// a directory containing only the file p_test.go defines an
   482  		// empty package p.
   483  		// TODO(adonovan): clarify this. Perhaps go/packages should
   484  		// report which nodes were synthesized.
   485  		if importPath != "unsafe" && len(imported.CompiledGoFiles) == 0 {
   486  			depsByImpPath[importPath] = "" // missing
   487  			continue
   488  		}
   489  
   490  		// Don't record self-import edges.
   491  		// (This simplifies metadataGraph's cycle check.)
   492  		if PackageID(imported.ID) == id {
   493  			if len(pkg.Errors) == 0 {
   494  				bug.Reportf("self-import without error in package %s", id)
   495  			}
   496  			continue
   497  		}
   498  
   499  		dep := buildMetadata(updates, imported, loadDir, false) // only top level packages can be standalone
   500  
   501  		// Don't record edges to packages with no name, as they cause trouble for
   502  		// the importer (golang/go#60952).
   503  		//
   504  		// Also don't record edges to packages whose ID was modified (i.e.
   505  		// command-line-arguments packages), as encountered in golang/go#66109. In
   506  		// this case, we could theoretically keep the edge through dep.ID, but
   507  		// since this import doesn't make any sense in the first place, we instead
   508  		// choose to consider it invalid.
   509  		//
   510  		// However, we do want to insert these packages into the update map
   511  		// (buildMetadata above), so that we get type-checking diagnostics for the
   512  		// invalid packages.
   513  		if dep == nil || dep.ID != PackageID(imported.ID) || imported.Name == "" {
   514  			depsByImpPath[importPath] = "" // missing
   515  			continue
   516  		}
   517  
   518  		depsByImpPath[importPath] = PackageID(imported.ID)
   519  		depsByPkgPath[PackagePath(imported.PkgPath)] = PackageID(imported.ID)
   520  	}
   521  	mp.DepsByImpPath = depsByImpPath
   522  	mp.DepsByPkgPath = depsByPkgPath
   523  	return mp
   524  
   525  	// m.Diagnostics is set later in the loading pass, using
   526  	// computeLoadDiagnostics.
   527  }
   528  
   529  // computeLoadDiagnostics computes and sets m.Diagnostics for the given metadata m.
   530  //
   531  // It should only be called during package handle construction in buildPackageHandle.
   532  func computeLoadDiagnostics(ctx context.Context, snapshot *Snapshot, mp *metadata.Package) []*Diagnostic {
   533  	var diags []*Diagnostic
   534  	for _, packagesErr := range mp.Errors {
   535  		// Filter out parse errors from go list. We'll get them when we
   536  		// actually parse, and buggy overlay support may generate spurious
   537  		// errors. (See TestNewModule_Issue38207.)
   538  		if strings.Contains(packagesErr.Msg, "expected '") {
   539  			continue
   540  		}
   541  		pkgDiags, err := goPackagesErrorDiagnostics(ctx, packagesErr, mp, snapshot)
   542  		if err != nil {
   543  			// There are certain cases where the go command returns invalid
   544  			// positions, so we cannot panic or even bug.Reportf here.
   545  			event.Error(ctx, "unable to compute positions for list errors", err, tag.Package.Of(string(mp.ID)))
   546  			continue
   547  		}
   548  		diags = append(diags, pkgDiags...)
   549  	}
   550  
   551  	// TODO(rfindley): this is buggy: an insignificant change to a modfile
   552  	// (or an unsaved modfile) could affect the position of deps errors,
   553  	// without invalidating the package.
   554  	depsDiags, err := depsErrors(ctx, snapshot, mp)
   555  	if err != nil {
   556  		if ctx.Err() == nil {
   557  			// TODO(rfindley): consider making this a bug.Reportf. depsErrors should
   558  			// not normally fail.
   559  			event.Error(ctx, "unable to compute deps errors", err, tag.Package.Of(string(mp.ID)))
   560  		}
   561  	} else {
   562  		diags = append(diags, depsDiags...)
   563  	}
   564  	return diags
   565  }
   566  
   567  // IsWorkspacePackage reports whether id points to a workspace package in s.
   568  //
   569  // Currently, the result depends on the current set of loaded packages, and so
   570  // is not guaranteed to be stable.
   571  func (s *Snapshot) IsWorkspacePackage(ctx context.Context, id PackageID) bool {
   572  	s.mu.Lock()
   573  	defer s.mu.Unlock()
   574  
   575  	mg := s.meta
   576  	m := mg.Packages[id]
   577  	if m == nil {
   578  		return false
   579  	}
   580  	return isWorkspacePackageLocked(ctx, s, mg, m)
   581  }
   582  
   583  // isWorkspacePackageLocked reports whether p is a workspace package for the
   584  // snapshot s.
   585  //
   586  // Workspace packages are packages that we consider the user to be actively
   587  // working on. As such, they are re-diagnosed on every keystroke, and searched
   588  // for various workspace-wide queries such as references or workspace symbols.
   589  //
   590  // See the commentary inline for a description of the workspace package
   591  // heuristics.
   592  //
   593  // s.mu must be held while calling this function.
   594  //
   595  // TODO(rfindley): remove 'meta' from this function signature. Whether or not a
   596  // package is a workspace package should depend only on the package, view
   597  // definition, and snapshot file source. While useful, the heuristic
   598  // "allFilesHaveRealPackages" does not add that much value and is path
   599  // dependent as it depends on the timing of loads.
   600  func isWorkspacePackageLocked(ctx context.Context, s *Snapshot, meta *metadata.Graph, pkg *metadata.Package) bool {
   601  	if metadata.IsCommandLineArguments(pkg.ID) {
   602  		// Ad-hoc command-line-arguments packages aren't workspace packages.
   603  		// With zero-config gopls (golang/go#57979) they should be very rare, as
   604  		// they should only arise when the user opens a file outside the workspace
   605  		// which isn't present in the import graph of a workspace package.
   606  		//
   607  		// Considering them as workspace packages tends to be racy, as they don't
   608  		// deterministically belong to any view.
   609  		if !pkg.Standalone {
   610  			return false
   611  		}
   612  
   613  		// If all the files contained in pkg have a real package, we don't need to
   614  		// keep pkg as a workspace package.
   615  		if allFilesHaveRealPackages(meta, pkg) {
   616  			return false
   617  		}
   618  
   619  		// For now, allow open standalone packages (i.e. go:build ignore) to be
   620  		// workspace packages, but this means they could belong to multiple views.
   621  		return containsOpenFileLocked(s, pkg)
   622  	}
   623  
   624  	// If a real package is open, consider it to be part of the workspace.
   625  	//
   626  	// TODO(rfindley): reconsider this. In golang/go#66145, we saw that even if a
   627  	// View sees a real package for a file, it doesn't mean that View is able to
   628  	// cleanly diagnose the package. Yet, we do want to show diagnostics for open
   629  	// packages outside the workspace. Is there a better way to ensure that only
   630  	// the 'best' View gets a workspace package for the open file?
   631  	if containsOpenFileLocked(s, pkg) {
   632  		return true
   633  	}
   634  
   635  	// Apply filtering logic.
   636  	//
   637  	// Workspace packages must contain at least one non-filtered file.
   638  	filterFunc := s.view.filterFunc()
   639  	uris := make(map[protocol.DocumentURI]unit) // filtered package URIs
   640  	for _, uri := range slices.Concat(pkg.CompiledGoFiles, pkg.GoFiles) {
   641  		if !strings.Contains(string(uri), "/vendor/") && !filterFunc(uri) {
   642  			uris[uri] = struct{}{}
   643  		}
   644  	}
   645  	if len(uris) == 0 {
   646  		return false // no non-filtered files
   647  	}
   648  
   649  	// For non-module views (of type GOPATH or AdHoc), or if
   650  	// expandWorkspaceToModule is unset, workspace packages must be contained in
   651  	// the workspace folder.
   652  	//
   653  	// For module views (of type GoMod or GoWork), packages must in any case be
   654  	// in a workspace module (enforced below).
   655  	if !s.view.moduleMode() || !s.Options().ExpandWorkspaceToModule {
   656  		folder := s.view.folder.Dir.Path()
   657  		inFolder := false
   658  		for uri := range uris {
   659  			if pathutil.InDir(folder, uri.Path()) {
   660  				inFolder = true
   661  				break
   662  			}
   663  		}
   664  		if !inFolder {
   665  			return false
   666  		}
   667  	}
   668  
   669  	// In module mode, a workspace package must be contained in a workspace
   670  	// module.
   671  	if s.view.moduleMode() {
   672  		var modURI protocol.DocumentURI
   673  		if pkg.Module != nil {
   674  			modURI = protocol.URIFromPath(pkg.Module.GoMod)
   675  		} else {
   676  			// golang/go#65816: for std and cmd, Module is nil.
   677  			// Fall back to an inferior heuristic.
   678  			if len(pkg.CompiledGoFiles) == 0 {
   679  				return false // need at least one file to guess the go.mod file
   680  			}
   681  			dir := pkg.CompiledGoFiles[0].Dir()
   682  			var err error
   683  			modURI, err = findRootPattern(ctx, dir, "go.mod", lockedSnapshot{s})
   684  			if err != nil || modURI == "" {
   685  				// err != nil implies context cancellation, in which case the result of
   686  				// this query does not matter.
   687  				return false
   688  			}
   689  		}
   690  		_, ok := s.view.workspaceModFiles[modURI]
   691  		return ok
   692  	}
   693  
   694  	return true // an ad-hoc package or GOPATH package
   695  }
   696  
   697  // containsOpenFileLocked reports whether any file referenced by m is open in
   698  // the snapshot s.
   699  //
   700  // s.mu must be held while calling this function.
   701  func containsOpenFileLocked(s *Snapshot, mp *metadata.Package) bool {
   702  	uris := map[protocol.DocumentURI]struct{}{}
   703  	for _, uri := range mp.CompiledGoFiles {
   704  		uris[uri] = struct{}{}
   705  	}
   706  	for _, uri := range mp.GoFiles {
   707  		uris[uri] = struct{}{}
   708  	}
   709  
   710  	for uri := range uris {
   711  		fh, _ := s.files.get(uri)
   712  		if _, open := fh.(*overlay); open {
   713  			return true
   714  		}
   715  	}
   716  	return false
   717  }
   718  
   719  // computeWorkspacePackagesLocked computes workspace packages in the
   720  // snapshot s for the given metadata graph. The result does not
   721  // contain intermediate test variants.
   722  //
   723  // s.mu must be held while calling this function.
   724  func computeWorkspacePackagesLocked(ctx context.Context, s *Snapshot, meta *metadata.Graph) immutable.Map[PackageID, PackagePath] {
   725  	// The provided context is used for reading snapshot files, which can only
   726  	// fail due to context cancellation. Don't let this happen as it could lead
   727  	// to inconsistent results.
   728  	ctx = xcontext.Detach(ctx)
   729  	workspacePackages := make(map[PackageID]PackagePath)
   730  	for _, mp := range meta.Packages {
   731  		if !isWorkspacePackageLocked(ctx, s, meta, mp) {
   732  			continue
   733  		}
   734  
   735  		switch {
   736  		case mp.ForTest == "":
   737  			// A normal package.
   738  			workspacePackages[mp.ID] = mp.PkgPath
   739  		case mp.ForTest == mp.PkgPath, mp.ForTest+"_test" == mp.PkgPath:
   740  			// The test variant of some workspace package or its x_test.
   741  			// To load it, we need to load the non-test variant with -test.
   742  			//
   743  			// Notably, this excludes intermediate test variants from workspace
   744  			// packages.
   745  			assert(!mp.IsIntermediateTestVariant(), "unexpected ITV")
   746  			workspacePackages[mp.ID] = mp.ForTest
   747  		}
   748  	}
   749  	return immutable.MapOf(workspacePackages)
   750  }
   751  
   752  // allFilesHaveRealPackages reports whether all files referenced by m are
   753  // contained in a "real" package (not command-line-arguments).
   754  //
   755  // If m is valid but all "real" packages containing any file are invalid, this
   756  // function returns false.
   757  //
   758  // If m is not a command-line-arguments package, this is trivially true.
   759  func allFilesHaveRealPackages(g *metadata.Graph, mp *metadata.Package) bool {
   760  	n := len(mp.CompiledGoFiles)
   761  checkURIs:
   762  	for _, uri := range append(mp.CompiledGoFiles[0:n:n], mp.GoFiles...) {
   763  		for _, id := range g.IDs[uri] {
   764  			if !metadata.IsCommandLineArguments(id) {
   765  				continue checkURIs
   766  			}
   767  		}
   768  		return false
   769  	}
   770  	return true
   771  }
   772  
   773  func isTestMain(pkg *packages.Package, gocache string) bool {
   774  	// Test mains must have an import path that ends with ".test".
   775  	if !strings.HasSuffix(pkg.PkgPath, ".test") {
   776  		return false
   777  	}
   778  	// Test main packages are always named "main".
   779  	if pkg.Name != "main" {
   780  		return false
   781  	}
   782  	// Test mains always have exactly one GoFile that is in the build cache.
   783  	if len(pkg.GoFiles) > 1 {
   784  		return false
   785  	}
   786  	if !pathutil.InDir(gocache, pkg.GoFiles[0]) {
   787  		return false
   788  	}
   789  	return true
   790  }