golang.org/x/tools/gopls@v0.15.3/internal/cache/snapshot.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  	"go/ast"
    13  	"go/build/constraint"
    14  	"go/parser"
    15  	"go/token"
    16  	"go/types"
    17  	"io"
    18  	"os"
    19  	"path"
    20  	"path/filepath"
    21  	"regexp"
    22  	"runtime"
    23  	"sort"
    24  	"strconv"
    25  	"strings"
    26  	"sync"
    27  
    28  	"golang.org/x/sync/errgroup"
    29  	"golang.org/x/tools/go/packages"
    30  	"golang.org/x/tools/go/types/objectpath"
    31  	"golang.org/x/tools/gopls/internal/cache/metadata"
    32  	"golang.org/x/tools/gopls/internal/cache/methodsets"
    33  	"golang.org/x/tools/gopls/internal/cache/typerefs"
    34  	"golang.org/x/tools/gopls/internal/cache/xrefs"
    35  	"golang.org/x/tools/gopls/internal/file"
    36  	"golang.org/x/tools/gopls/internal/filecache"
    37  	"golang.org/x/tools/gopls/internal/protocol"
    38  	"golang.org/x/tools/gopls/internal/protocol/command"
    39  	"golang.org/x/tools/gopls/internal/settings"
    40  	"golang.org/x/tools/gopls/internal/util/bug"
    41  	"golang.org/x/tools/gopls/internal/util/constraints"
    42  	"golang.org/x/tools/gopls/internal/util/immutable"
    43  	"golang.org/x/tools/gopls/internal/util/pathutil"
    44  	"golang.org/x/tools/gopls/internal/util/persistent"
    45  	"golang.org/x/tools/gopls/internal/util/slices"
    46  	"golang.org/x/tools/gopls/internal/vulncheck"
    47  	"golang.org/x/tools/internal/event"
    48  	"golang.org/x/tools/internal/event/label"
    49  	"golang.org/x/tools/internal/event/tag"
    50  	"golang.org/x/tools/internal/gocommand"
    51  	"golang.org/x/tools/internal/memoize"
    52  	"golang.org/x/tools/internal/packagesinternal"
    53  	"golang.org/x/tools/internal/typesinternal"
    54  )
    55  
    56  // A Snapshot represents the current state for a given view.
    57  //
    58  // It is first and foremost an idempotent implementation of file.Source whose
    59  // ReadFile method returns consistent information about the existence and
    60  // content of each file throughout its lifetime.
    61  //
    62  // However, the snapshot also manages additional state (such as parsed files
    63  // and packages) that are derived from file content.
    64  //
    65  // Snapshots are responsible for bookkeeping and invalidation of this state,
    66  // implemented in Snapshot.clone.
    67  type Snapshot struct {
    68  	// sequenceID is the monotonically increasing ID of this snapshot within its View.
    69  	//
    70  	// Sequence IDs for Snapshots from different Views cannot be compared.
    71  	sequenceID uint64
    72  
    73  	// TODO(rfindley): the snapshot holding a reference to the view poses
    74  	// lifecycle problems: a view may be shut down and waiting for work
    75  	// associated with this snapshot to complete. While most accesses of the view
    76  	// are benign (options or workspace information), this is not formalized and
    77  	// it is wrong for the snapshot to use a shutdown view.
    78  	//
    79  	// Fix this by passing options and workspace information to the snapshot,
    80  	// both of which should be immutable for the snapshot.
    81  	view *View
    82  
    83  	cancel        func()
    84  	backgroundCtx context.Context
    85  
    86  	store *memoize.Store // cache of handles shared by all snapshots
    87  
    88  	refMu sync.Mutex
    89  
    90  	// refcount holds the number of outstanding references to the current
    91  	// Snapshot. When refcount is decremented to 0, the Snapshot maps are
    92  	// destroyed and the done function is called.
    93  	//
    94  	// TODO(rfindley): use atomic.Int32 on Go 1.19+.
    95  	refcount int
    96  	done     func() // for implementing Session.Shutdown
    97  
    98  	// mu guards all of the maps in the snapshot, as well as the builtin URI and
    99  	// initialized.
   100  	mu sync.Mutex
   101  
   102  	// initialized reports whether the snapshot has been initialized. Concurrent
   103  	// initialization is guarded by the view.initializationSema. Each snapshot is
   104  	// initialized at most once: concurrent initialization is guarded by
   105  	// view.initializationSema.
   106  	initialized bool
   107  
   108  	// initialErr holds the last error resulting from initialization. If
   109  	// initialization fails, we only retry when the workspace modules change,
   110  	// to avoid too many go/packages calls.
   111  	// If initialized is false, initialErr stil holds the error resulting from
   112  	// the previous initialization.
   113  	// TODO(rfindley): can we unify the lifecycle of initialized and initialErr.
   114  	initialErr *InitializationError
   115  
   116  	// builtin is the location of builtin.go in GOROOT.
   117  	//
   118  	// TODO(rfindley): would it make more sense to eagerly parse builtin, and
   119  	// instead store a *ParsedGoFile here?
   120  	builtin protocol.DocumentURI
   121  
   122  	// meta holds loaded metadata.
   123  	//
   124  	// meta is guarded by mu, but the Graph itself is immutable.
   125  	//
   126  	// TODO(rfindley): in many places we hold mu while operating on meta, even
   127  	// though we only need to hold mu while reading the pointer.
   128  	meta *metadata.Graph
   129  
   130  	// files maps file URIs to their corresponding FileHandles.
   131  	// It may invalidated when a file's content changes.
   132  	files *fileMap
   133  
   134  	// symbolizeHandles maps each file URI to a handle for the future
   135  	// result of computing the symbols declared in that file.
   136  	symbolizeHandles *persistent.Map[protocol.DocumentURI, *memoize.Promise] // *memoize.Promise[symbolizeResult]
   137  
   138  	// packages maps a packageKey to a *packageHandle.
   139  	// It may be invalidated when a file's content changes.
   140  	//
   141  	// Invariants to preserve:
   142  	//  - packages.Get(id).meta == meta.metadata[id] for all ids
   143  	//  - if a package is in packages, then all of its dependencies should also
   144  	//    be in packages, unless there is a missing import
   145  	packages *persistent.Map[PackageID, *packageHandle]
   146  
   147  	// activePackages maps a package ID to a memoized active package, or nil if
   148  	// the package is known not to be open.
   149  	//
   150  	// IDs not contained in the map are not known to be open or not open.
   151  	activePackages *persistent.Map[PackageID, *Package]
   152  
   153  	// workspacePackages contains the workspace's packages, which are loaded
   154  	// when the view is created. It does not contain intermediate test variants.
   155  	workspacePackages immutable.Map[PackageID, PackagePath]
   156  
   157  	// shouldLoad tracks packages that need to be reloaded, mapping a PackageID
   158  	// to the package paths that should be used to reload it
   159  	//
   160  	// When we try to load a package, we clear it from the shouldLoad map
   161  	// regardless of whether the load succeeded, to prevent endless loads.
   162  	shouldLoad *persistent.Map[PackageID, []PackagePath]
   163  
   164  	// unloadableFiles keeps track of files that we've failed to load.
   165  	unloadableFiles *persistent.Set[protocol.DocumentURI]
   166  
   167  	// TODO(rfindley): rename the handles below to "promises". A promise is
   168  	// different from a handle (we mutate the package handle.)
   169  
   170  	// parseModHandles keeps track of any parseModHandles for the snapshot.
   171  	// The handles need not refer to only the view's go.mod file.
   172  	parseModHandles *persistent.Map[protocol.DocumentURI, *memoize.Promise] // *memoize.Promise[parseModResult]
   173  
   174  	// parseWorkHandles keeps track of any parseWorkHandles for the snapshot.
   175  	// The handles need not refer to only the view's go.work file.
   176  	parseWorkHandles *persistent.Map[protocol.DocumentURI, *memoize.Promise] // *memoize.Promise[parseWorkResult]
   177  
   178  	// Preserve go.mod-related handles to avoid garbage-collecting the results
   179  	// of various calls to the go command. The handles need not refer to only
   180  	// the view's go.mod file.
   181  	modTidyHandles *persistent.Map[protocol.DocumentURI, *memoize.Promise] // *memoize.Promise[modTidyResult]
   182  	modWhyHandles  *persistent.Map[protocol.DocumentURI, *memoize.Promise] // *memoize.Promise[modWhyResult]
   183  	modVulnHandles *persistent.Map[protocol.DocumentURI, *memoize.Promise] // *memoize.Promise[modVulnResult]
   184  
   185  	// importGraph holds a shared import graph to use for type-checking. Adding
   186  	// more packages to this import graph can speed up type checking, at the
   187  	// expense of in-use memory.
   188  	//
   189  	// See getImportGraph for additional documentation.
   190  	importGraphDone chan struct{} // closed when importGraph is set; may be nil
   191  	importGraph     *importGraph  // copied from preceding snapshot and re-evaluated
   192  
   193  	// pkgIndex is an index of package IDs, for efficient storage of typerefs.
   194  	pkgIndex *typerefs.PackageIndex
   195  
   196  	// moduleUpgrades tracks known upgrades for module paths in each modfile.
   197  	// Each modfile has a map of module name to upgrade version.
   198  	moduleUpgrades *persistent.Map[protocol.DocumentURI, map[string]string]
   199  
   200  	// vulns maps each go.mod file's URI to its known vulnerabilities.
   201  	vulns *persistent.Map[protocol.DocumentURI, *vulncheck.Result]
   202  
   203  	// gcOptimizationDetails describes the packages for which we want
   204  	// optimization details to be included in the diagnostics.
   205  	gcOptimizationDetails map[metadata.PackageID]unit
   206  }
   207  
   208  var _ memoize.RefCounted = (*Snapshot)(nil) // snapshots are reference-counted
   209  
   210  func (s *Snapshot) awaitPromise(ctx context.Context, p *memoize.Promise) (interface{}, error) {
   211  	return p.Get(ctx, s)
   212  }
   213  
   214  // Acquire prevents the snapshot from being destroyed until the returned
   215  // function is called.
   216  //
   217  // (s.Acquire().release() could instead be expressed as a pair of
   218  // method calls s.IncRef(); s.DecRef(). The latter has the advantage
   219  // that the DecRefs are fungible and don't require holding anything in
   220  // addition to the refcounted object s, but paradoxically that is also
   221  // an advantage of the current approach, which forces the caller to
   222  // consider the release function at every stage, making a reference
   223  // leak more obvious.)
   224  func (s *Snapshot) Acquire() func() {
   225  	s.refMu.Lock()
   226  	defer s.refMu.Unlock()
   227  	assert(s.refcount > 0, "non-positive refs")
   228  	s.refcount++
   229  
   230  	return s.decref
   231  }
   232  
   233  // decref should only be referenced by Acquire, and by View when it frees its
   234  // reference to View.snapshot.
   235  func (s *Snapshot) decref() {
   236  	s.refMu.Lock()
   237  	defer s.refMu.Unlock()
   238  
   239  	assert(s.refcount > 0, "non-positive refs")
   240  	s.refcount--
   241  	if s.refcount == 0 {
   242  		s.packages.Destroy()
   243  		s.activePackages.Destroy()
   244  		s.files.destroy()
   245  		s.symbolizeHandles.Destroy()
   246  		s.parseModHandles.Destroy()
   247  		s.parseWorkHandles.Destroy()
   248  		s.modTidyHandles.Destroy()
   249  		s.modVulnHandles.Destroy()
   250  		s.modWhyHandles.Destroy()
   251  		s.unloadableFiles.Destroy()
   252  		s.moduleUpgrades.Destroy()
   253  		s.vulns.Destroy()
   254  		s.done()
   255  	}
   256  }
   257  
   258  // SequenceID is the sequence id of this snapshot within its containing
   259  // view.
   260  //
   261  // Relative to their view sequence ids are monotonically increasing, but this
   262  // does not hold globally: when new views are created their initial snapshot
   263  // has sequence ID 0.
   264  func (s *Snapshot) SequenceID() uint64 {
   265  	return s.sequenceID
   266  }
   267  
   268  // SnapshotLabels returns a new slice of labels that should be used for events
   269  // related to a snapshot.
   270  func (s *Snapshot) Labels() []label.Label {
   271  	return []label.Label{tag.Snapshot.Of(s.SequenceID()), tag.Directory.Of(s.Folder())}
   272  }
   273  
   274  // Folder returns the folder at the base of this snapshot.
   275  func (s *Snapshot) Folder() protocol.DocumentURI {
   276  	return s.view.folder.Dir
   277  }
   278  
   279  // View returns the View associated with this snapshot.
   280  func (s *Snapshot) View() *View {
   281  	return s.view
   282  }
   283  
   284  // FileKind returns the kind of a file.
   285  //
   286  // We can't reliably deduce the kind from the file name alone,
   287  // as some editors can be told to interpret a buffer as
   288  // language different from the file name heuristic, e.g. that
   289  // an .html file actually contains Go "html/template" syntax,
   290  // or even that a .go file contains Python.
   291  func (s *Snapshot) FileKind(fh file.Handle) file.Kind {
   292  	if k := fileKind(fh); k != file.UnknownKind {
   293  		return k
   294  	}
   295  	fext := filepath.Ext(fh.URI().Path())
   296  	exts := s.Options().TemplateExtensions
   297  	for _, ext := range exts {
   298  		if fext == ext || fext == "."+ext {
   299  			return file.Tmpl
   300  		}
   301  	}
   302  
   303  	// and now what? This should never happen, but it does for cgo before go1.15
   304  	//
   305  	// TODO(rfindley): this doesn't look right. We should default to UnknownKind.
   306  	// Also, I don't understand the comment above, though I'd guess before go1.15
   307  	// we encountered cgo files without the .go extension.
   308  	return file.Go
   309  }
   310  
   311  // fileKind returns the default file kind for a file, before considering
   312  // template file extensions. See [Snapshot.FileKind].
   313  func fileKind(fh file.Handle) file.Kind {
   314  	// The kind of an unsaved buffer comes from the
   315  	// TextDocumentItem.LanguageID field in the didChange event,
   316  	// not from the file name. They may differ.
   317  	if o, ok := fh.(*overlay); ok {
   318  		if o.kind != file.UnknownKind {
   319  			return o.kind
   320  		}
   321  	}
   322  
   323  	fext := filepath.Ext(fh.URI().Path())
   324  	switch fext {
   325  	case ".go":
   326  		return file.Go
   327  	case ".mod":
   328  		return file.Mod
   329  	case ".sum":
   330  		return file.Sum
   331  	case ".work":
   332  		return file.Work
   333  	}
   334  	return file.UnknownKind
   335  }
   336  
   337  // Options returns the options associated with this snapshot.
   338  func (s *Snapshot) Options() *settings.Options {
   339  	return s.view.folder.Options
   340  }
   341  
   342  // BackgroundContext returns a context used for all background processing
   343  // on behalf of this snapshot.
   344  func (s *Snapshot) BackgroundContext() context.Context {
   345  	return s.backgroundCtx
   346  }
   347  
   348  // Templates returns the .tmpl files.
   349  func (s *Snapshot) Templates() map[protocol.DocumentURI]file.Handle {
   350  	s.mu.Lock()
   351  	defer s.mu.Unlock()
   352  
   353  	tmpls := map[protocol.DocumentURI]file.Handle{}
   354  	s.files.foreach(func(k protocol.DocumentURI, fh file.Handle) {
   355  		if s.FileKind(fh) == file.Tmpl {
   356  			tmpls[k] = fh
   357  		}
   358  	})
   359  	return tmpls
   360  }
   361  
   362  // config returns the configuration used for the snapshot's interaction with
   363  // the go/packages API. It uses the given working directory.
   364  //
   365  // TODO(rstambler): go/packages requires that we do not provide overlays for
   366  // multiple modules in on config, so buildOverlay needs to filter overlays by
   367  // module.
   368  func (s *Snapshot) config(ctx context.Context, inv *gocommand.Invocation) *packages.Config {
   369  
   370  	cfg := &packages.Config{
   371  		Context:    ctx,
   372  		Dir:        inv.WorkingDir,
   373  		Env:        inv.Env,
   374  		BuildFlags: inv.BuildFlags,
   375  		Mode: packages.NeedName |
   376  			packages.NeedFiles |
   377  			packages.NeedCompiledGoFiles |
   378  			packages.NeedImports |
   379  			packages.NeedDeps |
   380  			packages.NeedTypesSizes |
   381  			packages.NeedModule |
   382  			packages.NeedEmbedFiles |
   383  			packages.LoadMode(packagesinternal.DepsErrors) |
   384  			packages.LoadMode(packagesinternal.ForTest),
   385  		Fset:    nil, // we do our own parsing
   386  		Overlay: s.buildOverlay(),
   387  		ParseFile: func(*token.FileSet, string, []byte) (*ast.File, error) {
   388  			panic("go/packages must not be used to parse files")
   389  		},
   390  		Logf: func(format string, args ...interface{}) {
   391  			if s.Options().VerboseOutput {
   392  				event.Log(ctx, fmt.Sprintf(format, args...))
   393  			}
   394  		},
   395  		Tests: true,
   396  	}
   397  	packagesinternal.SetModFile(cfg, inv.ModFile)
   398  	packagesinternal.SetModFlag(cfg, inv.ModFlag)
   399  	// We want to type check cgo code if go/types supports it.
   400  	if typesinternal.SetUsesCgo(&types.Config{}) {
   401  		cfg.Mode |= packages.LoadMode(packagesinternal.TypecheckCgo)
   402  	}
   403  	return cfg
   404  }
   405  
   406  // InvocationFlags represents the settings of a particular go command invocation.
   407  // It is a mode, plus a set of flag bits.
   408  type InvocationFlags int
   409  
   410  const (
   411  	// Normal is appropriate for commands that might be run by a user and don't
   412  	// deliberately modify go.mod files, e.g. `go test`.
   413  	Normal InvocationFlags = iota
   414  	// WriteTemporaryModFile is for commands that need information from a
   415  	// modified version of the user's go.mod file, e.g. `go mod tidy` used to
   416  	// generate diagnostics.
   417  	WriteTemporaryModFile
   418  	// LoadWorkspace is for packages.Load, and other operations that should
   419  	// consider the whole workspace at once.
   420  	LoadWorkspace
   421  	// AllowNetwork is a flag bit that indicates the invocation should be
   422  	// allowed to access the network.
   423  	AllowNetwork InvocationFlags = 1 << 10
   424  )
   425  
   426  func (m InvocationFlags) Mode() InvocationFlags {
   427  	return m & (AllowNetwork - 1)
   428  }
   429  
   430  func (m InvocationFlags) AllowNetwork() bool {
   431  	return m&AllowNetwork != 0
   432  }
   433  
   434  // RunGoCommandDirect runs the given `go` command. Verb, Args, and
   435  // WorkingDir must be specified.
   436  func (s *Snapshot) RunGoCommandDirect(ctx context.Context, mode InvocationFlags, inv *gocommand.Invocation) (*bytes.Buffer, error) {
   437  	_, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv)
   438  	if err != nil {
   439  		return nil, err
   440  	}
   441  	defer cleanup()
   442  
   443  	return s.view.gocmdRunner.Run(ctx, *inv)
   444  }
   445  
   446  // RunGoCommandPiped runs the given `go` command, writing its output
   447  // to stdout and stderr. Verb, Args, and WorkingDir must be specified.
   448  //
   449  // RunGoCommandPiped runs the command serially using gocommand.RunPiped,
   450  // enforcing that this command executes exclusively to other commands on the
   451  // server.
   452  func (s *Snapshot) RunGoCommandPiped(ctx context.Context, mode InvocationFlags, inv *gocommand.Invocation, stdout, stderr io.Writer) error {
   453  	_, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv)
   454  	if err != nil {
   455  		return err
   456  	}
   457  	defer cleanup()
   458  	return s.view.gocmdRunner.RunPiped(ctx, *inv, stdout, stderr)
   459  }
   460  
   461  // RunGoModUpdateCommands runs a series of `go` commands that updates the go.mod
   462  // and go.sum file for wd, and returns their updated contents.
   463  //
   464  // TODO(rfindley): the signature of RunGoModUpdateCommands is very confusing.
   465  // Simplify it.
   466  func (s *Snapshot) RunGoModUpdateCommands(ctx context.Context, wd string, run func(invoke func(...string) (*bytes.Buffer, error)) error) ([]byte, []byte, error) {
   467  	flags := WriteTemporaryModFile | AllowNetwork
   468  	tmpURI, inv, cleanup, err := s.goCommandInvocation(ctx, flags, &gocommand.Invocation{WorkingDir: wd})
   469  	if err != nil {
   470  		return nil, nil, err
   471  	}
   472  	defer cleanup()
   473  	invoke := func(args ...string) (*bytes.Buffer, error) {
   474  		inv.Verb = args[0]
   475  		inv.Args = args[1:]
   476  		return s.view.gocmdRunner.Run(ctx, *inv)
   477  	}
   478  	if err := run(invoke); err != nil {
   479  		return nil, nil, err
   480  	}
   481  	if flags.Mode() != WriteTemporaryModFile {
   482  		return nil, nil, nil
   483  	}
   484  	var modBytes, sumBytes []byte
   485  	modBytes, err = os.ReadFile(tmpURI.Path())
   486  	if err != nil && !os.IsNotExist(err) {
   487  		return nil, nil, err
   488  	}
   489  	sumBytes, err = os.ReadFile(strings.TrimSuffix(tmpURI.Path(), ".mod") + ".sum")
   490  	if err != nil && !os.IsNotExist(err) {
   491  		return nil, nil, err
   492  	}
   493  	return modBytes, sumBytes, nil
   494  }
   495  
   496  // goCommandInvocation populates inv with configuration for running go commands on the snapshot.
   497  //
   498  // TODO(rfindley): refactor this function to compose the required configuration
   499  // explicitly, rather than implicitly deriving it from flags and inv.
   500  //
   501  // TODO(adonovan): simplify cleanup mechanism. It's hard to see, but
   502  // it used only after call to tempModFile.
   503  func (s *Snapshot) goCommandInvocation(ctx context.Context, flags InvocationFlags, inv *gocommand.Invocation) (tmpURI protocol.DocumentURI, updatedInv *gocommand.Invocation, cleanup func(), err error) {
   504  	allowModfileModificationOption := s.Options().AllowModfileModifications
   505  	allowNetworkOption := s.Options().AllowImplicitNetworkAccess
   506  
   507  	// TODO(rfindley): it's not clear that this is doing the right thing.
   508  	// Should inv.Env really overwrite view.options? Should s.view.envOverlay
   509  	// overwrite inv.Env? (Do we ever invoke this with a non-empty inv.Env?)
   510  	//
   511  	// We should survey existing uses and write down rules for how env is
   512  	// applied.
   513  	inv.Env = slices.Concat(
   514  		os.Environ(),
   515  		s.Options().EnvSlice(),
   516  		inv.Env,
   517  		[]string{"GO111MODULE=" + s.view.adjustedGO111MODULE()},
   518  		s.view.EnvOverlay(),
   519  	)
   520  	inv.BuildFlags = append([]string{}, s.Options().BuildFlags...)
   521  	cleanup = func() {} // fallback
   522  
   523  	// All logic below is for module mode.
   524  	if len(s.view.workspaceModFiles) == 0 {
   525  		return "", inv, cleanup, nil
   526  	}
   527  
   528  	mode, allowNetwork := flags.Mode(), flags.AllowNetwork()
   529  	if !allowNetwork && !allowNetworkOption {
   530  		inv.Env = append(inv.Env, "GOPROXY=off")
   531  	}
   532  
   533  	// What follows is rather complicated logic for how to actually run the go
   534  	// command. A word of warning: this is the result of various incremental
   535  	// features added to gopls, and varying behavior of the Go command across Go
   536  	// versions. It can surely be cleaned up significantly, but tread carefully.
   537  	//
   538  	// Roughly speaking we need to resolve four things:
   539  	//  - the working directory.
   540  	//  - the -mod flag
   541  	//  - the -modfile flag
   542  	//
   543  	// These are dependent on a number of factors: whether we need to run in a
   544  	// synthetic workspace, whether flags are supported at the current go
   545  	// version, and what we're actually trying to achieve (the
   546  	// InvocationFlags).
   547  	//
   548  	// TODO(rfindley): should we set -overlays here?
   549  
   550  	const mutableModFlag = "mod"
   551  
   552  	// If the mod flag isn't set, populate it based on the mode and workspace.
   553  	//
   554  	// (As noted in various TODOs throughout this function, this is very
   555  	// confusing and not obviously correct, but tests pass and we will eventually
   556  	// rewrite this entire function.)
   557  	if inv.ModFlag == "" {
   558  		switch mode {
   559  		case LoadWorkspace, Normal:
   560  			if allowModfileModificationOption {
   561  				inv.ModFlag = mutableModFlag
   562  			}
   563  		case WriteTemporaryModFile:
   564  			inv.ModFlag = mutableModFlag
   565  			// -mod must be readonly when using go.work files - see issue #48941
   566  			inv.Env = append(inv.Env, "GOWORK=off")
   567  		}
   568  	}
   569  
   570  	// TODO(rfindley): if inv.ModFlag was already set to "mod", we may not have
   571  	// set GOWORK=off here. But that doesn't happen. Clean up this entire API so
   572  	// that we don't have this mutation of the invocation, which is quite hard to
   573  	// follow.
   574  
   575  	// If the invocation needs to mutate the modfile, we must use a temp mod.
   576  	if inv.ModFlag == mutableModFlag {
   577  		var modURI protocol.DocumentURI
   578  		// Select the module context to use.
   579  		// If we're type checking, we need to use the workspace context, meaning
   580  		// the main (workspace) module. Otherwise, we should use the module for
   581  		// the passed-in working dir.
   582  		if mode == LoadWorkspace {
   583  			// TODO(rfindley): this seems unnecessary and overly complicated. Remove
   584  			// this along with 'allowModFileModifications'.
   585  			if s.view.typ == GoModView {
   586  				modURI = s.view.gomod
   587  			}
   588  		} else {
   589  			modURI = s.GoModForFile(protocol.URIFromPath(inv.WorkingDir))
   590  		}
   591  
   592  		var modContent []byte
   593  		if modURI != "" {
   594  			modFH, err := s.ReadFile(ctx, modURI)
   595  			if err != nil {
   596  				return "", nil, cleanup, err
   597  			}
   598  			modContent, err = modFH.Content()
   599  			if err != nil {
   600  				return "", nil, cleanup, err
   601  			}
   602  		}
   603  		if modURI == "" {
   604  			return "", nil, cleanup, fmt.Errorf("no go.mod file found in %s", inv.WorkingDir)
   605  		}
   606  		// Use the go.sum if it happens to be available.
   607  		gosum := s.goSum(ctx, modURI)
   608  		tmpURI, cleanup, err = tempModFile(modURI, modContent, gosum)
   609  		if err != nil {
   610  			return "", nil, cleanup, err
   611  		}
   612  		inv.ModFile = tmpURI.Path()
   613  	}
   614  
   615  	return tmpURI, inv, cleanup, nil
   616  }
   617  
   618  func (s *Snapshot) buildOverlay() map[string][]byte {
   619  	overlays := make(map[string][]byte)
   620  	for _, overlay := range s.Overlays() {
   621  		if overlay.saved {
   622  			continue
   623  		}
   624  		// TODO(rfindley): previously, there was a todo here to make sure we don't
   625  		// send overlays outside of the current view. IMO we should instead make
   626  		// sure this doesn't matter.
   627  		overlays[overlay.URI().Path()] = overlay.content
   628  	}
   629  	return overlays
   630  }
   631  
   632  // Overlays returns the set of overlays at this snapshot.
   633  //
   634  // Note that this may differ from the set of overlays on the server, if the
   635  // snapshot observed a historical state.
   636  func (s *Snapshot) Overlays() []*overlay {
   637  	s.mu.Lock()
   638  	defer s.mu.Unlock()
   639  
   640  	return s.files.getOverlays()
   641  }
   642  
   643  // Package data kinds, identifying various package data that may be stored in
   644  // the file cache.
   645  const (
   646  	xrefsKind       = "xrefs"
   647  	methodSetsKind  = "methodsets"
   648  	exportDataKind  = "export"
   649  	diagnosticsKind = "diagnostics"
   650  	typerefsKind    = "typerefs"
   651  )
   652  
   653  // PackageDiagnostics returns diagnostics for files contained in specified
   654  // packages.
   655  //
   656  // If these diagnostics cannot be loaded from cache, the requested packages
   657  // may be type-checked.
   658  func (s *Snapshot) PackageDiagnostics(ctx context.Context, ids ...PackageID) (map[protocol.DocumentURI][]*Diagnostic, error) {
   659  	ctx, done := event.Start(ctx, "cache.snapshot.PackageDiagnostics")
   660  	defer done()
   661  
   662  	var mu sync.Mutex
   663  	perFile := make(map[protocol.DocumentURI][]*Diagnostic)
   664  	collect := func(diags []*Diagnostic) {
   665  		mu.Lock()
   666  		defer mu.Unlock()
   667  		for _, diag := range diags {
   668  			perFile[diag.URI] = append(perFile[diag.URI], diag)
   669  		}
   670  	}
   671  	pre := func(_ int, ph *packageHandle) bool {
   672  		data, err := filecache.Get(diagnosticsKind, ph.key)
   673  		if err == nil { // hit
   674  			collect(ph.loadDiagnostics)
   675  			collect(decodeDiagnostics(data))
   676  			return false
   677  		} else if err != filecache.ErrNotFound {
   678  			event.Error(ctx, "reading diagnostics from filecache", err)
   679  		}
   680  		return true
   681  	}
   682  	post := func(_ int, pkg *Package) {
   683  		collect(pkg.loadDiagnostics)
   684  		collect(pkg.pkg.diagnostics)
   685  	}
   686  	return perFile, s.forEachPackage(ctx, ids, pre, post)
   687  }
   688  
   689  // References returns cross-reference indexes for the specified packages.
   690  //
   691  // If these indexes cannot be loaded from cache, the requested packages may
   692  // be type-checked.
   693  func (s *Snapshot) References(ctx context.Context, ids ...PackageID) ([]xrefIndex, error) {
   694  	ctx, done := event.Start(ctx, "cache.snapshot.References")
   695  	defer done()
   696  
   697  	indexes := make([]xrefIndex, len(ids))
   698  	pre := func(i int, ph *packageHandle) bool {
   699  		data, err := filecache.Get(xrefsKind, ph.key)
   700  		if err == nil { // hit
   701  			indexes[i] = xrefIndex{mp: ph.mp, data: data}
   702  			return false
   703  		} else if err != filecache.ErrNotFound {
   704  			event.Error(ctx, "reading xrefs from filecache", err)
   705  		}
   706  		return true
   707  	}
   708  	post := func(i int, pkg *Package) {
   709  		indexes[i] = xrefIndex{mp: pkg.metadata, data: pkg.pkg.xrefs()}
   710  	}
   711  	return indexes, s.forEachPackage(ctx, ids, pre, post)
   712  }
   713  
   714  // An xrefIndex is a helper for looking up references in a given package.
   715  type xrefIndex struct {
   716  	mp   *metadata.Package
   717  	data []byte
   718  }
   719  
   720  func (index xrefIndex) Lookup(targets map[PackagePath]map[objectpath.Path]struct{}) []protocol.Location {
   721  	return xrefs.Lookup(index.mp, index.data, targets)
   722  }
   723  
   724  // MethodSets returns method-set indexes for the specified packages.
   725  //
   726  // If these indexes cannot be loaded from cache, the requested packages may
   727  // be type-checked.
   728  func (s *Snapshot) MethodSets(ctx context.Context, ids ...PackageID) ([]*methodsets.Index, error) {
   729  	ctx, done := event.Start(ctx, "cache.snapshot.MethodSets")
   730  	defer done()
   731  
   732  	indexes := make([]*methodsets.Index, len(ids))
   733  	pre := func(i int, ph *packageHandle) bool {
   734  		data, err := filecache.Get(methodSetsKind, ph.key)
   735  		if err == nil { // hit
   736  			indexes[i] = methodsets.Decode(data)
   737  			return false
   738  		} else if err != filecache.ErrNotFound {
   739  			event.Error(ctx, "reading methodsets from filecache", err)
   740  		}
   741  		return true
   742  	}
   743  	post := func(i int, pkg *Package) {
   744  		indexes[i] = pkg.pkg.methodsets()
   745  	}
   746  	return indexes, s.forEachPackage(ctx, ids, pre, post)
   747  }
   748  
   749  // MetadataForFile returns a new slice containing metadata for each
   750  // package containing the Go file identified by uri, ordered by the
   751  // number of CompiledGoFiles (i.e. "narrowest" to "widest" package),
   752  // and secondarily by IsIntermediateTestVariant (false < true).
   753  // The result may include tests and intermediate test variants of
   754  // importable packages.
   755  // It returns an error if the context was cancelled.
   756  func (s *Snapshot) MetadataForFile(ctx context.Context, uri protocol.DocumentURI) ([]*metadata.Package, error) {
   757  	if s.view.typ == AdHocView {
   758  		// As described in golang/go#57209, in ad-hoc workspaces (where we load ./
   759  		// rather than ./...), preempting the directory load with file loads can
   760  		// lead to an inconsistent outcome, where certain files are loaded with
   761  		// command-line-arguments packages and others are loaded only in the ad-hoc
   762  		// package. Therefore, ensure that the workspace is loaded before doing any
   763  		// file loads.
   764  		if err := s.awaitLoaded(ctx); err != nil {
   765  			return nil, err
   766  		}
   767  	}
   768  
   769  	s.mu.Lock()
   770  
   771  	// Start with the set of package associations derived from the last load.
   772  	ids := s.meta.IDs[uri]
   773  
   774  	shouldLoad := false // whether any packages containing uri are marked 'shouldLoad'
   775  	for _, id := range ids {
   776  		if pkgs, _ := s.shouldLoad.Get(id); len(pkgs) > 0 {
   777  			shouldLoad = true
   778  		}
   779  	}
   780  
   781  	// Check if uri is known to be unloadable.
   782  	unloadable := s.unloadableFiles.Contains(uri)
   783  
   784  	s.mu.Unlock()
   785  
   786  	// Reload if loading is likely to improve the package associations for uri:
   787  	//  - uri is not contained in any valid packages
   788  	//  - ...or one of the packages containing uri is marked 'shouldLoad'
   789  	//  - ...but uri is not unloadable
   790  	if (shouldLoad || len(ids) == 0) && !unloadable {
   791  		scope := fileLoadScope(uri)
   792  		err := s.load(ctx, false, scope)
   793  
   794  		//
   795  		// Return the context error here as the current operation is no longer
   796  		// valid.
   797  		if err != nil {
   798  			// Guard against failed loads due to context cancellation. We don't want
   799  			// to mark loads as completed if they failed due to context cancellation.
   800  			if ctx.Err() != nil {
   801  				return nil, ctx.Err()
   802  			}
   803  
   804  			// Don't return an error here, as we may still return stale IDs.
   805  			// Furthermore, the result of MetadataForFile should be consistent upon
   806  			// subsequent calls, even if the file is marked as unloadable.
   807  			if !errors.Is(err, errNoPackages) {
   808  				event.Error(ctx, "MetadataForFile", err)
   809  			}
   810  		}
   811  
   812  		// We must clear scopes after loading.
   813  		//
   814  		// TODO(rfindley): unlike reloadWorkspace, this is simply marking loaded
   815  		// packages as loaded. We could do this from snapshot.load and avoid
   816  		// raciness.
   817  		s.clearShouldLoad(scope)
   818  	}
   819  
   820  	// Retrieve the metadata.
   821  	s.mu.Lock()
   822  	defer s.mu.Unlock()
   823  	ids = s.meta.IDs[uri]
   824  	metas := make([]*metadata.Package, len(ids))
   825  	for i, id := range ids {
   826  		metas[i] = s.meta.Packages[id]
   827  		if metas[i] == nil {
   828  			panic("nil metadata")
   829  		}
   830  	}
   831  	// Metadata is only ever added by loading,
   832  	// so if we get here and still have
   833  	// no IDs, uri is unloadable.
   834  	if !unloadable && len(ids) == 0 {
   835  		s.unloadableFiles.Add(uri)
   836  	}
   837  
   838  	// Sort packages "narrowest" to "widest" (in practice:
   839  	// non-tests before tests), and regular packages before
   840  	// their intermediate test variants (which have the same
   841  	// files but different imports).
   842  	sort.Slice(metas, func(i, j int) bool {
   843  		x, y := metas[i], metas[j]
   844  		xfiles, yfiles := len(x.CompiledGoFiles), len(y.CompiledGoFiles)
   845  		if xfiles != yfiles {
   846  			return xfiles < yfiles
   847  		}
   848  		return boolLess(x.IsIntermediateTestVariant(), y.IsIntermediateTestVariant())
   849  	})
   850  
   851  	return metas, nil
   852  }
   853  
   854  func boolLess(x, y bool) bool { return !x && y } // false < true
   855  
   856  // ReverseDependencies returns a new mapping whose entries are
   857  // the ID and Metadata of each package in the workspace that
   858  // directly or transitively depend on the package denoted by id,
   859  // excluding id itself.
   860  func (s *Snapshot) ReverseDependencies(ctx context.Context, id PackageID, transitive bool) (map[PackageID]*metadata.Package, error) {
   861  	if err := s.awaitLoaded(ctx); err != nil {
   862  		return nil, err
   863  	}
   864  
   865  	meta := s.MetadataGraph()
   866  	var rdeps map[PackageID]*metadata.Package
   867  	if transitive {
   868  		rdeps = meta.ReverseReflexiveTransitiveClosure(id)
   869  
   870  		// Remove the original package ID from the map.
   871  		// (Callers all want irreflexivity but it's easier
   872  		// to compute reflexively then subtract.)
   873  		delete(rdeps, id)
   874  
   875  	} else {
   876  		// direct reverse dependencies
   877  		rdeps = make(map[PackageID]*metadata.Package)
   878  		for _, rdepID := range meta.ImportedBy[id] {
   879  			if rdep := meta.Packages[rdepID]; rdep != nil {
   880  				rdeps[rdepID] = rdep
   881  			}
   882  		}
   883  	}
   884  
   885  	return rdeps, nil
   886  }
   887  
   888  // -- Active package tracking --
   889  //
   890  // We say a package is "active" if any of its files are open.
   891  // This is an optimization: the "active" concept is an
   892  // implementation detail of the cache and is not exposed
   893  // in the source or Snapshot API.
   894  // After type-checking we keep active packages in memory.
   895  // The activePackages persistent map does bookkeeping for
   896  // the set of active packages.
   897  
   898  // getActivePackage returns a the memoized active package for id, if it exists.
   899  // If id is not active or has not yet been type-checked, it returns nil.
   900  func (s *Snapshot) getActivePackage(id PackageID) *Package {
   901  	s.mu.Lock()
   902  	defer s.mu.Unlock()
   903  
   904  	if value, ok := s.activePackages.Get(id); ok {
   905  		return value
   906  	}
   907  	return nil
   908  }
   909  
   910  // setActivePackage checks if pkg is active, and if so either records it in
   911  // the active packages map or returns the existing memoized active package for id.
   912  func (s *Snapshot) setActivePackage(id PackageID, pkg *Package) {
   913  	s.mu.Lock()
   914  	defer s.mu.Unlock()
   915  
   916  	if _, ok := s.activePackages.Get(id); ok {
   917  		return // already memoized
   918  	}
   919  
   920  	if containsOpenFileLocked(s, pkg.Metadata()) {
   921  		s.activePackages.Set(id, pkg, nil)
   922  	} else {
   923  		s.activePackages.Set(id, (*Package)(nil), nil) // remember that pkg is not open
   924  	}
   925  }
   926  
   927  func (s *Snapshot) resetActivePackagesLocked() {
   928  	s.activePackages.Destroy()
   929  	s.activePackages = new(persistent.Map[PackageID, *Package])
   930  }
   931  
   932  // See Session.FileWatchingGlobPatterns for a description of gopls' file
   933  // watching heuristic.
   934  func (s *Snapshot) fileWatchingGlobPatterns() map[protocol.RelativePattern]unit {
   935  	// Always watch files that may change the view definition.
   936  	patterns := make(map[protocol.RelativePattern]unit)
   937  
   938  	// If GOWORK is outside the folder, ensure we are watching it.
   939  	if s.view.gowork != "" && !s.view.folder.Dir.Encloses(s.view.gowork) {
   940  		workPattern := protocol.RelativePattern{
   941  			BaseURI: s.view.gowork.Dir(),
   942  			Pattern: path.Base(string(s.view.gowork)),
   943  		}
   944  		patterns[workPattern] = unit{}
   945  	}
   946  
   947  	extensions := "go,mod,sum,work"
   948  	for _, ext := range s.Options().TemplateExtensions {
   949  		extensions += "," + ext
   950  	}
   951  	watchGoFiles := fmt.Sprintf("**/*.{%s}", extensions)
   952  
   953  	var dirs []string
   954  	if s.view.moduleMode() {
   955  		if s.view.typ == GoWorkView {
   956  			workVendorDir := filepath.Join(s.view.gowork.Dir().Path(), "vendor")
   957  			workVendorURI := protocol.URIFromPath(workVendorDir)
   958  			patterns[protocol.RelativePattern{BaseURI: workVendorURI, Pattern: watchGoFiles}] = unit{}
   959  		}
   960  
   961  		// In module mode, watch directories containing active modules, and collect
   962  		// these dirs for later filtering the set of known directories.
   963  		//
   964  		// The assumption is that the user is not actively editing non-workspace
   965  		// modules, so don't pay the price of file watching.
   966  		for modFile := range s.view.workspaceModFiles {
   967  			dir := filepath.Dir(modFile.Path())
   968  			dirs = append(dirs, dir)
   969  
   970  			// TODO(golang/go#64724): thoroughly test these patterns, particularly on
   971  			// on Windows.
   972  			//
   973  			// Note that glob patterns should use '/' on Windows:
   974  			// https://code.visualstudio.com/docs/editor/glob-patterns
   975  			patterns[protocol.RelativePattern{BaseURI: modFile.Dir(), Pattern: watchGoFiles}] = unit{}
   976  		}
   977  	} else {
   978  		// In non-module modes (GOPATH or AdHoc), we just watch the workspace root.
   979  		dirs = []string{s.view.root.Path()}
   980  		patterns[protocol.RelativePattern{Pattern: watchGoFiles}] = unit{}
   981  	}
   982  
   983  	if s.watchSubdirs() {
   984  		// Some clients (e.g. VS Code) do not send notifications for changes to
   985  		// directories that contain Go code (golang/go#42348). To handle this,
   986  		// explicitly watch all of the directories in the workspace. We find them
   987  		// by adding the directories of every file in the snapshot's workspace
   988  		// directories. There may be thousands of patterns, each a single
   989  		// directory.
   990  		//
   991  		// We compute this set by looking at files that we've previously observed.
   992  		// This may miss changed to directories that we haven't observed, but that
   993  		// shouldn't matter as there is nothing to invalidate (if a directory falls
   994  		// in forest, etc).
   995  		//
   996  		// (A previous iteration created a single glob pattern holding a union of
   997  		// all the directories, but this was found to cause VS Code to get stuck
   998  		// for several minutes after a buffer was saved twice in a workspace that
   999  		// had >8000 watched directories.)
  1000  		//
  1001  		// Some clients (notably coc.nvim, which uses watchman for globs) perform
  1002  		// poorly with a large list of individual directories.
  1003  		s.addKnownSubdirs(patterns, dirs)
  1004  	}
  1005  
  1006  	return patterns
  1007  }
  1008  
  1009  func (s *Snapshot) addKnownSubdirs(patterns map[protocol.RelativePattern]unit, wsDirs []string) {
  1010  	s.mu.Lock()
  1011  	defer s.mu.Unlock()
  1012  
  1013  	s.files.getDirs().Range(func(dir string) {
  1014  		for _, wsDir := range wsDirs {
  1015  			if pathutil.InDir(wsDir, dir) {
  1016  				patterns[protocol.RelativePattern{Pattern: filepath.ToSlash(dir)}] = unit{}
  1017  			}
  1018  		}
  1019  	})
  1020  }
  1021  
  1022  // watchSubdirs reports whether gopls should request separate file watchers for
  1023  // each relevant subdirectory. This is necessary only for clients (namely VS
  1024  // Code) that do not send notifications for individual files in a directory
  1025  // when the entire directory is deleted.
  1026  func (s *Snapshot) watchSubdirs() bool {
  1027  	switch p := s.Options().SubdirWatchPatterns; p {
  1028  	case settings.SubdirWatchPatternsOn:
  1029  		return true
  1030  	case settings.SubdirWatchPatternsOff:
  1031  		return false
  1032  	case settings.SubdirWatchPatternsAuto:
  1033  		// See the documentation of InternalOptions.SubdirWatchPatterns for an
  1034  		// explanation of why VS Code gets a different default value here.
  1035  		//
  1036  		// Unfortunately, there is no authoritative list of client names, nor any
  1037  		// requirements that client names do not change. We should update the VS
  1038  		// Code extension to set a default value of "subdirWatchPatterns" to "on",
  1039  		// so that this workaround is only temporary.
  1040  		if s.Options().ClientInfo != nil && s.Options().ClientInfo.Name == "Visual Studio Code" {
  1041  			return true
  1042  		}
  1043  		return false
  1044  	default:
  1045  		bug.Reportf("invalid subdirWatchPatterns: %q", p)
  1046  		return false
  1047  	}
  1048  }
  1049  
  1050  // filesInDir returns all files observed by the snapshot that are contained in
  1051  // a directory with the provided URI.
  1052  func (s *Snapshot) filesInDir(uri protocol.DocumentURI) []protocol.DocumentURI {
  1053  	s.mu.Lock()
  1054  	defer s.mu.Unlock()
  1055  
  1056  	dir := uri.Path()
  1057  	if !s.files.getDirs().Contains(dir) {
  1058  		return nil
  1059  	}
  1060  	var files []protocol.DocumentURI
  1061  	s.files.foreach(func(uri protocol.DocumentURI, _ file.Handle) {
  1062  		if pathutil.InDir(dir, uri.Path()) {
  1063  			files = append(files, uri)
  1064  		}
  1065  	})
  1066  	return files
  1067  }
  1068  
  1069  // WorkspaceMetadata returns a new, unordered slice containing
  1070  // metadata for all ordinary and test packages (but not
  1071  // intermediate test variants) in the workspace.
  1072  //
  1073  // The workspace is the set of modules typically defined by a
  1074  // go.work file. It is not transitively closed: for example,
  1075  // the standard library is not usually part of the workspace
  1076  // even though every module in the workspace depends on it.
  1077  //
  1078  // Operations that must inspect all the dependencies of the
  1079  // workspace packages should instead use AllMetadata.
  1080  func (s *Snapshot) WorkspaceMetadata(ctx context.Context) ([]*metadata.Package, error) {
  1081  	if err := s.awaitLoaded(ctx); err != nil {
  1082  		return nil, err
  1083  	}
  1084  
  1085  	s.mu.Lock()
  1086  	defer s.mu.Unlock()
  1087  
  1088  	meta := make([]*metadata.Package, 0, s.workspacePackages.Len())
  1089  	s.workspacePackages.Range(func(id PackageID, _ PackagePath) {
  1090  		meta = append(meta, s.meta.Packages[id])
  1091  	})
  1092  	return meta, nil
  1093  }
  1094  
  1095  // isWorkspacePackage reports whether the given package ID refers to a
  1096  // workspace package for the snapshot.
  1097  func (s *Snapshot) isWorkspacePackage(id PackageID) bool {
  1098  	s.mu.Lock()
  1099  	defer s.mu.Unlock()
  1100  	_, ok := s.workspacePackages.Value(id)
  1101  	return ok
  1102  }
  1103  
  1104  // Symbols extracts and returns symbol information for every file contained in
  1105  // a loaded package. It awaits snapshot loading.
  1106  //
  1107  // If workspaceOnly is set, this only includes symbols from files in a
  1108  // workspace package. Otherwise, it returns symbols from all loaded packages.
  1109  //
  1110  // TODO(rfindley): move to symbols.go.
  1111  func (s *Snapshot) Symbols(ctx context.Context, workspaceOnly bool) (map[protocol.DocumentURI][]Symbol, error) {
  1112  	var (
  1113  		meta []*metadata.Package
  1114  		err  error
  1115  	)
  1116  	if workspaceOnly {
  1117  		meta, err = s.WorkspaceMetadata(ctx)
  1118  	} else {
  1119  		meta, err = s.AllMetadata(ctx)
  1120  	}
  1121  	if err != nil {
  1122  		return nil, fmt.Errorf("loading metadata: %v", err)
  1123  	}
  1124  
  1125  	goFiles := make(map[protocol.DocumentURI]struct{})
  1126  	for _, mp := range meta {
  1127  		for _, uri := range mp.GoFiles {
  1128  			goFiles[uri] = struct{}{}
  1129  		}
  1130  		for _, uri := range mp.CompiledGoFiles {
  1131  			goFiles[uri] = struct{}{}
  1132  		}
  1133  	}
  1134  
  1135  	// Symbolize them in parallel.
  1136  	var (
  1137  		group    errgroup.Group
  1138  		nprocs   = 2 * runtime.GOMAXPROCS(-1) // symbolize is a mix of I/O and CPU
  1139  		resultMu sync.Mutex
  1140  		result   = make(map[protocol.DocumentURI][]Symbol)
  1141  	)
  1142  	group.SetLimit(nprocs)
  1143  	for uri := range goFiles {
  1144  		uri := uri
  1145  		group.Go(func() error {
  1146  			symbols, err := s.symbolize(ctx, uri)
  1147  			if err != nil {
  1148  				return err
  1149  			}
  1150  			resultMu.Lock()
  1151  			result[uri] = symbols
  1152  			resultMu.Unlock()
  1153  			return nil
  1154  		})
  1155  	}
  1156  	// Keep going on errors, but log the first failure.
  1157  	// Partial results are better than no symbol results.
  1158  	if err := group.Wait(); err != nil {
  1159  		event.Error(ctx, "getting snapshot symbols", err)
  1160  	}
  1161  	return result, nil
  1162  }
  1163  
  1164  // AllMetadata returns a new unordered array of metadata for
  1165  // all packages known to this snapshot, which includes the
  1166  // packages of all workspace modules plus their transitive
  1167  // import dependencies.
  1168  //
  1169  // It may also contain ad-hoc packages for standalone files.
  1170  // It includes all test variants.
  1171  //
  1172  // TODO(rfindley): Replace this with s.MetadataGraph().
  1173  func (s *Snapshot) AllMetadata(ctx context.Context) ([]*metadata.Package, error) {
  1174  	if err := s.awaitLoaded(ctx); err != nil {
  1175  		return nil, err
  1176  	}
  1177  
  1178  	g := s.MetadataGraph()
  1179  
  1180  	meta := make([]*metadata.Package, 0, len(g.Packages))
  1181  	for _, mp := range g.Packages {
  1182  		meta = append(meta, mp)
  1183  	}
  1184  	return meta, nil
  1185  }
  1186  
  1187  // GoModForFile returns the URI of the go.mod file for the given URI.
  1188  //
  1189  // TODO(rfindley): clarify that this is only active modules. Or update to just
  1190  // use findRootPattern.
  1191  func (s *Snapshot) GoModForFile(uri protocol.DocumentURI) protocol.DocumentURI {
  1192  	return moduleForURI(s.view.workspaceModFiles, uri)
  1193  }
  1194  
  1195  func moduleForURI(modFiles map[protocol.DocumentURI]struct{}, uri protocol.DocumentURI) protocol.DocumentURI {
  1196  	var match protocol.DocumentURI
  1197  	for modURI := range modFiles {
  1198  		if !modURI.Dir().Encloses(uri) {
  1199  			continue
  1200  		}
  1201  		if len(modURI) > len(match) {
  1202  			match = modURI
  1203  		}
  1204  	}
  1205  	return match
  1206  }
  1207  
  1208  // nearestModFile finds the nearest go.mod file contained in the directory
  1209  // containing uri, or a parent of that directory.
  1210  //
  1211  // The given uri must be a file, not a directory.
  1212  func nearestModFile(ctx context.Context, uri protocol.DocumentURI, fs file.Source) (protocol.DocumentURI, error) {
  1213  	dir := filepath.Dir(uri.Path())
  1214  	return findRootPattern(ctx, protocol.URIFromPath(dir), "go.mod", fs)
  1215  }
  1216  
  1217  // Metadata returns the metadata for the specified package,
  1218  // or nil if it was not found.
  1219  func (s *Snapshot) Metadata(id PackageID) *metadata.Package {
  1220  	s.mu.Lock()
  1221  	defer s.mu.Unlock()
  1222  	return s.meta.Packages[id]
  1223  }
  1224  
  1225  // clearShouldLoad clears package IDs that no longer need to be reloaded after
  1226  // scopes has been loaded.
  1227  func (s *Snapshot) clearShouldLoad(scopes ...loadScope) {
  1228  	s.mu.Lock()
  1229  	defer s.mu.Unlock()
  1230  
  1231  	for _, scope := range scopes {
  1232  		switch scope := scope.(type) {
  1233  		case packageLoadScope:
  1234  			scopePath := PackagePath(scope)
  1235  			var toDelete []PackageID
  1236  			s.shouldLoad.Range(func(id PackageID, pkgPaths []PackagePath) {
  1237  				for _, pkgPath := range pkgPaths {
  1238  					if pkgPath == scopePath {
  1239  						toDelete = append(toDelete, id)
  1240  					}
  1241  				}
  1242  			})
  1243  			for _, id := range toDelete {
  1244  				s.shouldLoad.Delete(id)
  1245  			}
  1246  		case fileLoadScope:
  1247  			uri := protocol.DocumentURI(scope)
  1248  			ids := s.meta.IDs[uri]
  1249  			for _, id := range ids {
  1250  				s.shouldLoad.Delete(id)
  1251  			}
  1252  		}
  1253  	}
  1254  }
  1255  
  1256  // FindFile returns the FileHandle for the given URI, if it is already
  1257  // in the given snapshot.
  1258  // TODO(adonovan): delete this operation; use ReadFile instead.
  1259  func (s *Snapshot) FindFile(uri protocol.DocumentURI) file.Handle {
  1260  	s.mu.Lock()
  1261  	defer s.mu.Unlock()
  1262  
  1263  	result, _ := s.files.get(uri)
  1264  	return result
  1265  }
  1266  
  1267  // ReadFile returns a File for the given URI. If the file is unknown it is added
  1268  // to the managed set.
  1269  //
  1270  // ReadFile succeeds even if the file does not exist. A non-nil error return
  1271  // indicates some type of internal error, for example if ctx is cancelled.
  1272  func (s *Snapshot) ReadFile(ctx context.Context, uri protocol.DocumentURI) (file.Handle, error) {
  1273  	s.mu.Lock()
  1274  	defer s.mu.Unlock()
  1275  
  1276  	return lockedSnapshot{s}.ReadFile(ctx, uri)
  1277  }
  1278  
  1279  // lockedSnapshot implements the file.Source interface, while holding s.mu.
  1280  //
  1281  // TODO(rfindley): This unfortunate type had been eliminated, but it had to be
  1282  // restored to fix golang/go#65801. We should endeavor to remove it again.
  1283  type lockedSnapshot struct {
  1284  	s *Snapshot
  1285  }
  1286  
  1287  func (s lockedSnapshot) ReadFile(ctx context.Context, uri protocol.DocumentURI) (file.Handle, error) {
  1288  	fh, ok := s.s.files.get(uri)
  1289  	if !ok {
  1290  		var err error
  1291  		fh, err = s.s.view.fs.ReadFile(ctx, uri)
  1292  		if err != nil {
  1293  			return nil, err
  1294  		}
  1295  		s.s.files.set(uri, fh)
  1296  	}
  1297  	return fh, nil
  1298  }
  1299  
  1300  // preloadFiles delegates to the view FileSource to read the requested uris in
  1301  // parallel, without holding the snapshot lock.
  1302  func (s *Snapshot) preloadFiles(ctx context.Context, uris []protocol.DocumentURI) {
  1303  	files := make([]file.Handle, len(uris))
  1304  	var wg sync.WaitGroup
  1305  	iolimit := make(chan struct{}, 20) // I/O concurrency limiting semaphore
  1306  	for i, uri := range uris {
  1307  		wg.Add(1)
  1308  		iolimit <- struct{}{}
  1309  		go func(i int, uri protocol.DocumentURI) {
  1310  			defer wg.Done()
  1311  			fh, err := s.view.fs.ReadFile(ctx, uri)
  1312  			<-iolimit
  1313  			if err != nil && ctx.Err() == nil {
  1314  				event.Error(ctx, fmt.Sprintf("reading %s", uri), err)
  1315  				return
  1316  			}
  1317  			files[i] = fh
  1318  		}(i, uri)
  1319  	}
  1320  	wg.Wait()
  1321  
  1322  	s.mu.Lock()
  1323  	defer s.mu.Unlock()
  1324  
  1325  	for i, fh := range files {
  1326  		if fh == nil {
  1327  			continue // error logged above
  1328  		}
  1329  		uri := uris[i]
  1330  		if _, ok := s.files.get(uri); !ok {
  1331  			s.files.set(uri, fh)
  1332  		}
  1333  	}
  1334  }
  1335  
  1336  // IsOpen returns whether the editor currently has a file open.
  1337  func (s *Snapshot) IsOpen(uri protocol.DocumentURI) bool {
  1338  	s.mu.Lock()
  1339  	defer s.mu.Unlock()
  1340  
  1341  	fh, _ := s.files.get(uri)
  1342  	_, open := fh.(*overlay)
  1343  	return open
  1344  }
  1345  
  1346  // MetadataGraph returns the current metadata graph for the Snapshot.
  1347  func (s *Snapshot) MetadataGraph() *metadata.Graph {
  1348  	s.mu.Lock()
  1349  	defer s.mu.Unlock()
  1350  	return s.meta
  1351  }
  1352  
  1353  // InitializationError returns the last error from initialization.
  1354  func (s *Snapshot) InitializationError() *InitializationError {
  1355  	s.mu.Lock()
  1356  	defer s.mu.Unlock()
  1357  	return s.initialErr
  1358  }
  1359  
  1360  // awaitLoaded awaits initialization and package reloading, and returns
  1361  // ctx.Err().
  1362  func (s *Snapshot) awaitLoaded(ctx context.Context) error {
  1363  	// Do not return results until the snapshot's view has been initialized.
  1364  	s.AwaitInitialized(ctx)
  1365  	s.reloadWorkspace(ctx)
  1366  	return ctx.Err()
  1367  }
  1368  
  1369  // AwaitInitialized waits until the snapshot's view is initialized.
  1370  func (s *Snapshot) AwaitInitialized(ctx context.Context) {
  1371  	select {
  1372  	case <-ctx.Done():
  1373  		return
  1374  	case <-s.view.initialWorkspaceLoad:
  1375  	}
  1376  	// We typically prefer to run something as intensive as the IWL without
  1377  	// blocking. I'm not sure if there is a way to do that here.
  1378  	s.initialize(ctx, false)
  1379  }
  1380  
  1381  // reloadWorkspace reloads the metadata for all invalidated workspace packages.
  1382  func (s *Snapshot) reloadWorkspace(ctx context.Context) {
  1383  	var scopes []loadScope
  1384  	var seen map[PackagePath]bool
  1385  	s.mu.Lock()
  1386  	s.shouldLoad.Range(func(_ PackageID, pkgPaths []PackagePath) {
  1387  		for _, pkgPath := range pkgPaths {
  1388  			if seen == nil {
  1389  				seen = make(map[PackagePath]bool)
  1390  			}
  1391  			if seen[pkgPath] {
  1392  				continue
  1393  			}
  1394  			seen[pkgPath] = true
  1395  			scopes = append(scopes, packageLoadScope(pkgPath))
  1396  		}
  1397  	})
  1398  	s.mu.Unlock()
  1399  
  1400  	if len(scopes) == 0 {
  1401  		return
  1402  	}
  1403  
  1404  	// For an ad-hoc view, we cannot reload by package path. Just reload the view.
  1405  	if s.view.typ == AdHocView {
  1406  		scopes = []loadScope{viewLoadScope{}}
  1407  	}
  1408  
  1409  	err := s.load(ctx, false, scopes...)
  1410  
  1411  	// Unless the context was canceled, set "shouldLoad" to false for all
  1412  	// of the metadata we attempted to load.
  1413  	if !errors.Is(err, context.Canceled) {
  1414  		s.clearShouldLoad(scopes...)
  1415  		if err != nil {
  1416  			event.Error(ctx, "reloading workspace", err, s.Labels()...)
  1417  		}
  1418  	}
  1419  }
  1420  
  1421  func (s *Snapshot) orphanedFileDiagnostics(ctx context.Context, overlays []*overlay) ([]*Diagnostic, error) {
  1422  	if err := s.awaitLoaded(ctx); err != nil {
  1423  		return nil, err
  1424  	}
  1425  
  1426  	var diagnostics []*Diagnostic
  1427  	var orphaned []*overlay
  1428  searchOverlays:
  1429  	for _, o := range overlays {
  1430  		uri := o.URI()
  1431  		if s.IsBuiltin(uri) || s.FileKind(o) != file.Go {
  1432  			continue
  1433  		}
  1434  		mps, err := s.MetadataForFile(ctx, uri)
  1435  		if err != nil {
  1436  			return nil, err
  1437  		}
  1438  		for _, mp := range mps {
  1439  			if !metadata.IsCommandLineArguments(mp.ID) || mp.Standalone {
  1440  				continue searchOverlays
  1441  			}
  1442  		}
  1443  		metadata.RemoveIntermediateTestVariants(&mps)
  1444  
  1445  		// With zero-config gopls (golang/go#57979), orphaned file diagnostics
  1446  		// include diagnostics for orphaned files -- not just diagnostics relating
  1447  		// to the reason the files are opened.
  1448  		//
  1449  		// This is because orphaned files are never considered part of a workspace
  1450  		// package: if they are loaded by a view, that view is arbitrary, and they
  1451  		// may be loaded by multiple views. If they were to be diagnosed by
  1452  		// multiple views, their diagnostics may become inconsistent.
  1453  		if len(mps) > 0 {
  1454  			diags, err := s.PackageDiagnostics(ctx, mps[0].ID)
  1455  			if err != nil {
  1456  				return nil, err
  1457  			}
  1458  			diagnostics = append(diagnostics, diags[uri]...)
  1459  		}
  1460  		orphaned = append(orphaned, o)
  1461  	}
  1462  
  1463  	if len(orphaned) == 0 {
  1464  		return nil, nil
  1465  	}
  1466  
  1467  	loadedModFiles := make(map[protocol.DocumentURI]struct{}) // all mod files, including dependencies
  1468  	ignoredFiles := make(map[protocol.DocumentURI]bool)       // files reported in packages.Package.IgnoredFiles
  1469  
  1470  	g := s.MetadataGraph()
  1471  	for _, meta := range g.Packages {
  1472  		if meta.Module != nil && meta.Module.GoMod != "" {
  1473  			gomod := protocol.URIFromPath(meta.Module.GoMod)
  1474  			loadedModFiles[gomod] = struct{}{}
  1475  		}
  1476  		for _, ignored := range meta.IgnoredFiles {
  1477  			ignoredFiles[ignored] = true
  1478  		}
  1479  	}
  1480  
  1481  	initialErr := s.InitializationError()
  1482  
  1483  	for _, fh := range orphaned {
  1484  		pgf, rng, ok := orphanedFileDiagnosticRange(ctx, s.view.parseCache, fh)
  1485  		if !ok {
  1486  			continue // e.g. cancellation or parse error
  1487  		}
  1488  
  1489  		var (
  1490  			msg            string         // if non-empty, report a diagnostic with this message
  1491  			suggestedFixes []SuggestedFix // associated fixes, if any
  1492  		)
  1493  		if initialErr != nil {
  1494  			msg = fmt.Sprintf("initialization failed: %v", initialErr.MainError)
  1495  		} else if goMod, err := nearestModFile(ctx, fh.URI(), s); err == nil && goMod != "" {
  1496  			// If we have a relevant go.mod file, check whether the file is orphaned
  1497  			// due to its go.mod file being inactive. We could also offer a
  1498  			// prescriptive diagnostic in the case that there is no go.mod file, but it
  1499  			// is harder to be precise in that case, and less important.
  1500  			if _, ok := loadedModFiles[goMod]; !ok {
  1501  				modDir := filepath.Dir(goMod.Path())
  1502  				viewDir := s.view.folder.Dir.Path()
  1503  
  1504  				// When the module is underneath the view dir, we offer
  1505  				// "use all modules" quick-fixes.
  1506  				inDir := pathutil.InDir(viewDir, modDir)
  1507  
  1508  				if rel, err := filepath.Rel(viewDir, modDir); err == nil {
  1509  					modDir = rel
  1510  				}
  1511  
  1512  				var fix string
  1513  				if s.view.folder.Env.GoVersion >= 18 {
  1514  					if s.view.gowork != "" {
  1515  						fix = fmt.Sprintf("To fix this problem, you can add this module to your go.work file (%s)", s.view.gowork)
  1516  						if cmd, err := command.NewRunGoWorkCommandCommand("Run `go work use`", command.RunGoWorkArgs{
  1517  							ViewID: s.view.ID(),
  1518  							Args:   []string{"use", modDir},
  1519  						}); err == nil {
  1520  							suggestedFixes = append(suggestedFixes, SuggestedFix{
  1521  								Title:      "Use this module in your go.work file",
  1522  								Command:    &cmd,
  1523  								ActionKind: protocol.QuickFix,
  1524  							})
  1525  						}
  1526  
  1527  						if inDir {
  1528  							if cmd, err := command.NewRunGoWorkCommandCommand("Run `go work use -r`", command.RunGoWorkArgs{
  1529  								ViewID: s.view.ID(),
  1530  								Args:   []string{"use", "-r", "."},
  1531  							}); err == nil {
  1532  								suggestedFixes = append(suggestedFixes, SuggestedFix{
  1533  									Title:      "Use all modules in your workspace",
  1534  									Command:    &cmd,
  1535  									ActionKind: protocol.QuickFix,
  1536  								})
  1537  							}
  1538  						}
  1539  					} else {
  1540  						fix = "To fix this problem, you can add a go.work file that uses this directory."
  1541  
  1542  						if cmd, err := command.NewRunGoWorkCommandCommand("Run `go work init && go work use`", command.RunGoWorkArgs{
  1543  							ViewID:    s.view.ID(),
  1544  							InitFirst: true,
  1545  							Args:      []string{"use", modDir},
  1546  						}); err == nil {
  1547  							suggestedFixes = []SuggestedFix{
  1548  								{
  1549  									Title:      "Add a go.work file using this module",
  1550  									Command:    &cmd,
  1551  									ActionKind: protocol.QuickFix,
  1552  								},
  1553  							}
  1554  						}
  1555  
  1556  						if inDir {
  1557  							if cmd, err := command.NewRunGoWorkCommandCommand("Run `go work init && go work use -r`", command.RunGoWorkArgs{
  1558  								ViewID:    s.view.ID(),
  1559  								InitFirst: true,
  1560  								Args:      []string{"use", "-r", "."},
  1561  							}); err == nil {
  1562  								suggestedFixes = append(suggestedFixes, SuggestedFix{
  1563  									Title:      "Add a go.work file using all modules in your workspace",
  1564  									Command:    &cmd,
  1565  									ActionKind: protocol.QuickFix,
  1566  								})
  1567  							}
  1568  						}
  1569  					}
  1570  				} else {
  1571  					fix = `To work with multiple modules simultaneously, please upgrade to Go 1.18 or
  1572  later, reinstall gopls, and use a go.work file.`
  1573  				}
  1574  
  1575  				msg = fmt.Sprintf(`This file is within module %q, which is not included in your workspace.
  1576  %s
  1577  See the documentation for more information on setting up your workspace:
  1578  https://github.com/golang/tools/blob/master/gopls/doc/workspace.md.`, modDir, fix)
  1579  			}
  1580  		}
  1581  
  1582  		if msg == "" {
  1583  			if ignoredFiles[fh.URI()] {
  1584  				// TODO(rfindley): use the constraint package to check if the file
  1585  				// _actually_ satisfies the current build context.
  1586  				hasConstraint := false
  1587  				walkConstraints(pgf.File, func(constraint.Expr) bool {
  1588  					hasConstraint = true
  1589  					return false
  1590  				})
  1591  				var fix string
  1592  				if hasConstraint {
  1593  					fix = `This file may be excluded due to its build tags; try adding "-tags=<build tag>" to your gopls "buildFlags" configuration
  1594  See the documentation for more information on working with build tags:
  1595  https://github.com/golang/tools/blob/master/gopls/doc/settings.md#buildflags-string.`
  1596  				} else if strings.Contains(filepath.Base(fh.URI().Path()), "_") {
  1597  					fix = `This file may be excluded due to its GOOS/GOARCH, or other build constraints.`
  1598  				} else {
  1599  					fix = `This file is ignored by your gopls build.` // we don't know why
  1600  				}
  1601  				msg = fmt.Sprintf("No packages found for open file %s.\n%s", fh.URI().Path(), fix)
  1602  			} else {
  1603  				// Fall back: we're not sure why the file is orphaned.
  1604  				// TODO(rfindley): we could do better here, diagnosing the lack of a
  1605  				// go.mod file and malformed file names (see the perc%ent marker test).
  1606  				msg = fmt.Sprintf("No packages found for open file %s.", fh.URI().Path())
  1607  			}
  1608  		}
  1609  
  1610  		if msg != "" {
  1611  			d := &Diagnostic{
  1612  				URI:            fh.URI(),
  1613  				Range:          rng,
  1614  				Severity:       protocol.SeverityWarning,
  1615  				Source:         ListError,
  1616  				Message:        msg,
  1617  				SuggestedFixes: suggestedFixes,
  1618  			}
  1619  			if ok := bundleQuickFixes(d); !ok {
  1620  				bug.Reportf("failed to bundle quick fixes for %v", d)
  1621  			}
  1622  			// Only report diagnostics if we detect an actual exclusion.
  1623  			diagnostics = append(diagnostics, d)
  1624  		}
  1625  	}
  1626  	return diagnostics, nil
  1627  }
  1628  
  1629  // orphanedFileDiagnosticRange returns the position to use for orphaned file diagnostics.
  1630  // We only warn about an orphaned file if it is well-formed enough to actually
  1631  // be part of a package. Otherwise, we need more information.
  1632  func orphanedFileDiagnosticRange(ctx context.Context, cache *parseCache, fh file.Handle) (*ParsedGoFile, protocol.Range, bool) {
  1633  	pgfs, err := cache.parseFiles(ctx, token.NewFileSet(), ParseHeader, false, fh)
  1634  	if err != nil {
  1635  		return nil, protocol.Range{}, false
  1636  	}
  1637  	pgf := pgfs[0]
  1638  	if !pgf.File.Name.Pos().IsValid() {
  1639  		return nil, protocol.Range{}, false
  1640  	}
  1641  	rng, err := pgf.PosRange(pgf.File.Name.Pos(), pgf.File.Name.End())
  1642  	if err != nil {
  1643  		return nil, protocol.Range{}, false
  1644  	}
  1645  	return pgf, rng, true
  1646  }
  1647  
  1648  // TODO(golang/go#53756): this function needs to consider more than just the
  1649  // absolute URI, for example:
  1650  //   - the position of /vendor/ with respect to the relevant module root
  1651  //   - whether or not go.work is in use (as vendoring isn't supported in workspace mode)
  1652  //
  1653  // Most likely, each call site of inVendor needs to be reconsidered to
  1654  // understand and correctly implement the desired behavior.
  1655  func inVendor(uri protocol.DocumentURI) bool {
  1656  	_, after, found := strings.Cut(string(uri), "/vendor/")
  1657  	// Only subdirectories of /vendor/ are considered vendored
  1658  	// (/vendor/a/foo.go is vendored, /vendor/foo.go is not).
  1659  	return found && strings.Contains(after, "/")
  1660  }
  1661  
  1662  // clone copies state from the receiver into a new Snapshot, applying the given
  1663  // state changes.
  1664  //
  1665  // The caller of clone must call Snapshot.decref on the returned
  1666  // snapshot when they are finished using it.
  1667  //
  1668  // The resulting bool reports whether the change invalidates any derived
  1669  // diagnostics for the snapshot, for example because it invalidates Packages or
  1670  // parsed go.mod files. This is used to mark a view as needing diagnosis in the
  1671  // server.
  1672  //
  1673  // TODO(rfindley): long term, it may be better to move responsibility for
  1674  // diagnostics into the Snapshot (e.g. a Snapshot.Diagnostics method), at which
  1675  // point the Snapshot could be responsible for tracking and forwarding a
  1676  // 'viewsToDiagnose' field. As is, this field is instead externalized in the
  1677  // server.viewsToDiagnose map. Moving it to the snapshot would entirely
  1678  // eliminate any 'relevance' heuristics from Session.DidModifyFiles, but would
  1679  // also require more strictness about diagnostic dependencies. For example,
  1680  // template.Diagnostics currently re-parses every time: there is no Snapshot
  1681  // data responsible for providing these diagnostics.
  1682  func (s *Snapshot) clone(ctx, bgCtx context.Context, changed StateChange, done func()) (*Snapshot, bool) {
  1683  	changedFiles := changed.Files
  1684  	ctx, stop := event.Start(ctx, "cache.snapshot.clone")
  1685  	defer stop()
  1686  
  1687  	s.mu.Lock()
  1688  	defer s.mu.Unlock()
  1689  
  1690  	// TODO(rfindley): reorganize this function to make the derivation of
  1691  	// needsDiagnosis clearer.
  1692  	needsDiagnosis := len(changed.GCDetails) > 0 || len(changed.ModuleUpgrades) > 0 || len(changed.Vulns) > 0
  1693  
  1694  	bgCtx, cancel := context.WithCancel(bgCtx)
  1695  	result := &Snapshot{
  1696  		sequenceID:        s.sequenceID + 1,
  1697  		store:             s.store,
  1698  		refcount:          1, // Snapshots are born referenced.
  1699  		done:              done,
  1700  		view:              s.view,
  1701  		backgroundCtx:     bgCtx,
  1702  		cancel:            cancel,
  1703  		builtin:           s.builtin,
  1704  		initialized:       s.initialized,
  1705  		initialErr:        s.initialErr,
  1706  		packages:          s.packages.Clone(),
  1707  		activePackages:    s.activePackages.Clone(),
  1708  		files:             s.files.clone(changedFiles),
  1709  		symbolizeHandles:  cloneWithout(s.symbolizeHandles, changedFiles, nil),
  1710  		workspacePackages: s.workspacePackages,
  1711  		shouldLoad:        s.shouldLoad.Clone(),      // not cloneWithout: shouldLoad is cleared on loads
  1712  		unloadableFiles:   s.unloadableFiles.Clone(), // not cloneWithout: typing in a file doesn't necessarily make it loadable
  1713  		parseModHandles:   cloneWithout(s.parseModHandles, changedFiles, &needsDiagnosis),
  1714  		parseWorkHandles:  cloneWithout(s.parseWorkHandles, changedFiles, &needsDiagnosis),
  1715  		modTidyHandles:    cloneWithout(s.modTidyHandles, changedFiles, &needsDiagnosis),
  1716  		modWhyHandles:     cloneWithout(s.modWhyHandles, changedFiles, &needsDiagnosis),
  1717  		modVulnHandles:    cloneWithout(s.modVulnHandles, changedFiles, &needsDiagnosis),
  1718  		importGraph:       s.importGraph,
  1719  		pkgIndex:          s.pkgIndex,
  1720  		moduleUpgrades:    cloneWith(s.moduleUpgrades, changed.ModuleUpgrades),
  1721  		vulns:             cloneWith(s.vulns, changed.Vulns),
  1722  	}
  1723  
  1724  	// Compute the new set of packages for which we want gc details, after
  1725  	// applying changed.GCDetails.
  1726  	if len(s.gcOptimizationDetails) > 0 || len(changed.GCDetails) > 0 {
  1727  		newGCDetails := make(map[metadata.PackageID]unit)
  1728  		for id := range s.gcOptimizationDetails {
  1729  			if _, ok := changed.GCDetails[id]; !ok {
  1730  				newGCDetails[id] = unit{} // no change
  1731  			}
  1732  		}
  1733  		for id, want := range changed.GCDetails {
  1734  			if want {
  1735  				newGCDetails[id] = unit{}
  1736  			}
  1737  		}
  1738  		if len(newGCDetails) > 0 {
  1739  			result.gcOptimizationDetails = newGCDetails
  1740  		}
  1741  	}
  1742  
  1743  	reinit := false
  1744  
  1745  	// Changes to vendor tree may require reinitialization,
  1746  	// either because of an initialization error
  1747  	// (e.g. "inconsistent vendoring detected"), or because
  1748  	// one or more modules may have moved into or out of the
  1749  	// vendor tree after 'go mod vendor' or 'rm -fr vendor/'.
  1750  	//
  1751  	// In this case, we consider the actual modification to see if was a creation
  1752  	// or deletion.
  1753  	//
  1754  	// TODO(rfindley): revisit the location of this check.
  1755  	for _, mod := range changed.Modifications {
  1756  		if inVendor(mod.URI) && (mod.Action == file.Create || mod.Action == file.Delete) ||
  1757  			strings.HasSuffix(string(mod.URI), "/vendor/modules.txt") {
  1758  
  1759  			reinit = true
  1760  			break
  1761  		}
  1762  	}
  1763  
  1764  	// Collect observed file handles for changed URIs from the old snapshot, if
  1765  	// they exist. Importantly, we don't call ReadFile here: consider the case
  1766  	// where a file is added on disk; we don't want to read the newly added file
  1767  	// into the old snapshot, as that will break our change detection below.
  1768  	//
  1769  	// TODO(rfindley): it may be more accurate to rely on the modification type
  1770  	// here, similarly to what we do for vendored files above. If we happened not
  1771  	// to have read a file in the previous snapshot, that's not the same as it
  1772  	// actually being created.
  1773  	oldFiles := make(map[protocol.DocumentURI]file.Handle)
  1774  	for uri := range changedFiles {
  1775  		if fh, ok := s.files.get(uri); ok {
  1776  			oldFiles[uri] = fh
  1777  		}
  1778  	}
  1779  	// changedOnDisk determines if the new file handle may have changed on disk.
  1780  	// It over-approximates, returning true if the new file is saved and either
  1781  	// the old file wasn't saved, or the on-disk contents changed.
  1782  	//
  1783  	// oldFH may be nil.
  1784  	changedOnDisk := func(oldFH, newFH file.Handle) bool {
  1785  		if !newFH.SameContentsOnDisk() {
  1786  			return false
  1787  		}
  1788  		if oe, ne := (oldFH != nil && fileExists(oldFH)), fileExists(newFH); !oe || !ne {
  1789  			return oe != ne
  1790  		}
  1791  		return !oldFH.SameContentsOnDisk() || oldFH.Identity() != newFH.Identity()
  1792  	}
  1793  
  1794  	// Reinitialize if any workspace mod file has changed on disk.
  1795  	for uri, newFH := range changedFiles {
  1796  		if _, ok := result.view.workspaceModFiles[uri]; ok && changedOnDisk(oldFiles[uri], newFH) {
  1797  			reinit = true
  1798  		}
  1799  	}
  1800  
  1801  	// Finally, process sumfile changes that may affect loading.
  1802  	for uri, newFH := range changedFiles {
  1803  		if !changedOnDisk(oldFiles[uri], newFH) {
  1804  			continue // like with go.mod files, we only reinit when things change on disk
  1805  		}
  1806  		dir, base := filepath.Split(uri.Path())
  1807  		if base == "go.work.sum" && s.view.typ == GoWorkView && dir == filepath.Dir(s.view.gowork.Path()) {
  1808  			reinit = true
  1809  		}
  1810  		if base == "go.sum" {
  1811  			modURI := protocol.URIFromPath(filepath.Join(dir, "go.mod"))
  1812  			if _, active := result.view.workspaceModFiles[modURI]; active {
  1813  				reinit = true
  1814  			}
  1815  		}
  1816  	}
  1817  
  1818  	// The snapshot should be initialized if either s was uninitialized, or we've
  1819  	// detected a change that triggers reinitialization.
  1820  	if reinit {
  1821  		result.initialized = false
  1822  		needsDiagnosis = true
  1823  	}
  1824  
  1825  	// directIDs keeps track of package IDs that have directly changed.
  1826  	// Note: this is not a set, it's a map from id to invalidateMetadata.
  1827  	directIDs := map[PackageID]bool{}
  1828  
  1829  	// Invalidate all package metadata if the workspace module has changed.
  1830  	if reinit {
  1831  		for k := range s.meta.Packages {
  1832  			// TODO(rfindley): this seems brittle; can we just start over?
  1833  			directIDs[k] = true
  1834  		}
  1835  	}
  1836  
  1837  	// Compute invalidations based on file changes.
  1838  	anyImportDeleted := false      // import deletions can resolve cycles
  1839  	anyFileOpenedOrClosed := false // opened files affect workspace packages
  1840  	anyFileAdded := false          // adding a file can resolve missing dependencies
  1841  
  1842  	for uri, newFH := range changedFiles {
  1843  		// The original FileHandle for this URI is cached on the snapshot.
  1844  		oldFH := oldFiles[uri] // may be nil
  1845  		_, oldOpen := oldFH.(*overlay)
  1846  		_, newOpen := newFH.(*overlay)
  1847  
  1848  		anyFileOpenedOrClosed = anyFileOpenedOrClosed || (oldOpen != newOpen)
  1849  		anyFileAdded = anyFileAdded || (oldFH == nil || !fileExists(oldFH)) && fileExists(newFH)
  1850  
  1851  		// If uri is a Go file, check if it has changed in a way that would
  1852  		// invalidate metadata. Note that we can't use s.view.FileKind here,
  1853  		// because the file type that matters is not what the *client* tells us,
  1854  		// but what the Go command sees.
  1855  		var invalidateMetadata, pkgFileChanged, importDeleted bool
  1856  		if strings.HasSuffix(uri.Path(), ".go") {
  1857  			invalidateMetadata, pkgFileChanged, importDeleted = metadataChanges(ctx, s, oldFH, newFH)
  1858  		}
  1859  		if invalidateMetadata {
  1860  			// If this is a metadata-affecting change, perhaps a reload will succeed.
  1861  			result.unloadableFiles.Remove(uri)
  1862  			needsDiagnosis = true
  1863  		}
  1864  
  1865  		invalidateMetadata = invalidateMetadata || reinit
  1866  		anyImportDeleted = anyImportDeleted || importDeleted
  1867  
  1868  		// Mark all of the package IDs containing the given file.
  1869  		filePackageIDs := invalidatedPackageIDs(uri, s.meta.IDs, pkgFileChanged)
  1870  		for id := range filePackageIDs {
  1871  			directIDs[id] = directIDs[id] || invalidateMetadata // may insert 'false'
  1872  		}
  1873  
  1874  		// Invalidate the previous modTidyHandle if any of the files have been
  1875  		// saved or if any of the metadata has been invalidated.
  1876  		//
  1877  		// TODO(rfindley): this seems like too-aggressive invalidation of mod
  1878  		// results. We should instead thread through overlays to the Go command
  1879  		// invocation and only run this if invalidateMetadata (and perhaps then
  1880  		// still do it less frequently).
  1881  		if invalidateMetadata || fileWasSaved(oldFH, newFH) {
  1882  			// Only invalidate mod tidy results for the most relevant modfile in the
  1883  			// workspace. This is a potentially lossy optimization for workspaces
  1884  			// with many modules (such as google-cloud-go, which has 145 modules as
  1885  			// of writing).
  1886  			//
  1887  			// While it is theoretically possible that a change in workspace module A
  1888  			// could affect the mod-tidiness of workspace module B (if B transitively
  1889  			// requires A), such changes are probably unlikely and not worth the
  1890  			// penalty of re-running go mod tidy for everything. Note that mod tidy
  1891  			// ignores GOWORK, so the two modules would have to be related by a chain
  1892  			// of replace directives.
  1893  			//
  1894  			// We could improve accuracy by inspecting replace directives, using
  1895  			// overlays in go mod tidy, and/or checking for metadata changes from the
  1896  			// on-disk content.
  1897  			//
  1898  			// Note that we iterate the modTidyHandles map here, rather than e.g.
  1899  			// using nearestModFile, because we don't have access to an accurate
  1900  			// FileSource at this point in the snapshot clone.
  1901  			const onlyInvalidateMostRelevant = true
  1902  			if onlyInvalidateMostRelevant {
  1903  				deleteMostRelevantModFile(result.modTidyHandles, uri)
  1904  			} else {
  1905  				result.modTidyHandles.Clear()
  1906  			}
  1907  
  1908  			// TODO(rfindley): should we apply the above heuristic to mod vuln or mod
  1909  			// why handles as well?
  1910  			//
  1911  			// TODO(rfindley): no tests fail if I delete the line below.
  1912  			result.modWhyHandles.Clear()
  1913  			result.modVulnHandles.Clear()
  1914  		}
  1915  	}
  1916  
  1917  	// Deleting an import can cause list errors due to import cycles to be
  1918  	// resolved. The best we can do without parsing the list error message is to
  1919  	// hope that list errors may have been resolved by a deleted import.
  1920  	//
  1921  	// We could do better by parsing the list error message. We already do this
  1922  	// to assign a better range to the list error, but for such critical
  1923  	// functionality as metadata, it's better to be conservative until it proves
  1924  	// impractical.
  1925  	//
  1926  	// We could also do better by looking at which imports were deleted and
  1927  	// trying to find cycles they are involved in. This fails when the file goes
  1928  	// from an unparseable state to a parseable state, as we don't have a
  1929  	// starting point to compare with.
  1930  	if anyImportDeleted {
  1931  		for id, mp := range s.meta.Packages {
  1932  			if len(mp.Errors) > 0 {
  1933  				directIDs[id] = true
  1934  			}
  1935  		}
  1936  	}
  1937  
  1938  	// Adding a file can resolve missing dependencies from existing packages.
  1939  	//
  1940  	// We could be smart here and try to guess which packages may have been
  1941  	// fixed, but until that proves necessary, just invalidate metadata for any
  1942  	// package with missing dependencies.
  1943  	if anyFileAdded {
  1944  		for id, mp := range s.meta.Packages {
  1945  			for _, impID := range mp.DepsByImpPath {
  1946  				if impID == "" { // missing import
  1947  					directIDs[id] = true
  1948  					break
  1949  				}
  1950  			}
  1951  		}
  1952  	}
  1953  
  1954  	// Invalidate reverse dependencies too.
  1955  	// idsToInvalidate keeps track of transitive reverse dependencies.
  1956  	// If an ID is present in the map, invalidate its types.
  1957  	// If an ID's value is true, invalidate its metadata too.
  1958  	idsToInvalidate := map[PackageID]bool{}
  1959  	var addRevDeps func(PackageID, bool)
  1960  	addRevDeps = func(id PackageID, invalidateMetadata bool) {
  1961  		current, seen := idsToInvalidate[id]
  1962  		newInvalidateMetadata := current || invalidateMetadata
  1963  
  1964  		// If we've already seen this ID, and the value of invalidate
  1965  		// metadata has not changed, we can return early.
  1966  		if seen && current == newInvalidateMetadata {
  1967  			return
  1968  		}
  1969  		idsToInvalidate[id] = newInvalidateMetadata
  1970  		for _, rid := range s.meta.ImportedBy[id] {
  1971  			addRevDeps(rid, invalidateMetadata)
  1972  		}
  1973  	}
  1974  	for id, invalidateMetadata := range directIDs {
  1975  		addRevDeps(id, invalidateMetadata)
  1976  	}
  1977  
  1978  	// Invalidated package information.
  1979  	for id, invalidateMetadata := range idsToInvalidate {
  1980  		if _, ok := directIDs[id]; ok || invalidateMetadata {
  1981  			if result.packages.Delete(id) {
  1982  				needsDiagnosis = true
  1983  			}
  1984  		} else {
  1985  			if entry, hit := result.packages.Get(id); hit {
  1986  				needsDiagnosis = true
  1987  				ph := entry.clone(false)
  1988  				result.packages.Set(id, ph, nil)
  1989  			}
  1990  		}
  1991  		if result.activePackages.Delete(id) {
  1992  			needsDiagnosis = true
  1993  		}
  1994  	}
  1995  
  1996  	// Compute which metadata updates are required. We only need to invalidate
  1997  	// packages directly containing the affected file, and only if it changed in
  1998  	// a relevant way.
  1999  	metadataUpdates := make(map[PackageID]*metadata.Package)
  2000  	for id, mp := range s.meta.Packages {
  2001  		invalidateMetadata := idsToInvalidate[id]
  2002  
  2003  		// For metadata that has been newly invalidated, capture package paths
  2004  		// requiring reloading in the shouldLoad map.
  2005  		if invalidateMetadata && !metadata.IsCommandLineArguments(mp.ID) {
  2006  			needsReload := []PackagePath{mp.PkgPath}
  2007  			if mp.ForTest != "" && mp.ForTest != mp.PkgPath {
  2008  				// When reloading test variants, always reload their ForTest package as
  2009  				// well. Otherwise, we may miss test variants in the resulting load.
  2010  				//
  2011  				// TODO(rfindley): is this actually sufficient? Is it possible that
  2012  				// other test variants may be invalidated? Either way, we should
  2013  				// determine exactly what needs to be reloaded here.
  2014  				needsReload = append(needsReload, mp.ForTest)
  2015  			}
  2016  			result.shouldLoad.Set(id, needsReload, nil)
  2017  		}
  2018  
  2019  		// Check whether the metadata should be deleted.
  2020  		if invalidateMetadata {
  2021  			needsDiagnosis = true
  2022  			metadataUpdates[id] = nil
  2023  			continue
  2024  		}
  2025  	}
  2026  
  2027  	// Update metadata, if necessary.
  2028  	result.meta = s.meta.Update(metadataUpdates)
  2029  
  2030  	// Update workspace and active packages, if necessary.
  2031  	if result.meta != s.meta || anyFileOpenedOrClosed {
  2032  		needsDiagnosis = true
  2033  		result.workspacePackages = computeWorkspacePackagesLocked(ctx, result, result.meta)
  2034  		result.resetActivePackagesLocked()
  2035  	} else {
  2036  		result.workspacePackages = s.workspacePackages
  2037  	}
  2038  
  2039  	return result, needsDiagnosis
  2040  }
  2041  
  2042  // cloneWithout clones m then deletes from it the keys of changes.
  2043  //
  2044  // The optional didDelete variable is set to true if there were deletions.
  2045  func cloneWithout[K constraints.Ordered, V1, V2 any](m *persistent.Map[K, V1], changes map[K]V2, didDelete *bool) *persistent.Map[K, V1] {
  2046  	m2 := m.Clone()
  2047  	for k := range changes {
  2048  		if m2.Delete(k) && didDelete != nil {
  2049  			*didDelete = true
  2050  		}
  2051  	}
  2052  	return m2
  2053  }
  2054  
  2055  // cloneWith clones m then inserts the changes into it.
  2056  func cloneWith[K constraints.Ordered, V any](m *persistent.Map[K, V], changes map[K]V) *persistent.Map[K, V] {
  2057  	m2 := m.Clone()
  2058  	for k, v := range changes {
  2059  		m2.Set(k, v, nil)
  2060  	}
  2061  	return m2
  2062  }
  2063  
  2064  // deleteMostRelevantModFile deletes the mod file most likely to be the mod
  2065  // file for the changed URI, if it exists.
  2066  //
  2067  // Specifically, this is the longest mod file path in a directory containing
  2068  // changed. This might not be accurate if there is another mod file closer to
  2069  // changed that happens not to be present in the map, but that's OK: the goal
  2070  // of this function is to guarantee that IF the nearest mod file is present in
  2071  // the map, it is invalidated.
  2072  func deleteMostRelevantModFile(m *persistent.Map[protocol.DocumentURI, *memoize.Promise], changed protocol.DocumentURI) {
  2073  	var mostRelevant protocol.DocumentURI
  2074  	changedFile := changed.Path()
  2075  
  2076  	m.Range(func(modURI protocol.DocumentURI, _ *memoize.Promise) {
  2077  		if len(modURI) > len(mostRelevant) {
  2078  			if pathutil.InDir(filepath.Dir(modURI.Path()), changedFile) {
  2079  				mostRelevant = modURI
  2080  			}
  2081  		}
  2082  	})
  2083  	if mostRelevant != "" {
  2084  		m.Delete(mostRelevant)
  2085  	}
  2086  }
  2087  
  2088  // invalidatedPackageIDs returns all packages invalidated by a change to uri.
  2089  // If we haven't seen this URI before, we guess based on files in the same
  2090  // directory. This is of course incorrect in build systems where packages are
  2091  // not organized by directory.
  2092  //
  2093  // If packageFileChanged is set, the file is either a new file, or has a new
  2094  // package name. In this case, all known packages in the directory will be
  2095  // invalidated.
  2096  func invalidatedPackageIDs(uri protocol.DocumentURI, known map[protocol.DocumentURI][]PackageID, packageFileChanged bool) map[PackageID]struct{} {
  2097  	invalidated := make(map[PackageID]struct{})
  2098  
  2099  	// At a minimum, we invalidate packages known to contain uri.
  2100  	for _, id := range known[uri] {
  2101  		invalidated[id] = struct{}{}
  2102  	}
  2103  
  2104  	// If the file didn't move to a new package, we should only invalidate the
  2105  	// packages it is currently contained inside.
  2106  	if !packageFileChanged && len(invalidated) > 0 {
  2107  		return invalidated
  2108  	}
  2109  
  2110  	// This is a file we don't yet know about, or which has moved packages. Guess
  2111  	// relevant packages by considering files in the same directory.
  2112  
  2113  	// Cache of FileInfo to avoid unnecessary stats for multiple files in the
  2114  	// same directory.
  2115  	stats := make(map[string]struct {
  2116  		os.FileInfo
  2117  		error
  2118  	})
  2119  	getInfo := func(dir string) (os.FileInfo, error) {
  2120  		if res, ok := stats[dir]; ok {
  2121  			return res.FileInfo, res.error
  2122  		}
  2123  		fi, err := os.Stat(dir)
  2124  		stats[dir] = struct {
  2125  			os.FileInfo
  2126  			error
  2127  		}{fi, err}
  2128  		return fi, err
  2129  	}
  2130  	dir := filepath.Dir(uri.Path())
  2131  	fi, err := getInfo(dir)
  2132  	if err == nil {
  2133  		// Aggregate all possibly relevant package IDs.
  2134  		for knownURI, ids := range known {
  2135  			knownDir := filepath.Dir(knownURI.Path())
  2136  			knownFI, err := getInfo(knownDir)
  2137  			if err != nil {
  2138  				continue
  2139  			}
  2140  			if os.SameFile(fi, knownFI) {
  2141  				for _, id := range ids {
  2142  					invalidated[id] = struct{}{}
  2143  				}
  2144  			}
  2145  		}
  2146  	}
  2147  	return invalidated
  2148  }
  2149  
  2150  // fileWasSaved reports whether the FileHandle passed in has been saved. It
  2151  // accomplishes this by checking to see if the original and current FileHandles
  2152  // are both overlays, and if the current FileHandle is saved while the original
  2153  // FileHandle was not saved.
  2154  func fileWasSaved(originalFH, currentFH file.Handle) bool {
  2155  	c, ok := currentFH.(*overlay)
  2156  	if !ok || c == nil {
  2157  		return true
  2158  	}
  2159  	o, ok := originalFH.(*overlay)
  2160  	if !ok || o == nil {
  2161  		return c.saved
  2162  	}
  2163  	return !o.saved && c.saved
  2164  }
  2165  
  2166  // metadataChanges detects features of the change from oldFH->newFH that may
  2167  // affect package metadata.
  2168  //
  2169  // It uses lockedSnapshot to access cached parse information. lockedSnapshot
  2170  // must be locked.
  2171  //
  2172  // The result parameters have the following meaning:
  2173  //   - invalidate means that package metadata for packages containing the file
  2174  //     should be invalidated.
  2175  //   - pkgFileChanged means that the file->package associates for the file have
  2176  //     changed (possibly because the file is new, or because its package name has
  2177  //     changed).
  2178  //   - importDeleted means that an import has been deleted, or we can't
  2179  //     determine if an import was deleted due to errors.
  2180  func metadataChanges(ctx context.Context, lockedSnapshot *Snapshot, oldFH, newFH file.Handle) (invalidate, pkgFileChanged, importDeleted bool) {
  2181  	if oe, ne := oldFH != nil && fileExists(oldFH), fileExists(newFH); !oe || !ne { // existential changes
  2182  		changed := oe != ne
  2183  		return changed, changed, !ne // we don't know if an import was deleted
  2184  	}
  2185  
  2186  	// If the file hasn't changed, there's no need to reload.
  2187  	if oldFH.Identity() == newFH.Identity() {
  2188  		return false, false, false
  2189  	}
  2190  
  2191  	fset := token.NewFileSet()
  2192  	// Parse headers to compare package names and imports.
  2193  	oldHeads, oldErr := lockedSnapshot.view.parseCache.parseFiles(ctx, fset, ParseHeader, false, oldFH)
  2194  	newHeads, newErr := lockedSnapshot.view.parseCache.parseFiles(ctx, fset, ParseHeader, false, newFH)
  2195  
  2196  	if oldErr != nil || newErr != nil {
  2197  		errChanged := (oldErr == nil) != (newErr == nil)
  2198  		return errChanged, errChanged, (newErr != nil) // we don't know if an import was deleted
  2199  	}
  2200  
  2201  	oldHead := oldHeads[0]
  2202  	newHead := newHeads[0]
  2203  
  2204  	// `go list` fails completely if the file header cannot be parsed. If we go
  2205  	// from a non-parsing state to a parsing state, we should reload.
  2206  	if oldHead.ParseErr != nil && newHead.ParseErr == nil {
  2207  		return true, true, true // We don't know what changed, so fall back on full invalidation.
  2208  	}
  2209  
  2210  	// If a package name has changed, the set of package imports may have changed
  2211  	// in ways we can't detect here. Assume an import has been deleted.
  2212  	if oldHead.File.Name.Name != newHead.File.Name.Name {
  2213  		return true, true, true
  2214  	}
  2215  
  2216  	// Check whether package imports have changed. Only consider potentially
  2217  	// valid imports paths.
  2218  	oldImports := validImports(oldHead.File.Imports)
  2219  	newImports := validImports(newHead.File.Imports)
  2220  
  2221  	for path := range newImports {
  2222  		if _, ok := oldImports[path]; ok {
  2223  			delete(oldImports, path)
  2224  		} else {
  2225  			invalidate = true // a new, potentially valid import was added
  2226  		}
  2227  	}
  2228  
  2229  	if len(oldImports) > 0 {
  2230  		invalidate = true
  2231  		importDeleted = true
  2232  	}
  2233  
  2234  	// If the change does not otherwise invalidate metadata, get the full ASTs in
  2235  	// order to check magic comments.
  2236  	//
  2237  	// Note: if this affects performance we can probably avoid parsing in the
  2238  	// common case by first scanning the source for potential comments.
  2239  	if !invalidate {
  2240  		origFulls, oldErr := lockedSnapshot.view.parseCache.parseFiles(ctx, fset, ParseFull, false, oldFH)
  2241  		newFulls, newErr := lockedSnapshot.view.parseCache.parseFiles(ctx, fset, ParseFull, false, newFH)
  2242  		if oldErr == nil && newErr == nil {
  2243  			invalidate = magicCommentsChanged(origFulls[0].File, newFulls[0].File)
  2244  		} else {
  2245  			// At this point, we shouldn't ever fail to produce a ParsedGoFile, as
  2246  			// we're already past header parsing.
  2247  			bug.Reportf("metadataChanges: unparseable file %v (old error: %v, new error: %v)", oldFH.URI(), oldErr, newErr)
  2248  		}
  2249  	}
  2250  
  2251  	return invalidate, pkgFileChanged, importDeleted
  2252  }
  2253  
  2254  func magicCommentsChanged(original *ast.File, current *ast.File) bool {
  2255  	oldComments := extractMagicComments(original)
  2256  	newComments := extractMagicComments(current)
  2257  	if len(oldComments) != len(newComments) {
  2258  		return true
  2259  	}
  2260  	for i := range oldComments {
  2261  		if oldComments[i] != newComments[i] {
  2262  			return true
  2263  		}
  2264  	}
  2265  	return false
  2266  }
  2267  
  2268  // validImports extracts the set of valid import paths from imports.
  2269  func validImports(imports []*ast.ImportSpec) map[string]struct{} {
  2270  	m := make(map[string]struct{})
  2271  	for _, spec := range imports {
  2272  		if path := spec.Path.Value; validImportPath(path) {
  2273  			m[path] = struct{}{}
  2274  		}
  2275  	}
  2276  	return m
  2277  }
  2278  
  2279  func validImportPath(path string) bool {
  2280  	path, err := strconv.Unquote(path)
  2281  	if err != nil {
  2282  		return false
  2283  	}
  2284  	if path == "" {
  2285  		return false
  2286  	}
  2287  	if path[len(path)-1] == '/' {
  2288  		return false
  2289  	}
  2290  	return true
  2291  }
  2292  
  2293  var buildConstraintOrEmbedRe = regexp.MustCompile(`^//(go:embed|go:build|\s*\+build).*`)
  2294  
  2295  // extractMagicComments finds magic comments that affect metadata in f.
  2296  func extractMagicComments(f *ast.File) []string {
  2297  	var results []string
  2298  	for _, cg := range f.Comments {
  2299  		for _, c := range cg.List {
  2300  			if buildConstraintOrEmbedRe.MatchString(c.Text) {
  2301  				results = append(results, c.Text)
  2302  			}
  2303  		}
  2304  	}
  2305  	return results
  2306  }
  2307  
  2308  // BuiltinFile returns information about the special builtin package.
  2309  func (s *Snapshot) BuiltinFile(ctx context.Context) (*ParsedGoFile, error) {
  2310  	s.AwaitInitialized(ctx)
  2311  
  2312  	s.mu.Lock()
  2313  	builtin := s.builtin
  2314  	s.mu.Unlock()
  2315  
  2316  	if builtin == "" {
  2317  		return nil, fmt.Errorf("no builtin package for view %s", s.view.folder.Name)
  2318  	}
  2319  
  2320  	fh, err := s.ReadFile(ctx, builtin)
  2321  	if err != nil {
  2322  		return nil, err
  2323  	}
  2324  	// For the builtin file only, we need syntactic object resolution
  2325  	// (since we can't type check).
  2326  	mode := ParseFull &^ parser.SkipObjectResolution
  2327  	pgfs, err := s.view.parseCache.parseFiles(ctx, token.NewFileSet(), mode, false, fh)
  2328  	if err != nil {
  2329  		return nil, err
  2330  	}
  2331  	return pgfs[0], nil
  2332  }
  2333  
  2334  // IsBuiltin reports whether uri is part of the builtin package.
  2335  func (s *Snapshot) IsBuiltin(uri protocol.DocumentURI) bool {
  2336  	s.mu.Lock()
  2337  	defer s.mu.Unlock()
  2338  	// We should always get the builtin URI in a canonical form, so use simple
  2339  	// string comparison here. span.CompareURI is too expensive.
  2340  	return uri == s.builtin
  2341  }
  2342  
  2343  func (s *Snapshot) setBuiltin(path string) {
  2344  	s.mu.Lock()
  2345  	defer s.mu.Unlock()
  2346  
  2347  	s.builtin = protocol.URIFromPath(path)
  2348  }
  2349  
  2350  // WantGCDetails reports whether to compute GC optimization details for the
  2351  // specified package.
  2352  func (s *Snapshot) WantGCDetails(id metadata.PackageID) bool {
  2353  	_, ok := s.gcOptimizationDetails[id]
  2354  	return ok
  2355  }