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
5package cache
6
7import (
8	"bytes"
9	"context"
10	"fmt"
11	"go/ast"
12	"go/token"
13	"go/types"
14	"io"
15	"os"
16	"path/filepath"
17	"sort"
18	"strconv"
19	"strings"
20	"sync"
21
22	"golang.org/x/mod/modfile"
23	"golang.org/x/mod/module"
24	"golang.org/x/tools/go/analysis"
25	"golang.org/x/tools/go/packages"
26	"golang.org/x/tools/internal/event"
27	"golang.org/x/tools/internal/gocommand"
28	"golang.org/x/tools/internal/lsp/debug/tag"
29	"golang.org/x/tools/internal/lsp/source"
30	"golang.org/x/tools/internal/memoize"
31	"golang.org/x/tools/internal/packagesinternal"
32	"golang.org/x/tools/internal/span"
33	"golang.org/x/tools/internal/typesinternal"
34	errors "golang.org/x/xerrors"
35)
36
37type snapshot struct {
38	memoize.Arg // allow as a memoize.Function arg
39
40	id   uint64
41	view *View
42
43	// the cache generation that contains the data for this snapshot.
44	generation *memoize.Generation
45
46	// builtin pins the AST and package for builtin.go in memory.
47	builtin *builtinPackageHandle
48
49	// The snapshot's initialization state is controlled by the fields below.
50	//
51	// initializeOnce guards snapshot initialization. Each snapshot is
52	// initialized at most once: reinitialization is triggered on later snapshots
53	// by invalidating this field.
54	initializeOnce *sync.Once
55	// initializedErr holds the last error resulting from initialization. If
56	// initialization fails, we only retry when the the workspace modules change,
57	// to avoid too many go/packages calls.
58	initializedErr error
59
60	// mu guards all of the maps in the snapshot.
61	mu sync.Mutex
62
63	// ids maps file URIs to package IDs.
64	// It may be invalidated on calls to go/packages.
65	ids map[span.URI][]packageID
66
67	// metadata maps file IDs to their associated metadata.
68	// It may invalidated on calls to go/packages.
69	metadata map[packageID]*metadata
70
71	// importedBy maps package IDs to the list of packages that import them.
72	importedBy map[packageID][]packageID
73
74	// files maps file URIs to their corresponding FileHandles.
75	// It may invalidated when a file's content changes.
76	files map[span.URI]source.VersionedFileHandle
77
78	// goFiles maps a parseKey to its parseGoHandle.
79	goFiles map[parseKey]*parseGoHandle
80
81	// packages maps a packageKey to a set of packageHandles to which that file belongs.
82	// It may be invalidated when a file's content changes.
83	packages map[packageKey]*packageHandle
84
85	// actions maps an actionkey to its actionHandle.
86	actions map[actionKey]*actionHandle
87
88	// workspacePackages contains the workspace's packages, which are loaded
89	// when the view is created.
90	workspacePackages map[packageID]packagePath
91
92	// unloadableFiles keeps track of files that we've failed to load.
93	unloadableFiles map[span.URI]struct{}
94
95	// parseModHandles keeps track of any ParseModHandles for the snapshot.
96	// The handles need not refer to only the view's go.mod file.
97	parseModHandles map[span.URI]*parseModHandle
98
99	// Preserve go.mod-related handles to avoid garbage-collecting the results
100	// of various calls to the go command. The handles need not refer to only
101	// the view's go.mod file.
102	modTidyHandles    map[span.URI]*modTidyHandle
103	modUpgradeHandles map[span.URI]*modUpgradeHandle
104	modWhyHandles     map[span.URI]*modWhyHandle
105
106	workspace          *workspace
107	workspaceDirHandle *memoize.Handle
108}
109
110type packageKey struct {
111	mode source.ParseMode
112	id   packageID
113}
114
115type actionKey struct {
116	pkg      packageKey
117	analyzer *analysis.Analyzer
118}
119
120func (s *snapshot) ID() uint64 {
121	return s.id
122}
123
124func (s *snapshot) View() source.View {
125	return s.view
126}
127
128func (s *snapshot) FileSet() *token.FileSet {
129	return s.view.session.cache.fset
130}
131
132func (s *snapshot) ModFiles() []span.URI {
133	var uris []span.URI
134	for modURI := range s.workspace.getActiveModFiles() {
135		uris = append(uris, modURI)
136	}
137	return uris
138}
139
140func (s *snapshot) ValidBuildConfiguration() bool {
141	return validBuildConfiguration(s.view.rootURI, &s.view.workspaceInformation, s.workspace.getActiveModFiles())
142}
143
144// workspaceMode describes the way in which the snapshot's workspace should
145// be loaded.
146func (s *snapshot) workspaceMode() workspaceMode {
147	var mode workspaceMode
148
149	// If the view has an invalid configuration, don't build the workspace
150	// module.
151	validBuildConfiguration := s.ValidBuildConfiguration()
152	if !validBuildConfiguration {
153		return mode
154	}
155	// If the view is not in a module and contains no modules, but still has a
156	// valid workspace configuration, do not create the workspace module.
157	// It could be using GOPATH or a different build system entirely.
158	if len(s.workspace.getActiveModFiles()) == 0 && validBuildConfiguration {
159		return mode
160	}
161	mode |= moduleMode
162	options := s.view.Options()
163	// The -modfile flag is available for Go versions >= 1.14.
164	if options.TempModfile && s.view.workspaceInformation.goversion >= 14 {
165		mode |= tempModfile
166	}
167	// If the user is intentionally limiting their workspace scope, don't
168	// enable multi-module workspace mode.
169	// TODO(rstambler): This should only change the calculation of the root,
170	// not the mode.
171	if !options.ExpandWorkspaceToModule {
172		return mode
173	}
174	// The workspace module has been disabled by the user.
175	if !options.ExperimentalWorkspaceModule {
176		return mode
177	}
178	mode |= usesWorkspaceModule
179	return mode
180}
181
182// config returns the configuration used for the snapshot's interaction with
183// the go/packages API. It uses the given working directory.
184//
185// TODO(rstambler): go/packages requires that we do not provide overlays for
186// multiple modules in on config, so buildOverlay needs to filter overlays by
187// module.
188func (s *snapshot) config(ctx context.Context, inv *gocommand.Invocation) *packages.Config {
189	s.view.optionsMu.Lock()
190	verboseOutput := s.view.options.VerboseOutput
191	s.view.optionsMu.Unlock()
192
193	// Forcibly disable GOPACKAGESDRIVER. It's incompatible with the
194	// packagesinternal APIs we use, and we really only support the go command
195	// anyway.
196	env := append(append([]string{}, inv.Env...), "GOPACKAGESDRIVER=off")
197	cfg := &packages.Config{
198		Context:    ctx,
199		Dir:        inv.WorkingDir,
200		Env:        env,
201		BuildFlags: inv.BuildFlags,
202		Mode: packages.NeedName |
203			packages.NeedFiles |
204			packages.NeedCompiledGoFiles |
205			packages.NeedImports |
206			packages.NeedDeps |
207			packages.NeedTypesSizes |
208			packages.NeedModule,
209		Fset:    s.view.session.cache.fset,
210		Overlay: s.buildOverlay(),
211		ParseFile: func(*token.FileSet, string, []byte) (*ast.File, error) {
212			panic("go/packages must not be used to parse files")
213		},
214		Logf: func(format string, args ...interface{}) {
215			if verboseOutput {
216				event.Log(ctx, fmt.Sprintf(format, args...))
217			}
218		},
219		Tests: true,
220	}
221	packagesinternal.SetModFile(cfg, inv.ModFile)
222	packagesinternal.SetModFlag(cfg, inv.ModFlag)
223	// We want to type check cgo code if go/types supports it.
224	if typesinternal.SetUsesCgo(&types.Config{}) {
225		cfg.Mode |= packages.LoadMode(packagesinternal.TypecheckCgo)
226	}
227	packagesinternal.SetGoCmdRunner(cfg, s.view.session.gocmdRunner)
228	return cfg
229}
230
231func (s *snapshot) RunGoCommandDirect(ctx context.Context, mode source.InvocationFlags, inv *gocommand.Invocation) (*bytes.Buffer, error) {
232	_, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv)
233	if err != nil {
234		return nil, err
235	}
236	defer cleanup()
237
238	return s.view.session.gocmdRunner.Run(ctx, *inv)
239}
240
241func (s *snapshot) RunGoCommandPiped(ctx context.Context, mode source.InvocationFlags, inv *gocommand.Invocation, stdout, stderr io.Writer) error {
242	_, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv)
243	if err != nil {
244		return err
245	}
246	defer cleanup()
247	return s.view.session.gocmdRunner.RunPiped(ctx, *inv, stdout, stderr)
248}
249
250func (s *snapshot) goCommandInvocation(ctx context.Context, flags source.InvocationFlags, inv *gocommand.Invocation) (tmpURI span.URI, updatedInv *gocommand.Invocation, cleanup func(), err error) {
251	s.view.optionsMu.Lock()
252	allowModfileModificationOption := s.view.options.AllowModfileModifications
253	allowNetworkOption := s.view.options.AllowImplicitNetworkAccess
254	inv.Env = append(append(append(os.Environ(), s.view.options.EnvSlice()...), inv.Env...), "GO111MODULE="+s.view.go111module)
255	inv.BuildFlags = append([]string{}, s.view.options.BuildFlags...)
256	s.view.optionsMu.Unlock()
257	cleanup = func() {} // fallback
258
259	// All logic below is for module mode.
260	if s.workspaceMode()&moduleMode == 0 {
261		return "", inv, cleanup, nil
262	}
263
264	mode, allowNetwork := flags.Mode(), flags.AllowNetwork()
265	if !allowNetwork && !allowNetworkOption {
266		inv.Env = append(inv.Env, "GOPROXY=off")
267	}
268
269	var modURI span.URI
270	// Select the module context to use.
271	// If we're type checking, we need to use the workspace context, meaning
272	// the main (workspace) module. Otherwise, we should use the module for
273	// the passed-in working dir.
274	if mode == source.LoadWorkspace {
275		if s.workspaceMode()&usesWorkspaceModule == 0 {
276			for m := range s.workspace.getActiveModFiles() { // range to access the only element
277				modURI = m
278			}
279		} else {
280			var tmpDir span.URI
281			var err error
282			tmpDir, err = s.getWorkspaceDir(ctx)
283			if err != nil {
284				return "", nil, cleanup, err
285			}
286			inv.WorkingDir = tmpDir.Filename()
287			modURI = span.URIFromPath(filepath.Join(tmpDir.Filename(), "go.mod"))
288		}
289	} else {
290		modURI = s.GoModForFile(ctx, span.URIFromPath(inv.WorkingDir))
291	}
292
293	var modContent []byte
294	if modURI != "" {
295		modFH, err := s.GetFile(ctx, modURI)
296		if err != nil {
297			return "", nil, cleanup, err
298		}
299		modContent, err = modFH.Read()
300		if err != nil {
301			return "", nil, cleanup, err
302		}
303	}
304
305	vendorEnabled, err := s.vendorEnabled(ctx, modURI, modContent)
306	if err != nil {
307		return "", nil, cleanup, err
308	}
309
310	mutableModFlag := ""
311	if s.view.goversion >= 16 {
312		mutableModFlag = "mod"
313	}
314
315	switch mode {
316	case source.LoadWorkspace, source.Normal:
317		if vendorEnabled {
318			inv.ModFlag = "vendor"
319		} else if s.workspaceMode()&usesWorkspaceModule == 0 && !allowModfileModificationOption {
320			inv.ModFlag = "readonly"
321		} else {
322			// Temporarily allow updates for multi-module workspace mode:
323			// it doesn't create a go.sum at all. golang/go#42509.
324			inv.ModFlag = mutableModFlag
325		}
326	case source.UpdateUserModFile, source.WriteTemporaryModFile:
327		inv.ModFlag = mutableModFlag
328	}
329
330	wantTempMod := mode != source.UpdateUserModFile
331	needTempMod := mode == source.WriteTemporaryModFile
332	tempMod := wantTempMod && s.workspaceMode()&tempModfile != 0
333	if needTempMod && !tempMod {
334		return "", nil, cleanup, source.ErrTmpModfileUnsupported
335	}
336
337	if tempMod {
338		if modURI == "" {
339			return "", nil, cleanup, fmt.Errorf("no go.mod file found in %s", inv.WorkingDir)
340		}
341		modFH, err := s.GetFile(ctx, modURI)
342		if err != nil {
343			return "", nil, cleanup, err
344		}
345		// Use the go.sum if it happens to be available.
346		gosum := s.goSum(ctx, modURI)
347		tmpURI, cleanup, err = tempModFile(modFH, gosum)
348		if err != nil {
349			return "", nil, cleanup, err
350		}
351		inv.ModFile = tmpURI.Filename()
352	}
353
354	return tmpURI, inv, cleanup, nil
355}
356
357func (s *snapshot) buildOverlay() map[string][]byte {
358	s.mu.Lock()
359	defer s.mu.Unlock()
360
361	overlays := make(map[string][]byte)
362	for uri, fh := range s.files {
363		overlay, ok := fh.(*overlay)
364		if !ok {
365			continue
366		}
367		if overlay.saved {
368			continue
369		}
370		// TODO(rstambler): Make sure not to send overlays outside of the current view.
371		overlays[uri.Filename()] = overlay.text
372	}
373	return overlays
374}
375
376func hashUnsavedOverlays(files map[span.URI]source.VersionedFileHandle) string {
377	var unsaved []string
378	for uri, fh := range files {
379		if overlay, ok := fh.(*overlay); ok && !overlay.saved {
380			unsaved = append(unsaved, uri.Filename())
381		}
382	}
383	sort.Strings(unsaved)
384	return hashContents([]byte(strings.Join(unsaved, "")))
385}
386
387func (s *snapshot) PackagesForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode) ([]source.Package, error) {
388	ctx = event.Label(ctx, tag.URI.Of(uri))
389
390	phs, err := s.packageHandlesForFile(ctx, uri, mode)
391	if err != nil {
392		return nil, err
393	}
394	var pkgs []source.Package
395	for _, ph := range phs {
396		pkg, err := ph.check(ctx, s)
397		if err != nil {
398			return nil, err
399		}
400		pkgs = append(pkgs, pkg)
401	}
402	return pkgs, nil
403}
404
405func (s *snapshot) PackageForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode, pkgPolicy source.PackageFilter) (source.Package, error) {
406	ctx = event.Label(ctx, tag.URI.Of(uri))
407
408	phs, err := s.packageHandlesForFile(ctx, uri, mode)
409	if err != nil {
410		return nil, err
411	}
412
413	if len(phs) < 1 {
414		return nil, errors.Errorf("no packages")
415	}
416
417	ph := phs[0]
418	for _, handle := range phs[1:] {
419		switch pkgPolicy {
420		case source.WidestPackage:
421			if ph == nil || len(handle.CompiledGoFiles()) > len(ph.CompiledGoFiles()) {
422				ph = handle
423			}
424		case source.NarrowestPackage:
425			if ph == nil || len(handle.CompiledGoFiles()) < len(ph.CompiledGoFiles()) {
426				ph = handle
427			}
428		}
429	}
430	if ph == nil {
431		return nil, errors.Errorf("no packages in input")
432	}
433
434	return ph.check(ctx, s)
435}
436
437func (s *snapshot) packageHandlesForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode) ([]*packageHandle, error) {
438	// Check if we should reload metadata for the file. We don't invalidate IDs
439	// (though we should), so the IDs will be a better source of truth than the
440	// metadata. If there are no IDs for the file, then we should also reload.
441	fh, err := s.GetFile(ctx, uri)
442	if err != nil {
443		return nil, err
444	}
445	if fh.Kind() != source.Go {
446		return nil, fmt.Errorf("no packages for non-Go file %s", uri)
447	}
448	ids := s.getIDsForURI(uri)
449	reload := len(ids) == 0
450	for _, id := range ids {
451		// Reload package metadata if any of the metadata has missing
452		// dependencies, in case something has changed since the last time we
453		// reloaded it.
454		if m := s.getMetadata(id); m == nil {
455			reload = true
456			break
457		}
458		// TODO(golang/go#36918): Previously, we would reload any package with
459		// missing dependencies. This is expensive and results in too many
460		// calls to packages.Load. Determine what we should do instead.
461	}
462	if reload {
463		if err := s.load(ctx, false, fileURI(uri)); err != nil {
464			return nil, err
465		}
466	}
467	// Get the list of IDs from the snapshot again, in case it has changed.
468	var phs []*packageHandle
469	for _, id := range s.getIDsForURI(uri) {
470		var parseModes []source.ParseMode
471		switch mode {
472		case source.TypecheckAll:
473			if s.workspaceParseMode(id) == source.ParseFull {
474				parseModes = []source.ParseMode{source.ParseFull}
475			} else {
476				parseModes = []source.ParseMode{source.ParseExported, source.ParseFull}
477			}
478		case source.TypecheckFull:
479			parseModes = []source.ParseMode{source.ParseFull}
480		case source.TypecheckWorkspace:
481			parseModes = []source.ParseMode{s.workspaceParseMode(id)}
482		}
483
484		for _, parseMode := range parseModes {
485			ph, err := s.buildPackageHandle(ctx, id, parseMode)
486			if err != nil {
487				return nil, err
488			}
489			phs = append(phs, ph)
490		}
491	}
492
493	return phs, nil
494}
495
496func (s *snapshot) GetReverseDependencies(ctx context.Context, id string) ([]source.Package, error) {
497	if err := s.awaitLoaded(ctx); err != nil {
498		return nil, err
499	}
500	ids := make(map[packageID]struct{})
501	s.transitiveReverseDependencies(packageID(id), ids)
502
503	// Make sure to delete the original package ID from the map.
504	delete(ids, packageID(id))
505
506	var pkgs []source.Package
507	for id := range ids {
508		pkg, err := s.checkedPackage(ctx, id, s.workspaceParseMode(id))
509		if err != nil {
510			return nil, err
511		}
512		pkgs = append(pkgs, pkg)
513	}
514	return pkgs, nil
515}
516
517func (s *snapshot) checkedPackage(ctx context.Context, id packageID, mode source.ParseMode) (*pkg, error) {
518	ph, err := s.buildPackageHandle(ctx, id, mode)
519	if err != nil {
520		return nil, err
521	}
522	return ph.check(ctx, s)
523}
524
525// transitiveReverseDependencies populates the uris map with file URIs
526// belonging to the provided package and its transitive reverse dependencies.
527func (s *snapshot) transitiveReverseDependencies(id packageID, ids map[packageID]struct{}) {
528	if _, ok := ids[id]; ok {
529		return
530	}
531	if s.getMetadata(id) == nil {
532		return
533	}
534	ids[id] = struct{}{}
535	importedBy := s.getImportedBy(id)
536	for _, parentID := range importedBy {
537		s.transitiveReverseDependencies(parentID, ids)
538	}
539}
540
541func (s *snapshot) getGoFile(key parseKey) *parseGoHandle {
542	s.mu.Lock()
543	defer s.mu.Unlock()
544	return s.goFiles[key]
545}
546
547func (s *snapshot) addGoFile(key parseKey, pgh *parseGoHandle) *parseGoHandle {
548	s.mu.Lock()
549	defer s.mu.Unlock()
550	if existing, ok := s.goFiles[key]; ok {
551		return existing
552	}
553	s.goFiles[key] = pgh
554	return pgh
555}
556
557func (s *snapshot) getParseModHandle(uri span.URI) *parseModHandle {
558	s.mu.Lock()
559	defer s.mu.Unlock()
560	return s.parseModHandles[uri]
561}
562
563func (s *snapshot) getModWhyHandle(uri span.URI) *modWhyHandle {
564	s.mu.Lock()
565	defer s.mu.Unlock()
566	return s.modWhyHandles[uri]
567}
568
569func (s *snapshot) getModUpgradeHandle(uri span.URI) *modUpgradeHandle {
570	s.mu.Lock()
571	defer s.mu.Unlock()
572	return s.modUpgradeHandles[uri]
573}
574
575func (s *snapshot) getModTidyHandle(uri span.URI) *modTidyHandle {
576	s.mu.Lock()
577	defer s.mu.Unlock()
578	return s.modTidyHandles[uri]
579}
580
581func (s *snapshot) getImportedBy(id packageID) []packageID {
582	s.mu.Lock()
583	defer s.mu.Unlock()
584	return s.getImportedByLocked(id)
585}
586
587func (s *snapshot) getImportedByLocked(id packageID) []packageID {
588	// If we haven't rebuilt the import graph since creating the snapshot.
589	if len(s.importedBy) == 0 {
590		s.rebuildImportGraph()
591	}
592	return s.importedBy[id]
593}
594
595func (s *snapshot) clearAndRebuildImportGraph() {
596	s.mu.Lock()
597	defer s.mu.Unlock()
598
599	// Completely invalidate the original map.
600	s.importedBy = make(map[packageID][]packageID)
601	s.rebuildImportGraph()
602}
603
604func (s *snapshot) rebuildImportGraph() {
605	for id, m := range s.metadata {
606		for _, importID := range m.deps {
607			s.importedBy[importID] = append(s.importedBy[importID], id)
608		}
609	}
610}
611
612func (s *snapshot) addPackageHandle(ph *packageHandle) *packageHandle {
613	s.mu.Lock()
614	defer s.mu.Unlock()
615
616	// If the package handle has already been cached,
617	// return the cached handle instead of overriding it.
618	if ph, ok := s.packages[ph.packageKey()]; ok {
619		return ph
620	}
621	s.packages[ph.packageKey()] = ph
622	return ph
623}
624
625func (s *snapshot) workspacePackageIDs() (ids []packageID) {
626	s.mu.Lock()
627	defer s.mu.Unlock()
628
629	for id := range s.workspacePackages {
630		ids = append(ids, id)
631	}
632	return ids
633}
634
635func (s *snapshot) fileWatchingGlobPatterns(ctx context.Context) map[string]struct{} {
636	// Work-around microsoft/vscode#100870 by making sure that we are,
637	// at least, watching the user's entire workspace. This will still be
638	// applied to every folder in the workspace.
639	patterns := map[string]struct{}{
640		"**/*.{go,mod,sum}": {},
641	}
642	dirs := s.workspace.dirs(ctx, s)
643	for _, dir := range dirs {
644		dirName := dir.Filename()
645
646		// If the directory is within the view's folder, we're already watching
647		// it with the pattern above.
648		if source.InDir(s.view.folder.Filename(), dirName) {
649			continue
650		}
651		// TODO(rstambler): If microsoft/vscode#3025 is resolved before
652		// microsoft/vscode#101042, we will need a work-around for Windows
653		// drive letter casing.
654		patterns[fmt.Sprintf("%s/**/*.{go,mod,sum}", dirName)] = struct{}{}
655	}
656
657	// Some clients do not send notifications for changes to directories that
658	// contain Go code (golang/go#42348). To handle this, explicitly watch all
659	// of the directories in the workspace. We find them by adding the
660	// directories of every file in the snapshot's workspace directories.
661	var dirNames []string
662	for uri := range s.allKnownSubdirs(ctx) {
663		dirNames = append(dirNames, uri.Filename())
664	}
665	sort.Strings(dirNames)
666	if len(dirNames) > 0 {
667		patterns[fmt.Sprintf("{%s}", strings.Join(dirNames, ","))] = struct{}{}
668	}
669	return patterns
670}
671
672// allKnownSubdirs returns all of the subdirectories within the snapshot's
673// workspace directories. None of the workspace directories are included.
674func (s *snapshot) allKnownSubdirs(ctx context.Context) map[span.URI]struct{} {
675	dirs := s.workspace.dirs(ctx, s)
676
677	s.mu.Lock()
678	defer s.mu.Unlock()
679	seen := make(map[span.URI]struct{})
680	for uri := range s.files {
681		dir := filepath.Dir(uri.Filename())
682		var matched span.URI
683		for _, wsDir := range dirs {
684			if source.InDir(wsDir.Filename(), dir) {
685				matched = wsDir
686				break
687			}
688		}
689		// Don't watch any directory outside of the workspace directories.
690		if matched == "" {
691			continue
692		}
693		for {
694			if dir == "" || dir == matched.Filename() {
695				break
696			}
697			uri := span.URIFromPath(dir)
698			if _, ok := seen[uri]; ok {
699				break
700			}
701			seen[uri] = struct{}{}
702			dir = filepath.Dir(dir)
703		}
704	}
705	return seen
706}
707
708// knownFilesInDir returns the files known to the given snapshot that are in
709// the given directory. It does not respect symlinks.
710func (s *snapshot) knownFilesInDir(ctx context.Context, dir span.URI) []span.URI {
711	var files []span.URI
712	for uri := range s.files {
713		if source.InDir(dir.Filename(), uri.Filename()) {
714			files = append(files, uri)
715		}
716	}
717	return files
718}
719
720func (s *snapshot) WorkspacePackages(ctx context.Context) ([]source.Package, error) {
721	if err := s.awaitLoaded(ctx); err != nil {
722		return nil, err
723	}
724	var pkgs []source.Package
725	for _, pkgID := range s.workspacePackageIDs() {
726		pkg, err := s.checkedPackage(ctx, pkgID, s.workspaceParseMode(pkgID))
727		if err != nil {
728			return nil, err
729		}
730		pkgs = append(pkgs, pkg)
731	}
732	return pkgs, nil
733}
734
735func (s *snapshot) KnownPackages(ctx context.Context) ([]source.Package, error) {
736	if err := s.awaitLoaded(ctx); err != nil {
737		return nil, err
738	}
739
740	// The WorkspaceSymbols implementation relies on this function returning
741	// workspace packages first.
742	ids := s.workspacePackageIDs()
743	s.mu.Lock()
744	for id := range s.metadata {
745		if _, ok := s.workspacePackages[id]; ok {
746			continue
747		}
748		ids = append(ids, id)
749	}
750	s.mu.Unlock()
751
752	var pkgs []source.Package
753	for _, id := range ids {
754		pkg, err := s.checkedPackage(ctx, id, s.workspaceParseMode(id))
755		if err != nil {
756			return nil, err
757		}
758		pkgs = append(pkgs, pkg)
759	}
760	return pkgs, nil
761}
762
763func (s *snapshot) CachedImportPaths(ctx context.Context) (map[string]source.Package, error) {
764	// Don't reload workspace package metadata.
765	// This function is meant to only return currently cached information.
766	s.AwaitInitialized(ctx)
767
768	s.mu.Lock()
769	defer s.mu.Unlock()
770
771	results := map[string]source.Package{}
772	for _, ph := range s.packages {
773		cachedPkg, err := ph.cached(s.generation)
774		if err != nil {
775			continue
776		}
777		for importPath, newPkg := range cachedPkg.imports {
778			if oldPkg, ok := results[string(importPath)]; ok {
779				// Using the same trick as NarrowestPackage, prefer non-variants.
780				if len(newPkg.compiledGoFiles) < len(oldPkg.(*pkg).compiledGoFiles) {
781					results[string(importPath)] = newPkg
782				}
783			} else {
784				results[string(importPath)] = newPkg
785			}
786		}
787	}
788	return results, nil
789}
790
791func (s *snapshot) GoModForFile(ctx context.Context, uri span.URI) span.URI {
792	var match span.URI
793	for modURI := range s.workspace.getActiveModFiles() {
794		if !source.InDir(dirURI(modURI).Filename(), uri.Filename()) {
795			continue
796		}
797		if len(modURI) > len(match) {
798			match = modURI
799		}
800	}
801	return match
802}
803
804func (s *snapshot) getPackage(id packageID, mode source.ParseMode) *packageHandle {
805	s.mu.Lock()
806	defer s.mu.Unlock()
807
808	key := packageKey{
809		id:   id,
810		mode: mode,
811	}
812	return s.packages[key]
813}
814
815func (s *snapshot) getActionHandle(id packageID, m source.ParseMode, a *analysis.Analyzer) *actionHandle {
816	s.mu.Lock()
817	defer s.mu.Unlock()
818
819	key := actionKey{
820		pkg: packageKey{
821			id:   id,
822			mode: m,
823		},
824		analyzer: a,
825	}
826	return s.actions[key]
827}
828
829func (s *snapshot) addActionHandle(ah *actionHandle) *actionHandle {
830	s.mu.Lock()
831	defer s.mu.Unlock()
832
833	key := actionKey{
834		analyzer: ah.analyzer,
835		pkg: packageKey{
836			id:   ah.pkg.m.id,
837			mode: ah.pkg.mode,
838		},
839	}
840	if ah, ok := s.actions[key]; ok {
841		return ah
842	}
843	s.actions[key] = ah
844	return ah
845}
846
847func (s *snapshot) getIDsForURI(uri span.URI) []packageID {
848	s.mu.Lock()
849	defer s.mu.Unlock()
850
851	return s.ids[uri]
852}
853
854func (s *snapshot) getMetadataForURILocked(uri span.URI) (metadata []*metadata) {
855	// TODO(matloob): uri can be a file or directory. Should we update the mappings
856	// to map directories to their contained packages?
857
858	for _, id := range s.ids[uri] {
859		if m, ok := s.metadata[id]; ok {
860			metadata = append(metadata, m)
861		}
862	}
863	return metadata
864}
865
866func (s *snapshot) getMetadata(id packageID) *metadata {
867	s.mu.Lock()
868	defer s.mu.Unlock()
869
870	return s.metadata[id]
871}
872
873func (s *snapshot) addID(uri span.URI, id packageID) {
874	s.mu.Lock()
875	defer s.mu.Unlock()
876
877	for i, existingID := range s.ids[uri] {
878		// TODO: We should make sure not to set duplicate IDs,
879		// and instead panic here. This can be done by making sure not to
880		// reset metadata information for packages we've already seen.
881		if existingID == id {
882			return
883		}
884		// If we are setting a real ID, when the package had only previously
885		// had a command-line-arguments ID, we should just replace it.
886		if existingID == "command-line-arguments" {
887			s.ids[uri][i] = id
888			// Delete command-line-arguments if it was a workspace package.
889			delete(s.workspacePackages, existingID)
890			return
891		}
892	}
893	s.ids[uri] = append(s.ids[uri], id)
894}
895
896func (s *snapshot) isWorkspacePackage(id packageID) (packagePath, bool) {
897	s.mu.Lock()
898	defer s.mu.Unlock()
899
900	scope, ok := s.workspacePackages[id]
901	return scope, ok
902}
903
904func (s *snapshot) FindFile(uri span.URI) source.VersionedFileHandle {
905	f, err := s.view.getFile(uri)
906	if err != nil {
907		return nil
908	}
909
910	s.mu.Lock()
911	defer s.mu.Unlock()
912
913	return s.files[f.URI()]
914}
915
916// GetVersionedFile returns a File for the given URI. If the file is unknown it
917// is added to the managed set.
918//
919// GetVersionedFile succeeds even if the file does not exist. A non-nil error return
920// indicates some type of internal error, for example if ctx is cancelled.
921func (s *snapshot) GetVersionedFile(ctx context.Context, uri span.URI) (source.VersionedFileHandle, error) {
922	f, err := s.view.getFile(uri)
923	if err != nil {
924		return nil, err
925	}
926
927	s.mu.Lock()
928	defer s.mu.Unlock()
929	return s.getFileLocked(ctx, f)
930}
931
932// GetFile implements the fileSource interface by wrapping GetVersionedFile.
933func (s *snapshot) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
934	return s.GetVersionedFile(ctx, uri)
935}
936
937func (s *snapshot) getFileLocked(ctx context.Context, f *fileBase) (source.VersionedFileHandle, error) {
938	if fh, ok := s.files[f.URI()]; ok {
939		return fh, nil
940	}
941
942	fh, err := s.view.session.cache.getFile(ctx, f.URI())
943	if err != nil {
944		return nil, err
945	}
946	closed := &closedFile{fh}
947	s.files[f.URI()] = closed
948	return closed, nil
949}
950
951func (s *snapshot) IsOpen(uri span.URI) bool {
952	s.mu.Lock()
953	defer s.mu.Unlock()
954	return s.isOpenLocked(uri)
955
956}
957
958func (s *snapshot) isOpenLocked(uri span.URI) bool {
959	_, open := s.files[uri].(*overlay)
960	return open
961}
962
963func (s *snapshot) awaitLoaded(ctx context.Context) error {
964	// Do not return results until the snapshot's view has been initialized.
965	s.AwaitInitialized(ctx)
966
967	if err := s.reloadWorkspace(ctx); err != nil {
968		return err
969	}
970	if err := s.reloadOrphanedFiles(ctx); err != nil {
971		return err
972	}
973	// If we still have absolutely no metadata, check if the view failed to
974	// initialize and return any errors.
975	// TODO(rstambler): Should we clear the error after we return it?
976	s.mu.Lock()
977	defer s.mu.Unlock()
978	if len(s.metadata) == 0 {
979		return s.initializedErr
980	}
981	return nil
982}
983
984func (s *snapshot) AwaitInitialized(ctx context.Context) {
985	select {
986	case <-ctx.Done():
987		return
988	case <-s.view.initialWorkspaceLoad:
989	}
990	// We typically prefer to run something as intensive as the IWL without
991	// blocking. I'm not sure if there is a way to do that here.
992	s.initialize(ctx, false)
993}
994
995// reloadWorkspace reloads the metadata for all invalidated workspace packages.
996func (s *snapshot) reloadWorkspace(ctx context.Context) error {
997	// See which of the workspace packages are missing metadata.
998	s.mu.Lock()
999	missingMetadata := len(s.workspacePackages) == 0 || len(s.metadata) == 0
1000	pkgPathSet := map[packagePath]struct{}{}
1001	for id, pkgPath := range s.workspacePackages {
1002		if s.metadata[id] != nil {
1003			continue
1004		}
1005		missingMetadata = true
1006
1007		// Don't try to reload "command-line-arguments" directly.
1008		if pkgPath == "command-line-arguments" {
1009			continue
1010		}
1011		pkgPathSet[pkgPath] = struct{}{}
1012	}
1013	s.mu.Unlock()
1014
1015	// If the view's build configuration is invalid, we cannot reload by
1016	// package path. Just reload the directory instead.
1017	if missingMetadata && !s.ValidBuildConfiguration() {
1018		return s.load(ctx, false, viewLoadScope("LOAD_INVALID_VIEW"))
1019	}
1020
1021	if len(pkgPathSet) == 0 {
1022		return nil
1023	}
1024
1025	var pkgPaths []interface{}
1026	for pkgPath := range pkgPathSet {
1027		pkgPaths = append(pkgPaths, pkgPath)
1028	}
1029	return s.load(ctx, false, pkgPaths...)
1030}
1031
1032func (s *snapshot) reloadOrphanedFiles(ctx context.Context) error {
1033	// When we load ./... or a package path directly, we may not get packages
1034	// that exist only in overlays. As a workaround, we search all of the files
1035	// available in the snapshot and reload their metadata individually using a
1036	// file= query if the metadata is unavailable.
1037	scopes := s.orphanedFileScopes()
1038	if len(scopes) == 0 {
1039		return nil
1040	}
1041
1042	err := s.load(ctx, false, scopes...)
1043
1044	// If we failed to load some files, i.e. they have no metadata,
1045	// mark the failures so we don't bother retrying until the file's
1046	// content changes.
1047	//
1048	// TODO(rstambler): This may be an overestimate if the load stopped
1049	// early for an unrelated errors. Add a fallback?
1050	//
1051	// Check for context cancellation so that we don't incorrectly mark files
1052	// as unloadable, but don't return before setting all workspace packages.
1053	if ctx.Err() == nil && err != nil {
1054		event.Error(ctx, "reloadOrphanedFiles: failed to load", err, tag.Query.Of(scopes))
1055		s.mu.Lock()
1056		for _, scope := range scopes {
1057			uri := span.URI(scope.(fileURI))
1058			if s.getMetadataForURILocked(uri) == nil {
1059				s.unloadableFiles[uri] = struct{}{}
1060			}
1061		}
1062		s.mu.Unlock()
1063	}
1064	return nil
1065}
1066
1067func (s *snapshot) orphanedFileScopes() []interface{} {
1068	s.mu.Lock()
1069	defer s.mu.Unlock()
1070
1071	scopeSet := make(map[span.URI]struct{})
1072	for uri, fh := range s.files {
1073		// Don't try to reload metadata for go.mod files.
1074		if fh.Kind() != source.Go {
1075			continue
1076		}
1077		// If the URI doesn't belong to this view, then it's not in a workspace
1078		// package and should not be reloaded directly.
1079		if !contains(s.view.session.viewsOf(uri), s.view) {
1080			continue
1081		}
1082		// If the file is not open and is in a vendor directory, don't treat it
1083		// like a workspace package.
1084		if _, ok := fh.(*overlay); !ok && inVendor(uri) {
1085			continue
1086		}
1087		// Don't reload metadata for files we've already deemed unloadable.
1088		if _, ok := s.unloadableFiles[uri]; ok {
1089			continue
1090		}
1091		if s.getMetadataForURILocked(uri) == nil {
1092			scopeSet[uri] = struct{}{}
1093		}
1094	}
1095	var scopes []interface{}
1096	for uri := range scopeSet {
1097		scopes = append(scopes, fileURI(uri))
1098	}
1099	return scopes
1100}
1101
1102func contains(views []*View, view *View) bool {
1103	for _, v := range views {
1104		if v == view {
1105			return true
1106		}
1107	}
1108	return false
1109}
1110
1111func inVendor(uri span.URI) bool {
1112	toSlash := filepath.ToSlash(uri.Filename())
1113	if !strings.Contains(toSlash, "/vendor/") {
1114		return false
1115	}
1116	// Only packages in _subdirectories_ of /vendor/ are considered vendored
1117	// (/vendor/a/foo.go is vendored, /vendor/foo.go is not).
1118	split := strings.Split(toSlash, "/vendor/")
1119	if len(split) < 2 {
1120		return false
1121	}
1122	return strings.Contains(split[1], "/")
1123}
1124
1125func generationName(v *View, snapshotID uint64) string {
1126	return fmt.Sprintf("v%v/%v", v.id, snapshotID)
1127}
1128
1129func (s *snapshot) clone(ctx context.Context, changes map[span.URI]*fileChange, forceReloadMetadata bool) (*snapshot, bool) {
1130	// Track some important types of changes.
1131	var (
1132		vendorChanged  bool
1133		modulesChanged bool
1134	)
1135	newWorkspace, workspaceChanged := s.workspace.invalidate(ctx, changes)
1136
1137	s.mu.Lock()
1138	defer s.mu.Unlock()
1139
1140	newGen := s.view.session.cache.store.Generation(generationName(s.view, s.id+1))
1141	result := &snapshot{
1142		id:                s.id + 1,
1143		generation:        newGen,
1144		view:              s.view,
1145		builtin:           s.builtin,
1146		initializeOnce:    s.initializeOnce,
1147		initializedErr:    s.initializedErr,
1148		ids:               make(map[span.URI][]packageID),
1149		importedBy:        make(map[packageID][]packageID),
1150		metadata:          make(map[packageID]*metadata),
1151		packages:          make(map[packageKey]*packageHandle),
1152		actions:           make(map[actionKey]*actionHandle),
1153		files:             make(map[span.URI]source.VersionedFileHandle),
1154		goFiles:           make(map[parseKey]*parseGoHandle),
1155		workspacePackages: make(map[packageID]packagePath),
1156		unloadableFiles:   make(map[span.URI]struct{}),
1157		parseModHandles:   make(map[span.URI]*parseModHandle),
1158		modTidyHandles:    make(map[span.URI]*modTidyHandle),
1159		modUpgradeHandles: make(map[span.URI]*modUpgradeHandle),
1160		modWhyHandles:     make(map[span.URI]*modWhyHandle),
1161		workspace:         newWorkspace,
1162	}
1163
1164	if !workspaceChanged && s.workspaceDirHandle != nil {
1165		result.workspaceDirHandle = s.workspaceDirHandle
1166		newGen.Inherit(s.workspaceDirHandle)
1167	}
1168
1169	if s.builtin != nil {
1170		newGen.Inherit(s.builtin.handle)
1171	}
1172
1173	// Copy all of the FileHandles.
1174	for k, v := range s.files {
1175		result.files[k] = v
1176	}
1177
1178	// Copy the set of unloadable files.
1179	for k, v := range s.unloadableFiles {
1180		result.unloadableFiles[k] = v
1181	}
1182	// Copy all of the modHandles.
1183	for k, v := range s.parseModHandles {
1184		result.parseModHandles[k] = v
1185	}
1186
1187	for k, v := range s.goFiles {
1188		if _, ok := changes[k.file.URI]; ok {
1189			continue
1190		}
1191		newGen.Inherit(v.handle)
1192		newGen.Inherit(v.astCacheHandle)
1193		result.goFiles[k] = v
1194	}
1195
1196	// Copy all of the go.mod-related handles. They may be invalidated later,
1197	// so we inherit them at the end of the function.
1198	for k, v := range s.modTidyHandles {
1199		if _, ok := changes[k]; ok {
1200			continue
1201		}
1202		result.modTidyHandles[k] = v
1203	}
1204	for k, v := range s.modUpgradeHandles {
1205		if _, ok := changes[k]; ok {
1206			continue
1207		}
1208		result.modUpgradeHandles[k] = v
1209	}
1210	for k, v := range s.modWhyHandles {
1211		if _, ok := changes[k]; ok {
1212			continue
1213		}
1214		result.modWhyHandles[k] = v
1215	}
1216
1217	// directIDs keeps track of package IDs that have directly changed.
1218	// It maps id->invalidateMetadata.
1219	directIDs := map[packageID]bool{}
1220	// Invalidate all package metadata if the workspace module has changed.
1221	if workspaceChanged {
1222		for k := range s.metadata {
1223			directIDs[k] = true
1224		}
1225	}
1226
1227	changedPkgNames := map[packageID][]span.URI{}
1228	for uri, change := range changes {
1229		// Maybe reinitialize the view if we see a change in the vendor
1230		// directory.
1231		if inVendor(uri) {
1232			vendorChanged = true
1233		}
1234
1235		// The original FileHandle for this URI is cached on the snapshot.
1236		originalFH := s.files[uri]
1237
1238		// Check if the file's package name or imports have changed,
1239		// and if so, invalidate this file's packages' metadata.
1240		shouldInvalidateMetadata, pkgNameChanged := s.shouldInvalidateMetadata(ctx, result, originalFH, change.fileHandle)
1241		invalidateMetadata := forceReloadMetadata || shouldInvalidateMetadata
1242
1243		// Mark all of the package IDs containing the given file.
1244		// TODO: if the file has moved into a new package, we should invalidate that too.
1245		filePackageIDs := guessPackageIDsForURI(uri, s.ids)
1246		if pkgNameChanged {
1247			for _, id := range filePackageIDs {
1248				changedPkgNames[id] = append(changedPkgNames[id], uri)
1249			}
1250		}
1251		for _, id := range filePackageIDs {
1252			directIDs[id] = directIDs[id] || invalidateMetadata
1253		}
1254
1255		// Invalidate the previous modTidyHandle if any of the files have been
1256		// saved or if any of the metadata has been invalidated.
1257		if invalidateMetadata || fileWasSaved(originalFH, change.fileHandle) {
1258			// TODO(rstambler): Only delete mod handles for which the
1259			// withoutURI is relevant.
1260			for k := range s.modTidyHandles {
1261				delete(result.modTidyHandles, k)
1262			}
1263			for k := range s.modUpgradeHandles {
1264				delete(result.modUpgradeHandles, k)
1265			}
1266			for k := range s.modWhyHandles {
1267				delete(result.modWhyHandles, k)
1268			}
1269		}
1270		if isGoMod(uri) {
1271			// If the view's go.mod file's contents have changed, invalidate
1272			// the metadata for every known package in the snapshot.
1273			delete(result.parseModHandles, uri)
1274			if _, ok := result.workspace.getActiveModFiles()[uri]; ok {
1275				modulesChanged = true
1276			}
1277		}
1278		// Handle the invalidated file; it may have new contents or not exist.
1279		if !change.exists {
1280			delete(result.files, uri)
1281		} else {
1282			result.files[uri] = change.fileHandle
1283		}
1284		// Make sure to remove the changed file from the unloadable set.
1285		delete(result.unloadableFiles, uri)
1286	}
1287
1288	// Invalidate reverse dependencies too.
1289	// TODO(heschi): figure out the locking model and use transitiveReverseDeps?
1290	// transitiveIDs keeps track of transitive reverse dependencies.
1291	// If an ID is present in the map, invalidate its types.
1292	// If an ID's value is true, invalidate its metadata too.
1293	transitiveIDs := make(map[packageID]bool)
1294	var addRevDeps func(packageID, bool)
1295	addRevDeps = func(id packageID, invalidateMetadata bool) {
1296		current, seen := transitiveIDs[id]
1297		newInvalidateMetadata := current || invalidateMetadata
1298
1299		// If we've already seen this ID, and the value of invalidate
1300		// metadata has not changed, we can return early.
1301		if seen && current == newInvalidateMetadata {
1302			return
1303		}
1304		transitiveIDs[id] = newInvalidateMetadata
1305		for _, rid := range s.getImportedByLocked(id) {
1306			addRevDeps(rid, invalidateMetadata)
1307		}
1308	}
1309	for id, invalidateMetadata := range directIDs {
1310		addRevDeps(id, invalidateMetadata)
1311	}
1312
1313	// Copy the package type information.
1314	for k, v := range s.packages {
1315		if _, ok := transitiveIDs[k.id]; ok {
1316			continue
1317		}
1318		newGen.Inherit(v.handle)
1319		result.packages[k] = v
1320	}
1321	// Copy the package analysis information.
1322	for k, v := range s.actions {
1323		if _, ok := transitiveIDs[k.pkg.id]; ok {
1324			continue
1325		}
1326		newGen.Inherit(v.handle)
1327		result.actions[k] = v
1328	}
1329	// Copy the package metadata. We only need to invalidate packages directly
1330	// containing the affected file, and only if it changed in a relevant way.
1331	for k, v := range s.metadata {
1332		if invalidateMetadata, ok := transitiveIDs[k]; invalidateMetadata && ok {
1333			continue
1334		}
1335		result.metadata[k] = v
1336	}
1337	// Copy the URI to package ID mappings, skipping only those URIs whose
1338	// metadata will be reloaded in future calls to load.
1339copyIDs:
1340	for k, ids := range s.ids {
1341		for _, id := range ids {
1342			if invalidateMetadata, ok := transitiveIDs[id]; invalidateMetadata && ok {
1343				continue copyIDs
1344			}
1345		}
1346		result.ids[k] = ids
1347	}
1348	// Copy the set of initially loaded packages.
1349	for id, pkgPath := range s.workspacePackages {
1350		// Packages with the id "command-line-arguments" are generated by the
1351		// go command when the user is outside of GOPATH and outside of a
1352		// module. Do not cache them as workspace packages for longer than
1353		// necessary.
1354		if id == "command-line-arguments" {
1355			if invalidateMetadata, ok := transitiveIDs[id]; invalidateMetadata && ok {
1356				continue
1357			}
1358		}
1359
1360		// If all the files we know about in a package have been deleted,
1361		// the package is gone and we should no longer try to load it.
1362		if m := s.metadata[id]; m != nil {
1363			hasFiles := false
1364			for _, uri := range s.metadata[id].goFiles {
1365				if _, ok := result.files[uri]; ok {
1366					hasFiles = true
1367					break
1368				}
1369			}
1370			if !hasFiles {
1371				continue
1372			}
1373		}
1374
1375		// If the package name of a file in the package has changed, it's
1376		// possible that the package ID may no longer exist.
1377		if uris, ok := changedPkgNames[id]; ok && s.shouldDeleteWorkspacePackageID(id, uris) {
1378			continue
1379		}
1380
1381		result.workspacePackages[id] = pkgPath
1382	}
1383
1384	// Inherit all of the go.mod-related handles.
1385	for _, v := range result.modTidyHandles {
1386		newGen.Inherit(v.handle)
1387	}
1388	for _, v := range result.modUpgradeHandles {
1389		newGen.Inherit(v.handle)
1390	}
1391	for _, v := range result.modWhyHandles {
1392		newGen.Inherit(v.handle)
1393	}
1394	for _, v := range result.parseModHandles {
1395		newGen.Inherit(v.handle)
1396	}
1397	// Don't bother copying the importedBy graph,
1398	// as it changes each time we update metadata.
1399
1400	// If the snapshot's workspace mode has changed, the packages loaded using
1401	// the previous mode are no longer relevant, so clear them out.
1402	if s.workspaceMode() != result.workspaceMode() {
1403		result.workspacePackages = map[packageID]packagePath{}
1404	}
1405
1406	// The snapshot may need to be reinitialized.
1407	if modulesChanged || workspaceChanged || vendorChanged {
1408		if workspaceChanged || result.initializedErr != nil {
1409			result.initializeOnce = &sync.Once{}
1410		}
1411	}
1412	return result, workspaceChanged
1413}
1414
1415// shouldDeleteWorkspacePackageID reports whether the given package ID should
1416// be removed from the set of workspace packages. If one of the files in the
1417// package has changed package names, we check if it is the only file that
1418// *only* belongs to this package. For example, in the case of a test variant,
1419// confirm that it is the sole file constituting the test variant.
1420func (s *snapshot) shouldDeleteWorkspacePackageID(id packageID, changedPkgNames []span.URI) bool {
1421	m, ok := s.metadata[id]
1422	if !ok {
1423		return false
1424	}
1425	changedPkgName := func(uri span.URI) bool {
1426		for _, changed := range changedPkgNames {
1427			if uri == changed {
1428				return true
1429			}
1430		}
1431		return false
1432	}
1433	for _, uri := range m.compiledGoFiles {
1434		if changedPkgName(uri) {
1435			continue
1436		}
1437		// If there is at least one file remaining that belongs only to this
1438		// package, and its package name has not changed, we shouldn't delete
1439		// its package ID from the set of workspace packages.
1440		if ids := guessPackageIDsForURI(uri, s.ids); len(ids) == 1 && ids[0] == id {
1441			return false
1442		}
1443	}
1444	return true
1445}
1446
1447// guessPackageIDsForURI returns all packages related to uri. If we haven't
1448// seen this URI before, we guess based on files in the same directory. This
1449// is of course incorrect in build systems where packages are not organized by
1450// directory.
1451func guessPackageIDsForURI(uri span.URI, known map[span.URI][]packageID) []packageID {
1452	packages := known[uri]
1453	if len(packages) > 0 {
1454		// We've seen this file before.
1455		return packages
1456	}
1457	// This is a file we don't yet know about. Guess relevant packages by
1458	// considering files in the same directory.
1459
1460	// Cache of FileInfo to avoid unnecessary stats for multiple files in the
1461	// same directory.
1462	stats := make(map[string]struct {
1463		os.FileInfo
1464		error
1465	})
1466	getInfo := func(dir string) (os.FileInfo, error) {
1467		if res, ok := stats[dir]; ok {
1468			return res.FileInfo, res.error
1469		}
1470		fi, err := os.Stat(dir)
1471		stats[dir] = struct {
1472			os.FileInfo
1473			error
1474		}{fi, err}
1475		return fi, err
1476	}
1477	dir := filepath.Dir(uri.Filename())
1478	fi, err := getInfo(dir)
1479	if err != nil {
1480		return nil
1481	}
1482
1483	// Aggregate all possibly relevant package IDs.
1484	var found []packageID
1485	for knownURI, ids := range known {
1486		knownDir := filepath.Dir(knownURI.Filename())
1487		knownFI, err := getInfo(knownDir)
1488		if err != nil {
1489			continue
1490		}
1491		if os.SameFile(fi, knownFI) {
1492			found = append(found, ids...)
1493		}
1494	}
1495	return found
1496}
1497
1498// fileWasSaved reports whether the FileHandle passed in has been saved. It
1499// accomplishes this by checking to see if the original and current FileHandles
1500// are both overlays, and if the current FileHandle is saved while the original
1501// FileHandle was not saved.
1502func fileWasSaved(originalFH, currentFH source.FileHandle) bool {
1503	c, ok := currentFH.(*overlay)
1504	if !ok || c == nil {
1505		return true
1506	}
1507	o, ok := originalFH.(*overlay)
1508	if !ok || o == nil {
1509		return c.saved
1510	}
1511	return !o.saved && c.saved
1512}
1513
1514// shouldInvalidateMetadata reparses a file's package and import declarations to
1515// determine if the file requires a metadata reload.
1516func (s *snapshot) shouldInvalidateMetadata(ctx context.Context, newSnapshot *snapshot, originalFH, currentFH source.FileHandle) (invalidate, pkgNameChanged bool) {
1517	if originalFH == nil {
1518		return true, false
1519	}
1520	// If the file hasn't changed, there's no need to reload.
1521	if originalFH.FileIdentity() == currentFH.FileIdentity() {
1522		return false, false
1523	}
1524	// If a go.mod in the workspace has been changed, invalidate metadata.
1525	if kind := originalFH.Kind(); kind == source.Mod {
1526		if !source.InDir(filepath.Dir(s.view.rootURI.Filename()), originalFH.URI().Filename()) {
1527			return false, false
1528		}
1529		return currentFH.Saved(), false
1530	}
1531	// Get the original and current parsed files in order to check package name
1532	// and imports. Use the new snapshot to parse to avoid modifying the
1533	// current snapshot.
1534	original, originalErr := newSnapshot.ParseGo(ctx, originalFH, source.ParseHeader)
1535	current, currentErr := newSnapshot.ParseGo(ctx, currentFH, source.ParseHeader)
1536	if originalErr != nil || currentErr != nil {
1537		return (originalErr == nil) != (currentErr == nil), false
1538	}
1539	// Check if the package's metadata has changed. The cases handled are:
1540	//    1. A package's name has changed
1541	//    2. A file's imports have changed
1542	if original.File.Name.Name != current.File.Name.Name {
1543		return true, true
1544	}
1545	importSet := make(map[string]struct{})
1546	for _, importSpec := range original.File.Imports {
1547		importSet[importSpec.Path.Value] = struct{}{}
1548	}
1549	// If any of the current imports were not in the original imports.
1550	for _, importSpec := range current.File.Imports {
1551		if _, ok := importSet[importSpec.Path.Value]; ok {
1552			continue
1553		}
1554		// If the import path is obviously not valid, we can skip reloading
1555		// metadata. For now, valid means properly quoted and without a
1556		// terminal slash.
1557		path, err := strconv.Unquote(importSpec.Path.Value)
1558		if err != nil {
1559			continue
1560		}
1561		if path == "" {
1562			continue
1563		}
1564		if path[len(path)-1] == '/' {
1565			continue
1566		}
1567		return true, false
1568	}
1569	return false, false
1570}
1571
1572func (s *snapshot) BuiltinPackage(ctx context.Context) (*source.BuiltinPackage, error) {
1573	s.AwaitInitialized(ctx)
1574
1575	if s.builtin == nil {
1576		return nil, errors.Errorf("no builtin package for view %s", s.view.name)
1577	}
1578	d, err := s.builtin.handle.Get(ctx, s.generation, s)
1579	if err != nil {
1580		return nil, err
1581	}
1582	data := d.(*builtinPackageData)
1583	return data.parsed, data.err
1584}
1585
1586func (s *snapshot) buildBuiltinPackage(ctx context.Context, goFiles []string) error {
1587	if len(goFiles) != 1 {
1588		return errors.Errorf("only expected 1 file, got %v", len(goFiles))
1589	}
1590	uri := span.URIFromPath(goFiles[0])
1591
1592	// Get the FileHandle through the cache to avoid adding it to the snapshot
1593	// and to get the file content from disk.
1594	fh, err := s.view.session.cache.getFile(ctx, uri)
1595	if err != nil {
1596		return err
1597	}
1598	h := s.generation.Bind(fh.FileIdentity(), func(ctx context.Context, arg memoize.Arg) interface{} {
1599		snapshot := arg.(*snapshot)
1600
1601		pgh := snapshot.parseGoHandle(ctx, fh, source.ParseFull)
1602		pgf, _, err := snapshot.parseGo(ctx, pgh)
1603		if err != nil {
1604			return &builtinPackageData{err: err}
1605		}
1606		pkg, err := ast.NewPackage(snapshot.view.session.cache.fset, map[string]*ast.File{
1607			pgf.URI.Filename(): pgf.File,
1608		}, nil, nil)
1609		if err != nil {
1610			return &builtinPackageData{err: err}
1611		}
1612		return &builtinPackageData{
1613			parsed: &source.BuiltinPackage{
1614				ParsedFile: pgf,
1615				Package:    pkg,
1616			},
1617		}
1618	}, nil)
1619	s.builtin = &builtinPackageHandle{handle: h}
1620	return nil
1621}
1622
1623// BuildGoplsMod generates a go.mod file for all modules in the workspace. It
1624// bypasses any existing gopls.mod.
1625func BuildGoplsMod(ctx context.Context, root span.URI, fs source.FileSource) (*modfile.File, error) {
1626	allModules, err := findModules(ctx, root, 0)
1627	if err != nil {
1628		return nil, err
1629	}
1630	return buildWorkspaceModFile(ctx, allModules, fs)
1631}
1632
1633// TODO(rfindley): move this to workspacemodule.go
1634func buildWorkspaceModFile(ctx context.Context, modFiles map[span.URI]struct{}, fs source.FileSource) (*modfile.File, error) {
1635	file := &modfile.File{}
1636	file.AddModuleStmt("gopls-workspace")
1637
1638	paths := make(map[string]span.URI)
1639	for modURI := range modFiles {
1640		fh, err := fs.GetFile(ctx, modURI)
1641		if err != nil {
1642			return nil, err
1643		}
1644		content, err := fh.Read()
1645		if err != nil {
1646			return nil, err
1647		}
1648		parsed, err := modfile.Parse(fh.URI().Filename(), content, nil)
1649		if err != nil {
1650			return nil, err
1651		}
1652		if file == nil || parsed.Module == nil {
1653			return nil, fmt.Errorf("no module declaration for %s", modURI)
1654		}
1655		path := parsed.Module.Mod.Path
1656		paths[path] = modURI
1657		// If the module's path includes a major version, we expect it to have
1658		// a matching major version.
1659		_, majorVersion, _ := module.SplitPathVersion(path)
1660		if majorVersion == "" {
1661			majorVersion = "/v0"
1662		}
1663		majorVersion = strings.TrimLeft(majorVersion, "/.") // handle gopkg.in versions
1664		file.AddNewRequire(path, source.WorkspaceModuleVersion(majorVersion), false)
1665		if err := file.AddReplace(path, "", dirURI(modURI).Filename(), ""); err != nil {
1666			return nil, err
1667		}
1668	}
1669	// Go back through all of the modules to handle any of their replace
1670	// statements.
1671	for modURI := range modFiles {
1672		fh, err := fs.GetFile(ctx, modURI)
1673		if err != nil {
1674			return nil, err
1675		}
1676		content, err := fh.Read()
1677		if err != nil {
1678			return nil, err
1679		}
1680		parsed, err := modfile.Parse(fh.URI().Filename(), content, nil)
1681		if err != nil {
1682			return nil, err
1683		}
1684		// If any of the workspace modules have replace directives, they need
1685		// to be reflected in the workspace module.
1686		for _, rep := range parsed.Replace {
1687			// Don't replace any modules that are in our workspace--we should
1688			// always use the version in the workspace.
1689			if _, ok := paths[rep.Old.Path]; ok {
1690				continue
1691			}
1692			newPath := rep.New.Path
1693			newVersion := rep.New.Version
1694			// If a replace points to a module in the workspace, make sure we
1695			// direct it to version of the module in the workspace.
1696			if m, ok := paths[rep.New.Path]; ok {
1697				newPath = dirURI(m).Filename()
1698				newVersion = ""
1699			} else if rep.New.Version == "" && !filepath.IsAbs(rep.New.Path) {
1700				// Make any relative paths absolute.
1701				newPath = filepath.Join(dirURI(modURI).Filename(), rep.New.Path)
1702			}
1703			if err := file.AddReplace(rep.Old.Path, rep.Old.Version, newPath, newVersion); err != nil {
1704				return nil, err
1705			}
1706		}
1707	}
1708	return file, nil
1709}
1710