cuelang.org/go@v0.13.0/internal/golangorgx/gopls/cache/session.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  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"path/filepath"
    12  	"strconv"
    13  	"strings"
    14  	"sync"
    15  	"sync/atomic"
    16  	"time"
    17  
    18  	"cuelang.org/go/cue/build"
    19  	"cuelang.org/go/internal/golangorgx/gopls/cache/metadata"
    20  	"cuelang.org/go/internal/golangorgx/gopls/cache/typerefs"
    21  	"cuelang.org/go/internal/golangorgx/gopls/file"
    22  	"cuelang.org/go/internal/golangorgx/gopls/protocol"
    23  	"cuelang.org/go/internal/golangorgx/gopls/util/bug"
    24  	"cuelang.org/go/internal/golangorgx/gopls/util/persistent"
    25  	"cuelang.org/go/internal/golangorgx/tools/event"
    26  	"cuelang.org/go/internal/golangorgx/tools/memoize"
    27  	"cuelang.org/go/internal/golangorgx/tools/xcontext"
    28  )
    29  
    30  // NewSession creates a new gopls session with the given cache.
    31  func NewSession(ctx context.Context, c *Cache) *Session {
    32  	index := atomic.AddInt64(&sessionIndex, 1)
    33  	s := &Session{
    34  		id:         strconv.FormatInt(index, 10),
    35  		cache:      c,
    36  		overlayFS:  newOverlayFS(c),
    37  		parseCache: newParseCache(1 * time.Minute), // keep recently parsed files for a minute, to optimize typing CPU
    38  		viewMap:    make(map[protocol.DocumentURI]*View),
    39  	}
    40  	event.Log(ctx, "New session", KeyCreateSession.Of(s))
    41  	return s
    42  }
    43  
    44  // A Session holds the state (views, file contents, parse cache,
    45  // memoized computations) of a gopls server process.
    46  //
    47  // It implements the file.Source interface.
    48  type Session struct {
    49  	// Unique identifier for this session.
    50  	id string
    51  
    52  	// Immutable attributes shared across views.
    53  	cache *Cache // shared cache
    54  
    55  	viewMu  sync.Mutex
    56  	views   []*View
    57  	viewMap map[protocol.DocumentURI]*View // file->best view; nil after shutdown
    58  
    59  	// snapshots is a counting semaphore that records the number
    60  	// of unreleased snapshots associated with this session.
    61  	// Shutdown waits for it to fall to zero.
    62  	snapshotWG sync.WaitGroup
    63  
    64  	parseCache *parseCache
    65  
    66  	*overlayFS
    67  }
    68  
    69  // ID returns the unique identifier for this session on this server.
    70  func (s *Session) ID() string     { return s.id }
    71  func (s *Session) String() string { return s.id }
    72  
    73  // Shutdown the session and all views it has created.
    74  func (s *Session) Shutdown(ctx context.Context) {
    75  	var views []*View
    76  	s.viewMu.Lock()
    77  	views = append(views, s.views...)
    78  	s.views = nil
    79  	s.viewMap = nil
    80  	s.viewMu.Unlock()
    81  	for _, view := range views {
    82  		view.shutdown()
    83  	}
    84  	s.parseCache.stop()
    85  	s.snapshotWG.Wait() // wait for all work on associated snapshots to finish
    86  	event.Log(ctx, "Shutdown session", KeyShutdownSession.Of(s))
    87  }
    88  
    89  // Cache returns the cache that created this session, for debugging only.
    90  func (s *Session) Cache() *Cache {
    91  	return s.cache
    92  }
    93  
    94  // NewView creates a new View, returning it and its first snapshot. If a
    95  // non-empty tempWorkspace directory is provided, the View will record a copy
    96  // of its gopls workspace module in that directory, so that client tooling
    97  // can execute in the same main module.  On success it also returns a release
    98  // function that must be called when the Snapshot is no longer needed.
    99  func (s *Session) NewView(ctx context.Context, folder *Folder) (*View, *Snapshot, func(), error) {
   100  	s.viewMu.Lock()
   101  	defer s.viewMu.Unlock()
   102  
   103  	// TODO(myitcv): when we shift to support multiple WorkspaceFolders, we
   104  	// might need to introduce logic here that determines if we have an existing
   105  	// view for a WorkspaceFolder we are adding.
   106  
   107  	def, err := defineView(ctx, s, folder, nil)
   108  	if err != nil {
   109  		return nil, nil, nil, err
   110  	}
   111  	view, snapshot, release := s.createView(ctx, def)
   112  	s.views = append(s.views, view)
   113  	// we always need to drop the view map
   114  	s.viewMap = make(map[protocol.DocumentURI]*View)
   115  	return view, snapshot, release, nil
   116  }
   117  
   118  // createView creates a new view, with an initial snapshot that retains the
   119  // supplied context, detached from events and cancelation.
   120  //
   121  // The caller is responsible for calling the release function once.
   122  func (s *Session) createView(ctx context.Context, def *viewDefinition) (*View, *Snapshot, func()) {
   123  	index := atomic.AddInt64(&viewIndex, 1)
   124  
   125  	// We want a true background context and not a detached context here
   126  	// the spans need to be unrelated and no tag values should pollute it.
   127  	baseCtx := event.Detach(xcontext.Detach(ctx))
   128  	backgroundCtx, cancel := context.WithCancel(baseCtx)
   129  
   130  	v := &View{
   131  		id:                   strconv.FormatInt(index, 10),
   132  		initialWorkspaceLoad: make(chan struct{}),
   133  		initializationSema:   make(chan struct{}, 1),
   134  		baseCtx:              baseCtx,
   135  		parseCache:           s.parseCache,
   136  		fs:                   s.overlayFS,
   137  		viewDefinition:       def,
   138  	}
   139  
   140  	s.snapshotWG.Add(1)
   141  	v.snapshot = &Snapshot{
   142  		view:             v,
   143  		backgroundCtx:    backgroundCtx,
   144  		cancel:           cancel,
   145  		store:            s.cache.store,
   146  		refcount:         1, // Snapshots are born referenced.
   147  		done:             s.snapshotWG.Done,
   148  		packages:         new(persistent.Map[ImportPath, *packageHandle]),
   149  		meta:             new(metadata.Graph),
   150  		files:            newFileMap(),
   151  		activePackages:   new(persistent.Map[ImportPath, *build.Instance]),
   152  		symbolizeHandles: new(persistent.Map[protocol.DocumentURI, *memoize.Promise]),
   153  		shouldLoad:       new(persistent.Map[ImportPath, []PackagePath]),
   154  		unloadableFiles:  new(persistent.Set[protocol.DocumentURI]),
   155  		pkgIndex:         typerefs.NewPackageIndex(),
   156  	}
   157  
   158  	// Snapshots must observe all open files, as there are some caching
   159  	// heuristics that change behavior depending on open files.
   160  	for _, o := range s.overlayFS.Overlays() {
   161  		_, _ = v.snapshot.ReadFile(ctx, o.URI())
   162  	}
   163  
   164  	// Record the environment of the newly created view in the log.
   165  	event.Log(ctx, viewEnv(v))
   166  
   167  	// Initialize the view without blocking.
   168  	initCtx, initCancel := context.WithCancel(xcontext.Detach(ctx))
   169  	v.cancelInitialWorkspaceLoad = initCancel
   170  	snapshot := v.snapshot
   171  
   172  	// Pass a second reference to the background goroutine.
   173  	bgRelease := snapshot.Acquire()
   174  	go func() {
   175  		defer bgRelease()
   176  		snapshot.initialize(initCtx, true)
   177  	}()
   178  
   179  	// Return a third reference to the caller.
   180  	return v, snapshot, snapshot.Acquire()
   181  }
   182  
   183  // RemoveView removes from the session the view rooted at the specified directory.
   184  // It reports whether a view of that directory was removed.
   185  func (s *Session) RemoveView(dir protocol.DocumentURI) bool {
   186  	s.viewMu.Lock()
   187  	defer s.viewMu.Unlock()
   188  	for _, view := range s.views {
   189  		if view.folder.Dir == dir {
   190  			i := s.dropView(view)
   191  			if i == -1 {
   192  				return false // can't happen
   193  			}
   194  			// delete this view... we don't care about order but we do want to make
   195  			// sure we can garbage collect the view
   196  			s.views = removeElement(s.views, i)
   197  			return true
   198  		}
   199  	}
   200  	return false
   201  }
   202  
   203  // View returns the view with a matching id, if present.
   204  func (s *Session) View(id string) (*View, error) {
   205  	s.viewMu.Lock()
   206  	defer s.viewMu.Unlock()
   207  	for _, view := range s.views {
   208  		if view.ID() == id {
   209  			return view, nil
   210  		}
   211  	}
   212  	return nil, fmt.Errorf("no view with ID %q", id)
   213  }
   214  
   215  // SnapshotOf returns a Snapshot corresponding to the given URI.
   216  //
   217  // In the case where the file can be  can be associated with a View by
   218  // bestViewForURI (based on directory information alone, without package
   219  // metadata), SnapshotOf returns the current Snapshot for that View. Otherwise,
   220  // it awaits loading package metadata and returns a Snapshot for the first View
   221  // containing a real (=not command-line-arguments) package for the file.
   222  //
   223  // If that also fails to find a View, SnapshotOf returns a Snapshot for the
   224  // first view in s.views that is not shut down (i.e. s.views[0] unless we lose
   225  // a race), for determinism in tests and so that we tend to aggregate the
   226  // resulting command-line-arguments packages into a single view.
   227  //
   228  // SnapshotOf returns an error if a failure occurs along the way (most likely due
   229  // to context cancellation), or if there are no Views in the Session.
   230  //
   231  // On success, the caller must call the returned function to release the snapshot.
   232  func (s *Session) SnapshotOf(ctx context.Context, uri protocol.DocumentURI) (*Snapshot, func(), error) {
   233  	// Fast path: if the uri has a static association with a view, return it.
   234  	s.viewMu.Lock()
   235  	v, err := s.viewOfLocked(ctx, uri)
   236  	s.viewMu.Unlock()
   237  
   238  	if err != nil {
   239  		return nil, nil, err
   240  	}
   241  
   242  	if v != nil {
   243  		snapshot, release, err := v.Snapshot()
   244  		if err == nil {
   245  			return snapshot, release, nil
   246  		}
   247  		// View is shut down. Forget this association.
   248  		s.viewMu.Lock()
   249  		if s.viewMap[uri] == v {
   250  			delete(s.viewMap, uri)
   251  		}
   252  		s.viewMu.Unlock()
   253  	}
   254  
   255  	// Fall-back: none of the views could be associated with uri based on
   256  	// directory information alone.
   257  	//
   258  	// Don't memoize the view association in viewMap, as it is not static: Views
   259  	// may change as metadata changes.
   260  	//
   261  	// TODO(rfindley): we could perhaps optimize this case by peeking at existing
   262  	// metadata before awaiting the load (after all, a load only adds metadata).
   263  	// But that seems potentially tricky, when in the common case no loading
   264  	// should be required.
   265  	views := s.Views()
   266  
   267  	for _, v := range views {
   268  		snapshot, release, err := v.Snapshot()
   269  		if err != nil {
   270  			continue
   271  		}
   272  		_ = snapshot.awaitLoaded(ctx) // ignore error
   273  		return snapshot, release, nil // first valid snapshot
   274  	}
   275  	return nil, nil, errNoViews
   276  }
   277  
   278  // errNoViews is sought by orphaned file diagnostics, to detect the case where
   279  // we have no view containing a file.
   280  var errNoViews = errors.New("no views")
   281  
   282  // viewOfLocked wraps bestViewForURI, memoizing its result.
   283  //
   284  // Precondition: caller holds s.viewMu lock.
   285  //
   286  // May return (nil, nil).
   287  func (s *Session) viewOfLocked(ctx context.Context, uri protocol.DocumentURI) (*View, error) {
   288  	v, hit := s.viewMap[uri]
   289  	if !hit {
   290  		// Cache miss: compute (and memoize) the best view.
   291  		fh, err := s.ReadFile(ctx, uri)
   292  		if err != nil {
   293  			return nil, err
   294  		}
   295  		v, err = bestView(ctx, s, fh, s.views)
   296  		if err != nil {
   297  			return nil, err
   298  		}
   299  		if s.viewMap == nil {
   300  			return nil, errors.New("session is shut down")
   301  		}
   302  		s.viewMap[uri] = v
   303  	}
   304  	return v, nil
   305  }
   306  
   307  func (s *Session) Views() []*View {
   308  	s.viewMu.Lock()
   309  	defer s.viewMu.Unlock()
   310  	result := make([]*View, len(s.views))
   311  	copy(result, s.views)
   312  	return result
   313  }
   314  
   315  // selectViewDefs constructs the best set of views covering the provided workspace
   316  // folders and open files.
   317  //
   318  // This implements the zero-config algorithm of golang/go#57979.
   319  func selectViewDefs(ctx context.Context, fs file.Source, folders []*Folder, openFiles []protocol.DocumentURI) ([]*viewDefinition, error) {
   320  	var defs []*viewDefinition
   321  
   322  	// First, compute a default view for each workspace folder.
   323  	// TODO(golang/go#57979): technically, this is path dependent, since
   324  	// DidChangeWorkspaceFolders could introduce a path-dependent ordering on
   325  	// folders. We should keep folders sorted, or sort them here.
   326  	for _, folder := range folders {
   327  		def, err := defineView(ctx, fs, folder, nil)
   328  		if err != nil {
   329  			return nil, err
   330  		}
   331  		defs = append(defs, def)
   332  	}
   333  
   334  	// Next, ensure that the set of views covers all open files contained in a
   335  	// workspace folder.
   336  	//
   337  	// We only do this for files contained in a workspace folder, because other
   338  	// open files are most likely the result of jumping to a definition from a
   339  	// workspace file; we don't want to create additional views in those cases:
   340  	// they should be resolved after initialization.
   341  
   342  	folderForFile := func(uri protocol.DocumentURI) *Folder {
   343  		var longest *Folder
   344  		for _, folder := range folders {
   345  			if (longest == nil || len(folder.Dir) > len(longest.Dir)) && folder.Dir.Encloses(uri) {
   346  				longest = folder
   347  			}
   348  		}
   349  		return longest
   350  	}
   351  
   352  checkFiles:
   353  	for _, uri := range openFiles {
   354  		folder := folderForFile(uri)
   355  		if folder == nil || !folder.Options.ZeroConfig {
   356  			continue // only guess views for open files
   357  		}
   358  		fh, err := fs.ReadFile(ctx, uri)
   359  		if err != nil {
   360  			return nil, err
   361  		}
   362  		def, err := bestView(ctx, fs, fh, defs)
   363  		if err != nil {
   364  			// We should never call selectViewDefs with a cancellable context, so
   365  			// this should never fail.
   366  			return nil, bug.Errorf("failed to find best view for open file: %v", err)
   367  		}
   368  		if def != nil {
   369  			continue // file covered by an existing view
   370  		}
   371  		def, err = defineView(ctx, fs, folder, fh)
   372  		if err != nil {
   373  			// We should never call selectViewDefs with a cancellable context, so
   374  			// this should never fail.
   375  			return nil, bug.Errorf("failed to define view for open file: %v", err)
   376  		}
   377  		// It need not strictly be the case that the best view for a file is
   378  		// distinct from other views, as the logic of getViewDefinition and
   379  		// bestViewForURI does not align perfectly. This is not necessarily a bug:
   380  		// there may be files for which we can't construct a valid view.
   381  		//
   382  		// Nevertheless, we should not create redundant views.
   383  		for _, alt := range defs {
   384  			if viewDefinitionsEqual(alt, def) {
   385  				continue checkFiles
   386  			}
   387  		}
   388  		defs = append(defs, def)
   389  	}
   390  
   391  	return defs, nil
   392  }
   393  
   394  // The viewDefiner interface allows the bestView algorithm to operate on both
   395  // Views and viewDefinitions.
   396  type viewDefiner interface{ definition() *viewDefinition }
   397  
   398  // bestView returns the best View or viewDefinition that contains the
   399  // given file, or (nil, nil) if no matching view is found.
   400  //
   401  // bestView only returns an error in the event of context cancellation.
   402  //
   403  // Making this function generic is convenient so that we can avoid mapping view
   404  // definitions back to views inside Session.DidModifyFiles, where performance
   405  // matters. It is, however, not the cleanest application of generics.
   406  //
   407  // Note: keep this function in sync with defineView.
   408  func bestView[V viewDefiner](ctx context.Context, fs file.Source, fh file.Handle, views []V) (V, error) {
   409  	var zero V
   410  
   411  	// Given current limitations of cue lsp (exactly one workspace folder
   412  	// supported), and that we create a single view per workspace folder, we
   413  	// should assert here that we have a single view. Otherwise we have problems
   414  	if len(views) != 1 {
   415  		return zero, fmt.Errorf("expected exactly 1 view; saw %d", len(views))
   416  	}
   417  
   418  	v := views[0]
   419  
   420  	dirURI := fh.URI().Dir()
   421  	moduleCue, err := findRootPattern(ctx, dirURI, filepath.FromSlash("cue.mod/module.cue"), fs)
   422  	if err != nil {
   423  		return zero, err
   424  	}
   425  
   426  	// Only if the module root corresponds to that of the view (workspace folder)
   427  	// do we match.
   428  	root := moduleCue.Dir().Dir()
   429  	if root == v.definition().root {
   430  		return v, nil
   431  	}
   432  
   433  	// TODO(myitcv): in the case of a nested module, we could prompt the user to
   434  	// do something here when we add support for multiple workspace folders. For
   435  	// now we simply return the zero view.
   436  	if v.definition().folder.Dir.Encloses(fh.URI()) {
   437  		return zero, nil
   438  	}
   439  
   440  	// Perhaps a random file opened by the user?
   441  	return zero, nil
   442  }
   443  
   444  // updateViewLocked recreates the view with the given options.
   445  //
   446  // If the resulting error is non-nil, the view may or may not have already been
   447  // dropped from the session.
   448  func (s *Session) updateViewLocked(ctx context.Context, view *View, def *viewDefinition) (*View, error) {
   449  	i := s.dropView(view)
   450  	if i == -1 {
   451  		return nil, fmt.Errorf("view %q not found", view.id)
   452  	}
   453  
   454  	view, _, release := s.createView(ctx, def)
   455  	defer release()
   456  
   457  	// substitute the new view into the array where the old view was
   458  	s.views[i] = view
   459  	s.viewMap = make(map[protocol.DocumentURI]*View)
   460  	return view, nil
   461  }
   462  
   463  // removeElement removes the ith element from the slice replacing it with the last element.
   464  // TODO(adonovan): generics, someday.
   465  func removeElement(slice []*View, index int) []*View {
   466  	last := len(slice) - 1
   467  	slice[index] = slice[last]
   468  	slice[last] = nil // aid GC
   469  	return slice[:last]
   470  }
   471  
   472  // dropView removes v from the set of views for the receiver s and calls
   473  // v.shutdown, returning the index of v in s.views (if found), or -1 if v was
   474  // not found. s.viewMu must be held while calling this function.
   475  func (s *Session) dropView(v *View) int {
   476  	// we always need to drop the view map
   477  	s.viewMap = make(map[protocol.DocumentURI]*View)
   478  	for i := range s.views {
   479  		if v == s.views[i] {
   480  			// we found the view, drop it and return the index it was found at
   481  			s.views[i] = nil
   482  			v.shutdown()
   483  			return i
   484  		}
   485  	}
   486  	// TODO(rfindley): it looks wrong that we don't shutdown v in this codepath.
   487  	// We should never get here.
   488  	bug.Reportf("tried to drop nonexistent view %q", v.id)
   489  	return -1
   490  }
   491  
   492  // ResetView resets the best view for the given URI.
   493  func (s *Session) ResetView(ctx context.Context, uri protocol.DocumentURI) (*View, error) {
   494  	s.viewMu.Lock()
   495  	defer s.viewMu.Unlock()
   496  	v, err := s.viewOfLocked(ctx, uri)
   497  	if err != nil {
   498  		return nil, err
   499  	}
   500  	return s.updateViewLocked(ctx, v, v.viewDefinition)
   501  }
   502  
   503  // DidModifyFiles reports a file modification to the session. It returns
   504  // the new snapshots after the modifications have been applied, paired with
   505  // the affected file URIs for those snapshots.
   506  // On success, it returns a release function that
   507  // must be called when the snapshots are no longer needed.
   508  //
   509  // TODO(rfindley): what happens if this function fails? It must leave us in a
   510  // broken state, which we should surface to the user, probably as a request to
   511  // restart gopls.
   512  func (s *Session) DidModifyFiles(ctx context.Context, modifications []file.Modification) (map[*View][]protocol.DocumentURI, error) {
   513  	s.viewMu.Lock()
   514  	defer s.viewMu.Unlock()
   515  
   516  	// Update overlays.
   517  	//
   518  	// This is done while holding viewMu because the set of open files affects
   519  	// the set of views, and to prevent views from seeing updated file content
   520  	// before they have processed invalidations.
   521  	_, err := s.updateOverlays(ctx, modifications)
   522  	if err != nil {
   523  		return nil, err
   524  	}
   525  
   526  	// checkViews controls whether the set of views needs to be recomputed, for
   527  	// example because a go.mod file was created or deleted, or a go.work file
   528  	// changed on disk.
   529  	checkViews := false
   530  
   531  	changed := make(map[protocol.DocumentURI]file.Handle)
   532  	for _, c := range modifications {
   533  		fh := mustReadFile(ctx, s, c.URI)
   534  		changed[c.URI] = fh
   535  	}
   536  
   537  	// We only want to run fast-path diagnostics (i.e. diagnoseChangedFiles) once
   538  	// for each changed file, in its best view.
   539  	viewsToDiagnose := map[*View][]protocol.DocumentURI{}
   540  	for _, mod := range modifications {
   541  		v, err := s.viewOfLocked(ctx, mod.URI)
   542  		if err != nil {
   543  			// bestViewForURI only returns an error in the event of context
   544  			// cancellation. Since state changes should occur on an uncancellable
   545  			// context, an error here is a bug.
   546  			bug.Reportf("finding best view for change: %v", err)
   547  			continue
   548  		}
   549  		if v != nil {
   550  			viewsToDiagnose[v] = append(viewsToDiagnose[v], mod.URI)
   551  		}
   552  	}
   553  
   554  	// ...but changes may be relevant to other views, for example if they are
   555  	// changes to a shared package.
   556  	for _, v := range s.views {
   557  		_, release, needsDiagnosis := s.invalidateViewLocked(ctx, v, StateChange{Modifications: modifications, Files: changed})
   558  		release()
   559  
   560  		if needsDiagnosis || checkViews {
   561  			if _, ok := viewsToDiagnose[v]; !ok {
   562  				viewsToDiagnose[v] = nil
   563  			}
   564  		}
   565  	}
   566  
   567  	return viewsToDiagnose, nil
   568  }
   569  
   570  // ExpandModificationsToDirectories returns the set of changes with the
   571  // directory changes removed and expanded to include all of the files in
   572  // the directory.
   573  func (s *Session) ExpandModificationsToDirectories(ctx context.Context, changes []file.Modification) []file.Modification {
   574  	var snapshots []*Snapshot
   575  	s.viewMu.Lock()
   576  	for _, v := range s.views {
   577  		snapshot, release, err := v.Snapshot()
   578  		if err != nil {
   579  			continue // view is shut down; continue with others
   580  		}
   581  		defer release()
   582  		snapshots = append(snapshots, snapshot)
   583  	}
   584  	s.viewMu.Unlock()
   585  
   586  	// Expand the modification to any file we could care about, which we define
   587  	// to be any file observed by any of the snapshots.
   588  	//
   589  	// There may be other files in the directory, but if we haven't read them yet
   590  	// we don't need to invalidate them.
   591  	var result []file.Modification
   592  	for _, c := range changes {
   593  		expanded := make(map[protocol.DocumentURI]bool)
   594  		for _, snapshot := range snapshots {
   595  			for _, uri := range snapshot.filesInDir(c.URI) {
   596  				expanded[uri] = true
   597  			}
   598  		}
   599  		if len(expanded) == 0 {
   600  			result = append(result, c)
   601  		} else {
   602  			for uri := range expanded {
   603  				result = append(result, file.Modification{
   604  					URI:        uri,
   605  					Action:     c.Action,
   606  					LanguageID: "",
   607  					OnDisk:     c.OnDisk,
   608  					// changes to directories cannot include text or versions
   609  				})
   610  			}
   611  		}
   612  	}
   613  	return result
   614  }
   615  
   616  // updateOverlays updates the set of overlays and returns a map of any existing
   617  // overlay values that were replaced.
   618  //
   619  // Precondition: caller holds s.viewMu lock.
   620  // TODO(rfindley): move this to fs_overlay.go.
   621  func (fs *overlayFS) updateOverlays(ctx context.Context, changes []file.Modification) (map[protocol.DocumentURI]*overlay, error) {
   622  	fs.mu.Lock()
   623  	defer fs.mu.Unlock()
   624  
   625  	replaced := make(map[protocol.DocumentURI]*overlay)
   626  	for _, c := range changes {
   627  		o, ok := fs.overlays[c.URI]
   628  		if ok {
   629  			replaced[c.URI] = o
   630  		}
   631  
   632  		// If the file is not opened in an overlay and the change is on disk,
   633  		// there's no need to update an overlay. If there is an overlay, we
   634  		// may need to update the overlay's saved value.
   635  		if !ok && c.OnDisk {
   636  			continue
   637  		}
   638  
   639  		// Determine the file kind on open, otherwise, assume it has been cached.
   640  		var kind file.Kind
   641  		switch c.Action {
   642  		case file.Open:
   643  			kind = file.KindForLang(c.LanguageID)
   644  		default:
   645  			if !ok {
   646  				return nil, fmt.Errorf("updateOverlays: modifying unopened overlay %v", c.URI)
   647  			}
   648  			kind = o.kind
   649  		}
   650  
   651  		// Closing a file just deletes its overlay.
   652  		if c.Action == file.Close {
   653  			delete(fs.overlays, c.URI)
   654  			continue
   655  		}
   656  
   657  		// If the file is on disk, check if its content is the same as in the
   658  		// overlay. Saves and on-disk file changes don't come with the file's
   659  		// content.
   660  		text := c.Text
   661  		if text == nil && (c.Action == file.Save || c.OnDisk) {
   662  			if !ok {
   663  				return nil, fmt.Errorf("no known content for overlay for %s", c.Action)
   664  			}
   665  			text = o.content
   666  		}
   667  		// On-disk changes don't come with versions.
   668  		version := c.Version
   669  		if c.OnDisk || c.Action == file.Save {
   670  			version = o.version
   671  		}
   672  		hash := file.HashOf(text)
   673  		var sameContentOnDisk bool
   674  		switch c.Action {
   675  		case file.Delete:
   676  			// Do nothing. sameContentOnDisk should be false.
   677  		case file.Save:
   678  			// Make sure the version and content (if present) is the same.
   679  			if c.Text != nil && o.hash != hash {
   680  				return nil, fmt.Errorf("updateOverlays: overlay %s changed on save", c.URI)
   681  			}
   682  			sameContentOnDisk = true
   683  		default:
   684  			fh := mustReadFile(ctx, fs.delegate, c.URI)
   685  			_, readErr := fh.Content()
   686  			sameContentOnDisk = (readErr == nil && fh.Identity().Hash == hash)
   687  		}
   688  		o = &overlay{
   689  			uri:     c.URI,
   690  			version: version,
   691  			content: text,
   692  			kind:    kind,
   693  			hash:    hash,
   694  			saved:   sameContentOnDisk,
   695  		}
   696  
   697  		// NOTE: previous versions of this code checked here that the overlay had a
   698  		// view and file kind (but we don't know why).
   699  
   700  		fs.overlays[c.URI] = o
   701  	}
   702  
   703  	return replaced, nil
   704  }
   705  
   706  func mustReadFile(ctx context.Context, fs file.Source, uri protocol.DocumentURI) file.Handle {
   707  	ctx = xcontext.Detach(ctx)
   708  	fh, err := fs.ReadFile(ctx, uri)
   709  	if err != nil {
   710  		// ReadFile cannot fail with an uncancellable context.
   711  		bug.Reportf("reading file failed unexpectedly: %v", err)
   712  		return brokenFile{uri, err}
   713  	}
   714  	return fh
   715  }
   716  
   717  // A brokenFile represents an unexpected failure to read a file.
   718  type brokenFile struct {
   719  	uri protocol.DocumentURI
   720  	err error
   721  }
   722  
   723  func (b brokenFile) URI() protocol.DocumentURI { return b.uri }
   724  func (b brokenFile) Identity() file.Identity   { return file.Identity{URI: b.uri} }
   725  func (b brokenFile) SameContentsOnDisk() bool  { return false }
   726  func (b brokenFile) Version() int32            { return 0 }
   727  func (b brokenFile) Content() ([]byte, error)  { return nil, b.err }
   728  
   729  // FileWatchingGlobPatterns returns a set of glob patterns that the client is
   730  // required to watch for changes, and notify the server of them, in order to
   731  // keep the server's state up to date.
   732  //
   733  // This set includes
   734  //  1. all go.mod and go.work files in the workspace; and
   735  //  2. for each Snapshot, its modules (or directory for ad-hoc views). In
   736  //     module mode, this is the set of active modules (and for VS Code, all
   737  //     workspace directories within them, due to golang/go#42348).
   738  //
   739  // The watch for workspace go.work and go.mod files in (1) is sufficient to
   740  // capture changes to the repo structure that may affect the set of views.
   741  // Whenever this set changes, we reload the workspace and invalidate memoized
   742  // files.
   743  //
   744  // The watch for workspace directories in (2) should keep each View up to date,
   745  // as it should capture any newly added/modified/deleted Go files.
   746  //
   747  // Patterns are returned as a set of protocol.RelativePatterns, since they can
   748  // always be later translated to glob patterns (i.e. strings) if the client
   749  // lacks relative pattern support. By convention, any pattern returned with
   750  // empty baseURI should be served as a glob pattern.
   751  //
   752  // In general, we prefer to serve relative patterns, as they work better on
   753  // most clients that support both, and do not have issues with Windows driver
   754  // letter casing:
   755  // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#relativePattern
   756  //
   757  // TODO(golang/go#57979): we need to reset the memoizedFS when a view changes.
   758  // Consider the case where we incidentally read a file, then it moved outside
   759  // of an active module, and subsequently changed: we would still observe the
   760  // original file state.
   761  func (s *Session) FileWatchingGlobPatterns(ctx context.Context) map[protocol.RelativePattern]unit {
   762  	s.viewMu.Lock()
   763  	defer s.viewMu.Unlock()
   764  
   765  	// Always watch files that may change the set of views.
   766  	patterns := map[protocol.RelativePattern]unit{
   767  		{Pattern: "**/*.{mod,work}"}: {},
   768  	}
   769  
   770  	for _, view := range s.views {
   771  		snapshot, release, err := view.Snapshot()
   772  		if err != nil {
   773  			continue // view is shut down; continue with others
   774  		}
   775  		for k, v := range snapshot.fileWatchingGlobPatterns() {
   776  			patterns[k] = v
   777  		}
   778  		release()
   779  	}
   780  	return patterns
   781  }
   782  
   783  // OrphanedFileDiagnostics reports diagnostics describing why open files have
   784  // no packages or have only command-line-arguments packages.
   785  //
   786  // If the resulting diagnostic is nil, the file is either not orphaned or we
   787  // can't produce a good diagnostic.
   788  //
   789  // The caller must not mutate the result.
   790  func (s *Session) OrphanedFileDiagnostics(ctx context.Context) (map[protocol.DocumentURI][]*Diagnostic, error) {
   791  	// Note: diagnostics holds a slice for consistency with other diagnostic
   792  	// funcs.
   793  	diagnostics := make(map[protocol.DocumentURI][]*Diagnostic)
   794  
   795  	byView := make(map[*View][]*overlay)
   796  	for _, o := range s.Overlays() {
   797  		uri := o.URI()
   798  		snapshot, release, err := s.SnapshotOf(ctx, uri)
   799  		if err != nil {
   800  			// TODO(golang/go#57979): we have to use the .go suffix as an approximation for
   801  			// file kind here, because we don't have access to Options if no View was
   802  			// matched.
   803  			//
   804  			// But Options are really a property of Folder, not View, and we could
   805  			// match a folder here.
   806  			//
   807  			// Refactor so that Folders are tracked independently of Views, and use
   808  			// the correct options here to get the most accurate file kind.
   809  			//
   810  			// TODO(golang/go#57979): once we switch entirely to the zeroconfig
   811  			// logic, we should use this diagnostic for the fallback case of
   812  			// s.views[0] in the ViewOf logic.
   813  			if errors.Is(err, errNoViews) {
   814  				if strings.HasSuffix(string(uri), ".go") {
   815  					if _, rng, ok := orphanedFileDiagnosticRange(ctx, s.parseCache, o); ok {
   816  						diagnostics[uri] = []*Diagnostic{{
   817  							URI:      uri,
   818  							Range:    rng,
   819  							Severity: protocol.SeverityWarning,
   820  							Source:   ListError,
   821  							Message:  fmt.Sprintf("No active builds contain %s: consider opening a new workspace folder containing it", uri.Path()),
   822  						}}
   823  					}
   824  				}
   825  				continue
   826  			}
   827  			return nil, err
   828  		}
   829  		v := snapshot.View()
   830  		release()
   831  		byView[v] = append(byView[v], o)
   832  	}
   833  
   834  	return diagnostics, nil
   835  }