blob: d709854ce34fe1021a3e4bb833c9530c44678a88 [file] [log] [blame]
Robert Findleyb15dac22022-08-30 14:40:12 -04001// 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 "errors"
11 "fmt"
12 "go/ast"
Rob Findley1c9fe3f2023-05-11 14:33:05 -040013 "go/build/constraint"
Robert Findleyb15dac22022-08-30 14:40:12 -040014 "go/token"
15 "go/types"
16 "io"
17 "io/ioutil"
18 "log"
19 "os"
20 "path/filepath"
21 "regexp"
22 "runtime"
23 "sort"
24 "strconv"
25 "strings"
26 "sync"
27 "sync/atomic"
28 "unsafe"
29
Robert Findleyb15dac22022-08-30 14:40:12 -040030 "golang.org/x/sync/errgroup"
31 "golang.org/x/tools/go/packages"
Robert Findley21d22562023-02-21 12:26:27 -050032 "golang.org/x/tools/go/types/objectpath"
Alan Donovan4baa3dc2023-04-25 10:21:06 -040033 "golang.org/x/tools/gopls/internal/bug"
Rob Findleya13793e2023-05-12 14:22:10 -040034 "golang.org/x/tools/gopls/internal/lsp/command"
Robert Findleyacaae982023-03-16 12:21:36 -040035 "golang.org/x/tools/gopls/internal/lsp/filecache"
Robert Findley21d22562023-02-21 12:26:27 -050036 "golang.org/x/tools/gopls/internal/lsp/protocol"
Robert Findleyb15dac22022-08-30 14:40:12 -040037 "golang.org/x/tools/gopls/internal/lsp/source"
Robert Findley21d22562023-02-21 12:26:27 -050038 "golang.org/x/tools/gopls/internal/lsp/source/methodsets"
Robert Findley1b2d1bd2023-03-16 18:52:13 -040039 "golang.org/x/tools/gopls/internal/lsp/source/typerefs"
Robert Findley21d22562023-02-21 12:26:27 -050040 "golang.org/x/tools/gopls/internal/lsp/source/xrefs"
Alan Donovan26a95e62022-10-07 10:40:32 -040041 "golang.org/x/tools/gopls/internal/span"
Robert Findley9c639112022-09-28 12:03:51 -040042 "golang.org/x/tools/internal/event"
43 "golang.org/x/tools/internal/event/tag"
44 "golang.org/x/tools/internal/gocommand"
Robert Findleyb15dac22022-08-30 14:40:12 -040045 "golang.org/x/tools/internal/memoize"
46 "golang.org/x/tools/internal/packagesinternal"
47 "golang.org/x/tools/internal/persistent"
Robert Findleyb15dac22022-08-30 14:40:12 -040048 "golang.org/x/tools/internal/typesinternal"
49)
50
51type snapshot struct {
Robert Findley0c71b562022-11-14 11:58:07 -050052 sequenceID uint64
53 globalID source.GlobalSnapshotID
Robert Findleyc4c6aa62023-01-19 20:24:55 -050054
55 // TODO(rfindley): the snapshot holding a reference to the view poses
56 // lifecycle problems: a view may be shut down and waiting for work
57 // associated with this snapshot to complete. While most accesses of the view
58 // are benign (options or workspace information), this is not formalized and
59 // it is wrong for the snapshot to use a shutdown view.
60 //
61 // Fix this by passing options and workspace information to the snapshot,
62 // both of which should be immutable for the snapshot.
63 view *View
Robert Findleyb15dac22022-08-30 14:40:12 -040064
65 cancel func()
66 backgroundCtx context.Context
67
68 store *memoize.Store // cache of handles shared by all snapshots
69
70 refcount sync.WaitGroup // number of references
71 destroyedBy *string // atomically set to non-nil in Destroy once refcount = 0
72
73 // initialized reports whether the snapshot has been initialized. Concurrent
74 // initialization is guarded by the view.initializationSema. Each snapshot is
75 // initialized at most once: concurrent initialization is guarded by
76 // view.initializationSema.
77 initialized bool
78 // initializedErr holds the last error resulting from initialization. If
cui fliter165099b2023-04-27 20:32:55 +080079 // initialization fails, we only retry when the workspace modules change,
Robert Findleyb15dac22022-08-30 14:40:12 -040080 // to avoid too many go/packages calls.
81 initializedErr *source.CriticalError
82
83 // mu guards all of the maps in the snapshot, as well as the builtin URI.
84 mu sync.Mutex
85
Robert Findley4b112032023-03-28 20:06:14 -040086 // builtin is the location of builtin.go in GOROOT.
87 //
88 // TODO(rfindley): would it make more sense to eagerly parse builtin, and
89 // instead store a *ParsedGoFile here?
Robert Findleyb15dac22022-08-30 14:40:12 -040090 builtin span.URI
91
92 // meta holds loaded metadata.
93 //
94 // meta is guarded by mu, but the metadataGraph itself is immutable.
95 // TODO(rfindley): in many places we hold mu while operating on meta, even
96 // though we only need to hold mu while reading the pointer.
97 meta *metadataGraph
98
99 // files maps file URIs to their corresponding FileHandles.
100 // It may invalidated when a file's content changes.
101 files filesMap
102
Robert Findleyb15dac22022-08-30 14:40:12 -0400103 // symbolizeHandles maps each file URI to a handle for the future
104 // result of computing the symbols declared in that file.
105 symbolizeHandles *persistent.Map // from span.URI to *memoize.Promise[symbolizeResult]
106
107 // packages maps a packageKey to a *packageHandle.
108 // It may be invalidated when a file's content changes.
109 //
110 // Invariants to preserve:
Alan Donovanff22fab2022-11-18 14:47:36 -0500111 // - packages.Get(id).meta == meta.metadata[id] for all ids
Robert Findleyb15dac22022-08-30 14:40:12 -0400112 // - if a package is in packages, then all of its dependencies should also
113 // be in packages, unless there is a missing import
Robert Findley21d22562023-02-21 12:26:27 -0500114 packages *persistent.Map // from packageID to *packageHandle
Robert Findleyb15dac22022-08-30 14:40:12 -0400115
Robert Findley21d22562023-02-21 12:26:27 -0500116 // activePackages maps a package ID to a memoized active package, or nil if
117 // the package is known not to be open.
118 //
119 // IDs not contained in the map are not known to be open or not open.
120 activePackages *persistent.Map // from packageID to *Package
Robert Findleyb15dac22022-08-30 14:40:12 -0400121
Robert Findleyb15dac22022-08-30 14:40:12 -0400122 // workspacePackages contains the workspace's packages, which are loaded
Alan Donovand59a28f2023-04-21 16:40:05 -0400123 // when the view is created. It contains no intermediate test variants.
Robert Findleyb15dac22022-08-30 14:40:12 -0400124 workspacePackages map[PackageID]PackagePath
125
126 // shouldLoad tracks packages that need to be reloaded, mapping a PackageID
127 // to the package paths that should be used to reload it
128 //
129 // When we try to load a package, we clear it from the shouldLoad map
130 // regardless of whether the load succeeded, to prevent endless loads.
131 shouldLoad map[PackageID][]PackagePath
132
133 // unloadableFiles keeps track of files that we've failed to load.
134 unloadableFiles map[span.URI]struct{}
135
Robert Findley21d22562023-02-21 12:26:27 -0500136 // TODO(rfindley): rename the handles below to "promises". A promise is
137 // different from a handle (we mutate the package handle.)
138
Robert Findleyb15dac22022-08-30 14:40:12 -0400139 // parseModHandles keeps track of any parseModHandles for the snapshot.
140 // The handles need not refer to only the view's go.mod file.
141 parseModHandles *persistent.Map // from span.URI to *memoize.Promise[parseModResult]
142
143 // parseWorkHandles keeps track of any parseWorkHandles for the snapshot.
144 // The handles need not refer to only the view's go.work file.
145 parseWorkHandles *persistent.Map // from span.URI to *memoize.Promise[parseWorkResult]
146
147 // Preserve go.mod-related handles to avoid garbage-collecting the results
148 // of various calls to the go command. The handles need not refer to only
149 // the view's go.mod file.
150 modTidyHandles *persistent.Map // from span.URI to *memoize.Promise[modTidyResult]
151 modWhyHandles *persistent.Map // from span.URI to *memoize.Promise[modWhyResult]
Robert Findley7cda55e2022-11-22 12:09:11 -0500152 modVulnHandles *persistent.Map // from span.URI to *memoize.Promise[modVulnResult]
Robert Findleyb15dac22022-08-30 14:40:12 -0400153
Alan Donovan2ec42992023-05-18 13:45:43 -0400154 // knownSubdirs is the set of subdirectory URIs in the workspace,
155 // used to create glob patterns for file watching.
156 knownSubdirs knownDirsSet
157 knownSubdirsCache map[string]struct{} // memo of knownSubdirs as a set of filenames
Robert Findleyb15dac22022-08-30 14:40:12 -0400158 // unprocessedSubdirChanges are any changes that might affect the set of
159 // subdirectories in the workspace. They are not reflected to knownSubdirs
160 // during the snapshot cloning step as it can slow down cloning.
161 unprocessedSubdirChanges []*fileChange
Robert Findley80879112022-12-16 09:11:12 -0500162
163 // workspaceModFiles holds the set of mod files active in this snapshot.
164 //
165 // This is either empty, a single entry for the workspace go.mod file, or the
166 // set of mod files used by the workspace go.work file.
167 //
168 // This set is immutable inside the snapshot, and therefore is not guarded by mu.
169 workspaceModFiles map[span.URI]struct{}
170 workspaceModFilesErr error // error encountered computing workspaceModFiles
Robert Findley488ba862023-03-23 12:18:06 -0400171
172 // importGraph holds a shared import graph to use for type-checking. Adding
173 // more packages to this import graph can speed up type checking, at the
174 // expense of in-use memory.
175 //
176 // See getImportGraph for additional documentation.
177 importGraphDone chan struct{} // closed when importGraph is set; may be nil
178 importGraph *importGraph // copied from preceding snapshot and re-evaluated
Robert Findley1b2d1bd2023-03-16 18:52:13 -0400179
180 // pkgIndex is an index of package IDs, for efficient storage of typerefs.
181 pkgIndex *typerefs.PackageIndex
Rob Findley787e7202023-05-09 18:57:05 -0400182
183 // Only compute module prefixes once, as they are used with high frequency to
184 // detect ignored files.
185 ignoreFilterOnce sync.Once
186 ignoreFilter *ignoreFilter
Rob Findleya13793e2023-05-12 14:22:10 -0400187
188 // If non-nil, the result of computing orphaned file diagnostics.
189 //
190 // Only the field, not the map itself, is guarded by the mutex. The map must
191 // not be mutated.
192 //
193 // Used to save work across diagnostics+code action passes.
194 // TODO(rfindley): refactor all of this so there's no need to re-evaluate
195 // diagnostics during code-action.
196 orphanedFileDiagnostics map[span.URI]*source.Diagnostic
Robert Findleyb15dac22022-08-30 14:40:12 -0400197}
198
Robert Findley0c71b562022-11-14 11:58:07 -0500199var globalSnapshotID uint64
200
201func nextSnapshotID() source.GlobalSnapshotID {
202 return source.GlobalSnapshotID(atomic.AddUint64(&globalSnapshotID, 1))
203}
204
Robert Findleyb15dac22022-08-30 14:40:12 -0400205var _ memoize.RefCounted = (*snapshot)(nil) // snapshots are reference-counted
206
207// Acquire prevents the snapshot from being destroyed until the returned function is called.
208//
209// (s.Acquire().release() could instead be expressed as a pair of
210// method calls s.IncRef(); s.DecRef(). The latter has the advantage
211// that the DecRefs are fungible and don't require holding anything in
212// addition to the refcounted object s, but paradoxically that is also
213// an advantage of the current approach, which forces the caller to
214// consider the release function at every stage, making a reference
215// leak more obvious.)
216func (s *snapshot) Acquire() func() {
217 type uP = unsafe.Pointer
218 if destroyedBy := atomic.LoadPointer((*uP)(uP(&s.destroyedBy))); destroyedBy != nil {
Robert Findley0c71b562022-11-14 11:58:07 -0500219 log.Panicf("%d: acquire() after Destroy(%q)", s.globalID, *(*string)(destroyedBy))
Robert Findleyb15dac22022-08-30 14:40:12 -0400220 }
221 s.refcount.Add(1)
222 return s.refcount.Done
223}
224
225func (s *snapshot) awaitPromise(ctx context.Context, p *memoize.Promise) (interface{}, error) {
226 return p.Get(ctx, s)
227}
228
229// destroy waits for all leases on the snapshot to expire then releases
230// any resources (reference counts and files) associated with it.
231// Snapshots being destroyed can be awaited using v.destroyWG.
232//
233// TODO(adonovan): move this logic into the release function returned
234// by Acquire when the reference count becomes zero. (This would cost
235// us the destroyedBy debug info, unless we add it to the signature of
236// memoize.RefCounted.Acquire.)
237//
238// The destroyedBy argument is used for debugging.
239//
240// v.snapshotMu must be held while calling this function, in order to preserve
cui fliter165099b2023-04-27 20:32:55 +0800241// the invariants described by the docstring for v.snapshot.
Robert Findleyb15dac22022-08-30 14:40:12 -0400242func (v *View) destroy(s *snapshot, destroyedBy string) {
243 v.snapshotWG.Add(1)
244 go func() {
245 defer v.snapshotWG.Done()
246 s.destroy(destroyedBy)
247 }()
248}
249
250func (s *snapshot) destroy(destroyedBy string) {
251 // Wait for all leases to end before commencing destruction.
252 s.refcount.Wait()
253
254 // Report bad state as a debugging aid.
255 // Not foolproof: another thread could acquire() at this moment.
256 type uP = unsafe.Pointer // looking forward to generics...
257 if old := atomic.SwapPointer((*uP)(uP(&s.destroyedBy)), uP(&destroyedBy)); old != nil {
Robert Findley0c71b562022-11-14 11:58:07 -0500258 log.Panicf("%d: Destroy(%q) after Destroy(%q)", s.globalID, destroyedBy, *(*string)(old))
Robert Findleyb15dac22022-08-30 14:40:12 -0400259 }
260
261 s.packages.Destroy()
Robert Findley21d22562023-02-21 12:26:27 -0500262 s.activePackages.Destroy()
Robert Findleyb15dac22022-08-30 14:40:12 -0400263 s.files.Destroy()
Robert Findleyb15dac22022-08-30 14:40:12 -0400264 s.knownSubdirs.Destroy()
265 s.symbolizeHandles.Destroy()
266 s.parseModHandles.Destroy()
267 s.parseWorkHandles.Destroy()
268 s.modTidyHandles.Destroy()
Robert Findley7cda55e2022-11-22 12:09:11 -0500269 s.modVulnHandles.Destroy()
Robert Findleyb15dac22022-08-30 14:40:12 -0400270 s.modWhyHandles.Destroy()
Robert Findleyb15dac22022-08-30 14:40:12 -0400271}
272
Robert Findley0c71b562022-11-14 11:58:07 -0500273func (s *snapshot) SequenceID() uint64 {
274 return s.sequenceID
275}
276
277func (s *snapshot) GlobalID() source.GlobalSnapshotID {
278 return s.globalID
Robert Findleyb15dac22022-08-30 14:40:12 -0400279}
280
281func (s *snapshot) View() source.View {
282 return s.view
283}
284
285func (s *snapshot) BackgroundContext() context.Context {
286 return s.backgroundCtx
287}
288
Robert Findleyb15dac22022-08-30 14:40:12 -0400289func (s *snapshot) ModFiles() []span.URI {
290 var uris []span.URI
Robert Findley80879112022-12-16 09:11:12 -0500291 for modURI := range s.workspaceModFiles {
Robert Findleyb15dac22022-08-30 14:40:12 -0400292 uris = append(uris, modURI)
293 }
294 return uris
295}
296
297func (s *snapshot) WorkFile() span.URI {
Rob Findleya13793e2023-05-12 14:22:10 -0400298 gowork, _ := s.view.GOWORK()
299 return gowork
Robert Findleyb15dac22022-08-30 14:40:12 -0400300}
301
Robert Findleya7f033a2023-01-19 18:12:18 -0500302func (s *snapshot) Templates() map[span.URI]source.FileHandle {
Robert Findleyb15dac22022-08-30 14:40:12 -0400303 s.mu.Lock()
304 defer s.mu.Unlock()
305
Robert Findleya7f033a2023-01-19 18:12:18 -0500306 tmpls := map[span.URI]source.FileHandle{}
307 s.files.Range(func(k span.URI, fh source.FileHandle) {
Robert Findleyb15dac22022-08-30 14:40:12 -0400308 if s.view.FileKind(fh) == source.Tmpl {
309 tmpls[k] = fh
310 }
311 })
312 return tmpls
313}
314
Rob Findleye5c8d4d2023-05-15 19:50:02 -0400315func (s *snapshot) validBuildConfiguration() bool {
Robert Findleyb15dac22022-08-30 14:40:12 -0400316 // Since we only really understand the `go` command, if the user has a
317 // different GOPACKAGESDRIVER, assume that their configuration is valid.
318 if s.view.hasGopackagesDriver {
319 return true
320 }
Rob Findley3d53c2d2023-05-15 20:03:00 -0400321
Robert Findleyb15dac22022-08-30 14:40:12 -0400322 // Check if the user is working within a module or if we have found
323 // multiple modules in the workspace.
Robert Findley80879112022-12-16 09:11:12 -0500324 if len(s.workspaceModFiles) > 0 {
Robert Findleyb15dac22022-08-30 14:40:12 -0400325 return true
326 }
Rob Findley3d53c2d2023-05-15 20:03:00 -0400327
Alan Donovan18f76ec2022-12-08 09:48:21 -0500328 // TODO(rfindley): this should probably be subject to "if GO111MODULES = off {...}".
Rob Findley3d53c2d2023-05-15 20:03:00 -0400329 if s.view.inGOPATH {
Robert Findley80879112022-12-16 09:11:12 -0500330 return true
Robert Findley80879112022-12-16 09:11:12 -0500331 }
Rob Findley3d53c2d2023-05-15 20:03:00 -0400332
333 return false
Robert Findley80879112022-12-16 09:11:12 -0500334}
335
Robert Findleyb15dac22022-08-30 14:40:12 -0400336// workspaceMode describes the way in which the snapshot's workspace should
337// be loaded.
Robert Findley80879112022-12-16 09:11:12 -0500338//
339// TODO(rfindley): remove this, in favor of specific methods.
Robert Findleyb15dac22022-08-30 14:40:12 -0400340func (s *snapshot) workspaceMode() workspaceMode {
341 var mode workspaceMode
342
343 // If the view has an invalid configuration, don't build the workspace
344 // module.
Rob Findleye5c8d4d2023-05-15 19:50:02 -0400345 validBuildConfiguration := s.validBuildConfiguration()
Robert Findleyb15dac22022-08-30 14:40:12 -0400346 if !validBuildConfiguration {
347 return mode
348 }
349 // If the view is not in a module and contains no modules, but still has a
350 // valid workspace configuration, do not create the workspace module.
351 // It could be using GOPATH or a different build system entirely.
Robert Findley80879112022-12-16 09:11:12 -0500352 if len(s.workspaceModFiles) == 0 && validBuildConfiguration {
Robert Findleyb15dac22022-08-30 14:40:12 -0400353 return mode
354 }
355 mode |= moduleMode
356 options := s.view.Options()
Robert Findley16b3bf82023-01-06 11:35:54 -0500357 if options.TempModfile {
Robert Findleyb15dac22022-08-30 14:40:12 -0400358 mode |= tempModfile
359 }
360 return mode
361}
362
363// config returns the configuration used for the snapshot's interaction with
364// the go/packages API. It uses the given working directory.
365//
366// TODO(rstambler): go/packages requires that we do not provide overlays for
367// multiple modules in on config, so buildOverlay needs to filter overlays by
368// module.
369func (s *snapshot) config(ctx context.Context, inv *gocommand.Invocation) *packages.Config {
370 s.view.optionsMu.Lock()
371 verboseOutput := s.view.options.VerboseOutput
372 s.view.optionsMu.Unlock()
373
374 cfg := &packages.Config{
375 Context: ctx,
376 Dir: inv.WorkingDir,
377 Env: inv.Env,
378 BuildFlags: inv.BuildFlags,
379 Mode: packages.NeedName |
380 packages.NeedFiles |
381 packages.NeedCompiledGoFiles |
382 packages.NeedImports |
383 packages.NeedDeps |
384 packages.NeedTypesSizes |
385 packages.NeedModule |
Frank Viernau3ec30bd2023-02-22 21:48:57 +0000386 packages.NeedEmbedFiles |
Robert Findleyb15dac22022-08-30 14:40:12 -0400387 packages.LoadMode(packagesinternal.DepsErrors) |
388 packages.LoadMode(packagesinternal.ForTest),
Alan Donovan6f993662022-11-10 15:07:47 -0500389 Fset: nil, // we do our own parsing
Robert Findleyb15dac22022-08-30 14:40:12 -0400390 Overlay: s.buildOverlay(),
391 ParseFile: func(*token.FileSet, string, []byte) (*ast.File, error) {
392 panic("go/packages must not be used to parse files")
393 },
394 Logf: func(format string, args ...interface{}) {
395 if verboseOutput {
396 event.Log(ctx, fmt.Sprintf(format, args...))
397 }
398 },
399 Tests: true,
400 }
401 packagesinternal.SetModFile(cfg, inv.ModFile)
402 packagesinternal.SetModFlag(cfg, inv.ModFlag)
403 // We want to type check cgo code if go/types supports it.
404 if typesinternal.SetUsesCgo(&types.Config{}) {
405 cfg.Mode |= packages.LoadMode(packagesinternal.TypecheckCgo)
406 }
Robert Findleye8a70a52022-11-28 14:58:06 -0500407 packagesinternal.SetGoCmdRunner(cfg, s.view.gocmdRunner)
Robert Findleyb15dac22022-08-30 14:40:12 -0400408 return cfg
409}
410
411func (s *snapshot) RunGoCommandDirect(ctx context.Context, mode source.InvocationFlags, inv *gocommand.Invocation) (*bytes.Buffer, error) {
412 _, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv)
413 if err != nil {
414 return nil, err
415 }
416 defer cleanup()
417
Robert Findleye8a70a52022-11-28 14:58:06 -0500418 return s.view.gocmdRunner.Run(ctx, *inv)
Robert Findleyb15dac22022-08-30 14:40:12 -0400419}
420
421func (s *snapshot) RunGoCommandPiped(ctx context.Context, mode source.InvocationFlags, inv *gocommand.Invocation, stdout, stderr io.Writer) error {
422 _, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv)
423 if err != nil {
424 return err
425 }
426 defer cleanup()
Robert Findleye8a70a52022-11-28 14:58:06 -0500427 return s.view.gocmdRunner.RunPiped(ctx, *inv, stdout, stderr)
Robert Findleyb15dac22022-08-30 14:40:12 -0400428}
429
430func (s *snapshot) RunGoCommands(ctx context.Context, allowNetwork bool, wd string, run func(invoke func(...string) (*bytes.Buffer, error)) error) (bool, []byte, []byte, error) {
431 var flags source.InvocationFlags
432 if s.workspaceMode()&tempModfile != 0 {
433 flags = source.WriteTemporaryModFile
434 } else {
435 flags = source.Normal
436 }
437 if allowNetwork {
438 flags |= source.AllowNetwork
439 }
440 tmpURI, inv, cleanup, err := s.goCommandInvocation(ctx, flags, &gocommand.Invocation{WorkingDir: wd})
441 if err != nil {
442 return false, nil, nil, err
443 }
444 defer cleanup()
445 invoke := func(args ...string) (*bytes.Buffer, error) {
446 inv.Verb = args[0]
447 inv.Args = args[1:]
Robert Findleye8a70a52022-11-28 14:58:06 -0500448 return s.view.gocmdRunner.Run(ctx, *inv)
Robert Findleyb15dac22022-08-30 14:40:12 -0400449 }
450 if err := run(invoke); err != nil {
451 return false, nil, nil, err
452 }
453 if flags.Mode() != source.WriteTemporaryModFile {
454 return false, nil, nil, nil
455 }
456 var modBytes, sumBytes []byte
457 modBytes, err = ioutil.ReadFile(tmpURI.Filename())
458 if err != nil && !os.IsNotExist(err) {
459 return false, nil, nil, err
460 }
461 sumBytes, err = ioutil.ReadFile(strings.TrimSuffix(tmpURI.Filename(), ".mod") + ".sum")
462 if err != nil && !os.IsNotExist(err) {
463 return false, nil, nil, err
464 }
465 return true, modBytes, sumBytes, nil
466}
467
468// goCommandInvocation populates inv with configuration for running go commands on the snapshot.
469//
470// TODO(rfindley): refactor this function to compose the required configuration
471// explicitly, rather than implicitly deriving it from flags and inv.
472//
473// TODO(adonovan): simplify cleanup mechanism. It's hard to see, but
474// it used only after call to tempModFile. Clarify that it is only
475// non-nil on success.
476func (s *snapshot) goCommandInvocation(ctx context.Context, flags source.InvocationFlags, inv *gocommand.Invocation) (tmpURI span.URI, updatedInv *gocommand.Invocation, cleanup func(), err error) {
477 s.view.optionsMu.Lock()
478 allowModfileModificationOption := s.view.options.AllowModfileModifications
479 allowNetworkOption := s.view.options.AllowImplicitNetworkAccess
480
481 // TODO(rfindley): this is very hard to follow, and may not even be doing the
482 // right thing: should inv.Env really trample view.options? Do we ever invoke
483 // this with a non-empty inv.Env?
484 //
485 // We should refactor to make it clearer that the correct env is being used.
Robert Findleyc7ed4b32022-11-16 13:52:25 -0500486 inv.Env = append(append(append(os.Environ(), s.view.options.EnvSlice()...), inv.Env...), "GO111MODULE="+s.view.GO111MODULE())
Robert Findleyb15dac22022-08-30 14:40:12 -0400487 inv.BuildFlags = append([]string{}, s.view.options.BuildFlags...)
488 s.view.optionsMu.Unlock()
489 cleanup = func() {} // fallback
490
491 // All logic below is for module mode.
492 if s.workspaceMode()&moduleMode == 0 {
493 return "", inv, cleanup, nil
494 }
495
496 mode, allowNetwork := flags.Mode(), flags.AllowNetwork()
497 if !allowNetwork && !allowNetworkOption {
498 inv.Env = append(inv.Env, "GOPROXY=off")
499 }
500
501 // What follows is rather complicated logic for how to actually run the go
502 // command. A word of warning: this is the result of various incremental
503 // features added to gopls, and varying behavior of the Go command across Go
504 // versions. It can surely be cleaned up significantly, but tread carefully.
505 //
506 // Roughly speaking we need to resolve four things:
507 // - the working directory.
508 // - the -mod flag
509 // - the -modfile flag
510 //
511 // These are dependent on a number of factors: whether we need to run in a
512 // synthetic workspace, whether flags are supported at the current go
513 // version, and what we're actually trying to achieve (the
514 // source.InvocationFlags).
515
516 var modURI span.URI
517 // Select the module context to use.
518 // If we're type checking, we need to use the workspace context, meaning
519 // the main (workspace) module. Otherwise, we should use the module for
520 // the passed-in working dir.
521 if mode == source.LoadWorkspace {
Rob Findleya13793e2023-05-12 14:22:10 -0400522 if gowork, _ := s.view.GOWORK(); gowork == "" && s.view.gomod != "" {
Robert Findley80879112022-12-16 09:11:12 -0500523 modURI = s.view.gomod
Robert Findleyb15dac22022-08-30 14:40:12 -0400524 }
525 } else {
526 modURI = s.GoModForFile(span.URIFromPath(inv.WorkingDir))
527 }
528
529 var modContent []byte
530 if modURI != "" {
Alan Donovan36ed0b12023-03-13 14:20:23 -0400531 modFH, err := s.ReadFile(ctx, modURI)
Robert Findleyb15dac22022-08-30 14:40:12 -0400532 if err != nil {
533 return "", nil, cleanup, err
534 }
Alan Donovan786752b2023-03-07 12:14:28 -0500535 modContent, err = modFH.Content()
Robert Findleyb15dac22022-08-30 14:40:12 -0400536 if err != nil {
537 return "", nil, cleanup, err
538 }
539 }
540
541 // TODO(rfindley): in the case of go.work mode, modURI is empty and we fall
542 // back on the default behavior of vendorEnabled with an empty modURI. Figure
543 // out what is correct here and implement it explicitly.
544 vendorEnabled, err := s.vendorEnabled(ctx, modURI, modContent)
545 if err != nil {
546 return "", nil, cleanup, err
547 }
548
Robert Findley16b3bf82023-01-06 11:35:54 -0500549 const mutableModFlag = "mod"
Robert Findleyb15dac22022-08-30 14:40:12 -0400550 // If the mod flag isn't set, populate it based on the mode and workspace.
Robert Findley80879112022-12-16 09:11:12 -0500551 // TODO(rfindley): this doesn't make sense if we're not in module mode
Robert Findleyb15dac22022-08-30 14:40:12 -0400552 if inv.ModFlag == "" {
Robert Findleyb15dac22022-08-30 14:40:12 -0400553 switch mode {
554 case source.LoadWorkspace, source.Normal:
555 if vendorEnabled {
556 inv.ModFlag = "vendor"
557 } else if !allowModfileModificationOption {
558 inv.ModFlag = "readonly"
559 } else {
560 inv.ModFlag = mutableModFlag
561 }
562 case source.WriteTemporaryModFile:
563 inv.ModFlag = mutableModFlag
564 // -mod must be readonly when using go.work files - see issue #48941
565 inv.Env = append(inv.Env, "GOWORK=off")
566 }
567 }
568
569 // Only use a temp mod file if the modfile can actually be mutated.
570 needTempMod := inv.ModFlag == mutableModFlag
571 useTempMod := s.workspaceMode()&tempModfile != 0
572 if needTempMod && !useTempMod {
573 return "", nil, cleanup, source.ErrTmpModfileUnsupported
574 }
575
576 // We should use -modfile if:
577 // - the workspace mode supports it
578 // - we're using a go.work file on go1.18+, or we need a temp mod file (for
579 // example, if running go mod tidy in a go.work workspace)
580 //
581 // TODO(rfindley): this is very hard to follow. Refactor.
Robert Findley80879112022-12-16 09:11:12 -0500582 if !needTempMod && s.view.gowork != "" {
Robert Findleyb15dac22022-08-30 14:40:12 -0400583 // Since we're running in the workspace root, the go command will resolve GOWORK automatically.
584 } else if useTempMod {
585 if modURI == "" {
586 return "", nil, cleanup, fmt.Errorf("no go.mod file found in %s", inv.WorkingDir)
587 }
Alan Donovan36ed0b12023-03-13 14:20:23 -0400588 modFH, err := s.ReadFile(ctx, modURI)
Robert Findleyb15dac22022-08-30 14:40:12 -0400589 if err != nil {
590 return "", nil, cleanup, err
591 }
592 // Use the go.sum if it happens to be available.
593 gosum := s.goSum(ctx, modURI)
594 tmpURI, cleanup, err = tempModFile(modFH, gosum)
595 if err != nil {
596 return "", nil, cleanup, err
597 }
598 inv.ModFile = tmpURI.Filename()
599 }
600
601 return tmpURI, inv, cleanup, nil
602}
603
Robert Findleyb15dac22022-08-30 14:40:12 -0400604func (s *snapshot) buildOverlay() map[string][]byte {
Robert Findley488ba862023-03-23 12:18:06 -0400605 overlays := make(map[string][]byte)
606 for _, overlay := range s.overlays() {
607 if overlay.saved {
608 continue
609 }
610 // TODO(rfindley): previously, there was a todo here to make sure we don't
611 // send overlays outside of the current view. IMO we should instead make
612 // sure this doesn't matter.
613 overlays[overlay.URI().Filename()] = overlay.content
614 }
615 return overlays
616}
617
Robert Findley488ba862023-03-23 12:18:06 -0400618func (s *snapshot) overlays() []*Overlay {
Robert Findleyb15dac22022-08-30 14:40:12 -0400619 s.mu.Lock()
620 defer s.mu.Unlock()
621
Rob Findleya5ef6c32023-05-19 14:43:24 -0400622 return s.files.overlays()
Robert Findleyb15dac22022-08-30 14:40:12 -0400623}
624
Robert Findley21d22562023-02-21 12:26:27 -0500625// Package data kinds, identifying various package data that may be stored in
626// the file cache.
627const (
628 xrefsKind = "xrefs"
629 methodSetsKind = "methodsets"
630 exportDataKind = "export"
631 diagnosticsKind = "diagnostics"
Robert Findley1b2d1bd2023-03-16 18:52:13 -0400632 typerefsKind = "typerefs"
Robert Findley21d22562023-02-21 12:26:27 -0500633)
Alan Donovan1dcc4232022-12-06 13:27:13 -0500634
Robert Findley21d22562023-02-21 12:26:27 -0500635func (s *snapshot) PackageDiagnostics(ctx context.Context, ids ...PackageID) (map[span.URI][]*source.Diagnostic, error) {
Robert Findleyf7963612023-03-28 21:34:10 -0400636 ctx, done := event.Start(ctx, "cache.snapshot.PackageDiagnostics")
637 defer done()
638
Robert Findleyacaae982023-03-16 12:21:36 -0400639 var mu sync.Mutex
Robert Findley21d22562023-02-21 12:26:27 -0500640 perFile := make(map[span.URI][]*source.Diagnostic)
Robert Findleyacaae982023-03-16 12:21:36 -0400641 collect := func(diags []*source.Diagnostic) {
642 mu.Lock()
643 defer mu.Unlock()
644 for _, diag := range diags {
645 perFile[diag.URI] = append(perFile[diag.URI], diag)
Alan Donovan1dcc4232022-12-06 13:27:13 -0500646 }
Alan Donovan1dcc4232022-12-06 13:27:13 -0500647 }
Robert Findleyacaae982023-03-16 12:21:36 -0400648 pre := func(i int, ph *packageHandle) bool {
649 data, err := filecache.Get(diagnosticsKind, ph.key)
650 if err == nil { // hit
651 collect(ph.m.Diagnostics)
652 collect(decodeDiagnostics(data))
653 return false
654 } else if err != filecache.ErrNotFound {
655 event.Error(ctx, "reading diagnostics from filecache", err)
656 }
657 return true
658 }
659 post := func(_ int, pkg *Package) {
Robert Findley1b2d1bd2023-03-16 18:52:13 -0400660 collect(pkg.ph.m.Diagnostics)
Robert Findleyacaae982023-03-16 12:21:36 -0400661 collect(pkg.pkg.diagnostics)
662 }
663 return perFile, s.forEachPackage(ctx, ids, pre, post)
Robert Findley21d22562023-02-21 12:26:27 -0500664}
Alan Donovan1dcc4232022-12-06 13:27:13 -0500665
Robert Findley21d22562023-02-21 12:26:27 -0500666func (s *snapshot) References(ctx context.Context, ids ...PackageID) ([]source.XrefIndex, error) {
Robert Findleyf7963612023-03-28 21:34:10 -0400667 ctx, done := event.Start(ctx, "cache.snapshot.References")
668 defer done()
669
Robert Findley21d22562023-02-21 12:26:27 -0500670 indexes := make([]source.XrefIndex, len(ids))
Robert Findleyacaae982023-03-16 12:21:36 -0400671 pre := func(i int, ph *packageHandle) bool {
672 data, err := filecache.Get(xrefsKind, ph.key)
673 if err == nil { // hit
674 indexes[i] = XrefIndex{m: ph.m, data: data}
675 return false
676 } else if err != filecache.ErrNotFound {
677 event.Error(ctx, "reading xrefs from filecache", err)
Robert Findleyb15dac22022-08-30 14:40:12 -0400678 }
Robert Findleyacaae982023-03-16 12:21:36 -0400679 return true
Robert Findleyb15dac22022-08-30 14:40:12 -0400680 }
Robert Findleyacaae982023-03-16 12:21:36 -0400681 post := func(i int, pkg *Package) {
Rob Findleyf394d452023-06-13 13:50:51 -0400682 indexes[i] = XrefIndex{m: pkg.ph.m, data: pkg.pkg.xrefs()}
Robert Findleyacaae982023-03-16 12:21:36 -0400683 }
684 return indexes, s.forEachPackage(ctx, ids, pre, post)
Robert Findley21d22562023-02-21 12:26:27 -0500685}
Robert Findleyf10e7d52023-01-12 13:51:49 -0500686
Robert Findley21d22562023-02-21 12:26:27 -0500687// An XrefIndex is a helper for looking up a package in a given package.
688type XrefIndex struct {
689 m *source.Metadata
690 data []byte
691}
692
693func (index XrefIndex) Lookup(targets map[PackagePath]map[objectpath.Path]struct{}) []protocol.Location {
694 return xrefs.Lookup(index.m, index.data, targets)
695}
696
697func (s *snapshot) MethodSets(ctx context.Context, ids ...PackageID) ([]*methodsets.Index, error) {
Robert Findleyf7963612023-03-28 21:34:10 -0400698 ctx, done := event.Start(ctx, "cache.snapshot.MethodSets")
699 defer done()
700
Robert Findley21d22562023-02-21 12:26:27 -0500701 indexes := make([]*methodsets.Index, len(ids))
Robert Findleyacaae982023-03-16 12:21:36 -0400702 pre := func(i int, ph *packageHandle) bool {
703 data, err := filecache.Get(methodSetsKind, ph.key)
704 if err == nil { // hit
705 indexes[i] = methodsets.Decode(data)
706 return false
707 } else if err != filecache.ErrNotFound {
708 event.Error(ctx, "reading methodsets from filecache", err)
Robert Findley21d22562023-02-21 12:26:27 -0500709 }
Robert Findleyacaae982023-03-16 12:21:36 -0400710 return true
Robert Findley21d22562023-02-21 12:26:27 -0500711 }
Robert Findleyacaae982023-03-16 12:21:36 -0400712 post := func(i int, pkg *Package) {
Rob Findleyf394d452023-06-13 13:50:51 -0400713 indexes[i] = pkg.pkg.methodsets()
Robert Findleyacaae982023-03-16 12:21:36 -0400714 }
715 return indexes, s.forEachPackage(ctx, ids, pre, post)
Robert Findleyb15dac22022-08-30 14:40:12 -0400716}
717
Alan Donovanff22fab2022-11-18 14:47:36 -0500718func (s *snapshot) MetadataForFile(ctx context.Context, uri span.URI) ([]*source.Metadata, error) {
Rob Findley3d53c2d2023-05-15 20:03:00 -0400719 if s.view.ViewType() == AdHocView {
720 // As described in golang/go#57209, in ad-hoc workspaces (where we load ./
721 // rather than ./...), preempting the directory load with file loads can
722 // lead to an inconsistent outcome, where certain files are loaded with
723 // command-line-arguments packages and others are loaded only in the ad-hoc
724 // package. Therefore, ensure that the workspace is loaded before doing any
725 // file loads.
726 if err := s.awaitLoaded(ctx); err != nil {
727 return nil, err
728 }
729 }
730
Robert Findleyb15dac22022-08-30 14:40:12 -0400731 s.mu.Lock()
Robert Findleyb2533142022-10-10 13:50:45 -0400732
733 // Start with the set of package associations derived from the last load.
Robert Findleyb15dac22022-08-30 14:40:12 -0400734 ids := s.meta.ids[uri]
Robert Findleyb2533142022-10-10 13:50:45 -0400735
Robert Findleyb2533142022-10-10 13:50:45 -0400736 shouldLoad := false // whether any packages containing uri are marked 'shouldLoad'
Robert Findleyb15dac22022-08-30 14:40:12 -0400737 for _, id := range ids {
Robert Findleyb2533142022-10-10 13:50:45 -0400738 if len(s.shouldLoad[id]) > 0 {
739 shouldLoad = true
740 }
Robert Findleyb15dac22022-08-30 14:40:12 -0400741 }
Robert Findleyb2533142022-10-10 13:50:45 -0400742
743 // Check if uri is known to be unloadable.
Robert Findleyb2533142022-10-10 13:50:45 -0400744 _, unloadable := s.unloadableFiles[uri]
745
Robert Findleyb15dac22022-08-30 14:40:12 -0400746 s.mu.Unlock()
747
Robert Findleyb2533142022-10-10 13:50:45 -0400748 // Reload if loading is likely to improve the package associations for uri:
749 // - uri is not contained in any valid packages
750 // - ...or one of the packages containing uri is marked 'shouldLoad'
751 // - ...but uri is not unloadable
Robert Findley23056f62022-11-03 19:06:31 -0400752 if (shouldLoad || len(ids) == 0) && !unloadable {
Robert Findleyb2533142022-10-10 13:50:45 -0400753 scope := fileLoadScope(uri)
Robert Findleyb15dac22022-08-30 14:40:12 -0400754 err := s.load(ctx, false, scope)
755
Robert Findleyb2533142022-10-10 13:50:45 -0400756 // Guard against failed loads due to context cancellation.
Robert Findleyb15dac22022-08-30 14:40:12 -0400757 //
Robert Findleyb2533142022-10-10 13:50:45 -0400758 // Return the context error here as the current operation is no longer
759 // valid.
760 if ctxErr := ctx.Err(); ctxErr != nil {
761 return nil, ctxErr
Robert Findleyb15dac22022-08-30 14:40:12 -0400762 }
763
Robert Findleyb2533142022-10-10 13:50:45 -0400764 // We must clear scopes after loading.
765 //
766 // TODO(rfindley): unlike reloadWorkspace, this is simply marking loaded
767 // packages as loaded. We could do this from snapshot.load and avoid
768 // raciness.
769 s.clearShouldLoad(scope)
Robert Findleyb15dac22022-08-30 14:40:12 -0400770
Robert Findleyb2533142022-10-10 13:50:45 -0400771 // Don't return an error here, as we may still return stale IDs.
Alan Donovanff22fab2022-11-18 14:47:36 -0500772 // Furthermore, the result of MetadataForFile should be consistent upon
Robert Findleyb2533142022-10-10 13:50:45 -0400773 // subsequent calls, even if the file is marked as unloadable.
774 if err != nil && !errors.Is(err, errNoPackages) {
Alan Donovanff22fab2022-11-18 14:47:36 -0500775 event.Error(ctx, "MetadataForFile", err)
Robert Findleyb15dac22022-08-30 14:40:12 -0400776 }
777 }
778
Alan Donovanff22fab2022-11-18 14:47:36 -0500779 // Retrieve the metadata.
Robert Findleyb2533142022-10-10 13:50:45 -0400780 s.mu.Lock()
Alan Donovanff22fab2022-11-18 14:47:36 -0500781 defer s.mu.Unlock()
Robert Findleyb2533142022-10-10 13:50:45 -0400782 ids = s.meta.ids[uri]
Alan Donovanff22fab2022-11-18 14:47:36 -0500783 metas := make([]*source.Metadata, len(ids))
784 for i, id := range ids {
785 metas[i] = s.meta.metadata[id]
786 if metas[i] == nil {
787 panic("nil metadata")
788 }
789 }
790 // Metadata is only ever added by loading,
791 // so if we get here and still have
792 // no IDs, uri is unloadable.
Robert Findley23056f62022-11-03 19:06:31 -0400793 if !unloadable && len(ids) == 0 {
794 s.unloadableFiles[uri] = struct{}{}
Robert Findleyb2533142022-10-10 13:50:45 -0400795 }
Alan Donovan1dcc4232022-12-06 13:27:13 -0500796
Alan Donovanb35949e2023-04-20 14:53:41 -0400797 // Sort packages "narrowest" to "widest" (in practice:
798 // non-tests before tests), and regular packages before
799 // their intermediate test variants (which have the same
800 // files but different imports).
Alan Donovan1dcc4232022-12-06 13:27:13 -0500801 sort.Slice(metas, func(i, j int) bool {
Alan Donovanb35949e2023-04-20 14:53:41 -0400802 x, y := metas[i], metas[j]
803 xfiles, yfiles := len(x.CompiledGoFiles), len(y.CompiledGoFiles)
804 if xfiles != yfiles {
805 return xfiles < yfiles
806 }
807 return boolLess(x.IsIntermediateTestVariant(), y.IsIntermediateTestVariant())
Alan Donovan1dcc4232022-12-06 13:27:13 -0500808 })
809
Alan Donovanff22fab2022-11-18 14:47:36 -0500810 return metas, nil
Robert Findleyb15dac22022-08-30 14:40:12 -0400811}
812
Alan Donovanb35949e2023-04-20 14:53:41 -0400813func boolLess(x, y bool) bool { return !x && y } // false < true
814
Alan Donovan44395ff2022-12-21 12:13:36 -0500815func (s *snapshot) ReverseDependencies(ctx context.Context, id PackageID, transitive bool) (map[PackageID]*source.Metadata, error) {
Robert Findleyb15dac22022-08-30 14:40:12 -0400816 if err := s.awaitLoaded(ctx); err != nil {
817 return nil, err
818 }
819 s.mu.Lock()
820 meta := s.meta
821 s.mu.Unlock()
Robert Findleyb15dac22022-08-30 14:40:12 -0400822
Alan Donovan44395ff2022-12-21 12:13:36 -0500823 var rdeps map[PackageID]*source.Metadata
824 if transitive {
825 rdeps = meta.reverseReflexiveTransitiveClosure(id)
826
827 // Remove the original package ID from the map.
828 // (Callers all want irreflexivity but it's easier
829 // to compute reflexively then subtract.)
830 delete(rdeps, id)
831
832 } else {
833 // direct reverse dependencies
834 rdeps = make(map[PackageID]*source.Metadata)
835 for _, rdepID := range meta.importedBy[id] {
836 if rdep := meta.metadata[rdepID]; rdep != nil {
837 rdeps[rdepID] = rdep
838 }
839 }
840 }
Robert Findleyb15dac22022-08-30 14:40:12 -0400841
Alan Donovanb2b9dc32022-12-12 11:31:59 -0500842 return rdeps, nil
Robert Findleyb15dac22022-08-30 14:40:12 -0400843}
844
Robert Findley21d22562023-02-21 12:26:27 -0500845// -- Active package tracking --
846//
Alan Donovane8f417a2023-04-21 14:54:28 -0400847// We say a package is "active" if any of its files are open.
848// This is an optimization: the "active" concept is an
849// implementation detail of the cache and is not exposed
850// in the source or Snapshot API.
851// After type-checking we keep active packages in memory.
852// The activePackages persistent map does bookkeeping for
853// the set of active packages.
Robert Findley21d22562023-02-21 12:26:27 -0500854
855// getActivePackage returns a the memoized active package for id, if it exists.
856// If id is not active or has not yet been type-checked, it returns nil.
857func (s *snapshot) getActivePackage(id PackageID) *Package {
858 s.mu.Lock()
859 defer s.mu.Unlock()
860
861 if value, ok := s.activePackages.Get(id); ok {
862 return value.(*Package) // possibly nil, if we have already checked this id.
Robert Findleyb15dac22022-08-30 14:40:12 -0400863 }
Robert Findley21d22562023-02-21 12:26:27 -0500864 return nil
Robert Findleyb15dac22022-08-30 14:40:12 -0400865}
866
Rob Findley4810eda2023-07-24 20:10:33 -0400867// setActivePackage checks if pkg is active, and if so either records it in
Robert Findley21d22562023-02-21 12:26:27 -0500868// the active packages map or returns the existing memoized active package for id.
Rob Findley4810eda2023-07-24 20:10:33 -0400869func (s *snapshot) setActivePackage(id PackageID, pkg *Package) {
Robert Findley21d22562023-02-21 12:26:27 -0500870 s.mu.Lock()
871 defer s.mu.Unlock()
872
Rob Findley4810eda2023-07-24 20:10:33 -0400873 if _, ok := s.activePackages.Get(id); ok {
874 return // already memoized
Robert Findley21d22562023-02-21 12:26:27 -0500875 }
876
Rob Findley1c9fe3f2023-05-11 14:33:05 -0400877 if containsOpenFileLocked(s, pkg.Metadata()) {
Rob Findley4810eda2023-07-24 20:10:33 -0400878 s.activePackages.Set(id, pkg, nil)
879 } else {
880 s.activePackages.Set(id, (*Package)(nil), nil) // remember that pkg is not open
Robert Findley21d22562023-02-21 12:26:27 -0500881 }
Robert Findley21d22562023-02-21 12:26:27 -0500882}
883
884func (s *snapshot) resetActivePackagesLocked() {
885 s.activePackages.Destroy()
886 s.activePackages = persistent.NewMap(packageIDLessInterface)
Robert Findleyb15dac22022-08-30 14:40:12 -0400887}
888
889const fileExtensions = "go,mod,sum,work"
890
891func (s *snapshot) fileWatchingGlobPatterns(ctx context.Context) map[string]struct{} {
892 extensions := fileExtensions
893 for _, ext := range s.View().Options().TemplateExtensions {
894 extensions += "," + ext
895 }
896 // Work-around microsoft/vscode#100870 by making sure that we are,
897 // at least, watching the user's entire workspace. This will still be
898 // applied to every folder in the workspace.
899 patterns := map[string]struct{}{
900 fmt.Sprintf("**/*.{%s}", extensions): {},
901 }
902
Robert Findley5b300bd2022-12-29 13:39:59 -0500903 // If GOWORK is outside the folder, ensure we are watching it.
Rob Findleya13793e2023-05-12 14:22:10 -0400904 gowork, _ := s.view.GOWORK()
Robert Findley5b300bd2022-12-29 13:39:59 -0500905 if gowork != "" && !source.InDir(s.view.folder.Filename(), gowork.Filename()) {
906 patterns[gowork.Filename()] = struct{}{}
Robert Findleyb15dac22022-08-30 14:40:12 -0400907 }
908
909 // Add a pattern for each Go module in the workspace that is not within the view.
Robert Findley80879112022-12-16 09:11:12 -0500910 dirs := s.dirs(ctx)
Robert Findleyb15dac22022-08-30 14:40:12 -0400911 for _, dir := range dirs {
912 dirName := dir.Filename()
913
914 // If the directory is within the view's folder, we're already watching
Alan Donovane0b516b2022-11-28 23:41:43 -0500915 // it with the first pattern above.
Robert Findleyb15dac22022-08-30 14:40:12 -0400916 if source.InDir(s.view.folder.Filename(), dirName) {
917 continue
918 }
919 // TODO(rstambler): If microsoft/vscode#3025 is resolved before
920 // microsoft/vscode#101042, we will need a work-around for Windows
921 // drive letter casing.
922 patterns[fmt.Sprintf("%s/**/*.{%s}", dirName, extensions)] = struct{}{}
923 }
924
Rob Findley3a5dbf32023-05-21 11:03:28 -0400925 if s.watchSubdirs() {
926 // Some clients (e.g. VS Code) do not send notifications for changes to
927 // directories that contain Go code (golang/go#42348). To handle this,
928 // explicitly watch all of the directories in the workspace. We find them
929 // by adding the directories of every file in the snapshot's workspace
930 // directories. There may be thousands of patterns, each a single
931 // directory.
932 //
933 // (A previous iteration created a single glob pattern holding a union of
934 // all the directories, but this was found to cause VS Code to get stuck
935 // for several minutes after a buffer was saved twice in a workspace that
936 // had >8000 watched directories.)
937 //
938 // Some clients (notably coc.nvim, which uses watchman for globs) perform
939 // poorly with a large list of individual directories.
940 s.addKnownSubdirs(patterns, dirs)
941 }
Robert Findleyb15dac22022-08-30 14:40:12 -0400942
943 return patterns
944}
945
Rob Findley3a5dbf32023-05-21 11:03:28 -0400946// watchSubdirs reports whether gopls should request separate file watchers for
947// each relevant subdirectory. This is necessary only for clients (namely VS
948// Code) that do not send notifications for individual files in a directory
949// when the entire directory is deleted.
950func (s *snapshot) watchSubdirs() bool {
951 opts := s.view.Options()
952 switch p := opts.SubdirWatchPatterns; p {
953 case source.SubdirWatchPatternsOn:
954 return true
955 case source.SubdirWatchPatternsOff:
956 return false
957 case source.SubdirWatchPatternsAuto:
958 // See the documentation of InternalOptions.SubdirWatchPatterns for an
959 // explanation of why VS Code gets a different default value here.
960 //
961 // Unfortunately, there is no authoritative list of client names, nor any
962 // requirements that client names do not change. We should update the VS
963 // Code extension to set a default value of "subdirWatchPatterns" to "on",
964 // so that this workaround is only temporary.
965 if opts.ClientInfo != nil && opts.ClientInfo.Name == "Visual Studio Code" {
966 return true
967 }
968 return false
969 default:
970 bug.Reportf("invalid subdirWatchPatterns: %q", p)
971 return false
972 }
973}
974
Alan Donovan2ec42992023-05-18 13:45:43 -0400975func (s *snapshot) addKnownSubdirs(patterns map[string]struct{}, wsDirs []span.URI) {
Robert Findleyb15dac22022-08-30 14:40:12 -0400976 s.mu.Lock()
977 defer s.mu.Unlock()
978
979 // First, process any pending changes and update the set of known
980 // subdirectories.
981 // It may change list of known subdirs and therefore invalidate the cache.
982 s.applyKnownSubdirsChangesLocked(wsDirs)
983
Alan Donovan2ec42992023-05-18 13:45:43 -0400984 // TODO(adonovan): is it still necessary to memoize the Range
985 // and URI.Filename operations?
986 if s.knownSubdirsCache == nil {
987 s.knownSubdirsCache = make(map[string]struct{})
Robert Findleyb15dac22022-08-30 14:40:12 -0400988 s.knownSubdirs.Range(func(uri span.URI) {
Alan Donovan2ec42992023-05-18 13:45:43 -0400989 s.knownSubdirsCache[uri.Filename()] = struct{}{}
Robert Findleyb15dac22022-08-30 14:40:12 -0400990 })
Robert Findleyb15dac22022-08-30 14:40:12 -0400991 }
992
Alan Donovan2ec42992023-05-18 13:45:43 -0400993 for pattern := range s.knownSubdirsCache {
994 patterns[pattern] = struct{}{}
995 }
Robert Findleyb15dac22022-08-30 14:40:12 -0400996}
997
998// collectAllKnownSubdirs collects all of the subdirectories within the
999// snapshot's workspace directories. None of the workspace directories are
1000// included.
1001func (s *snapshot) collectAllKnownSubdirs(ctx context.Context) {
Robert Findley80879112022-12-16 09:11:12 -05001002 dirs := s.dirs(ctx)
Robert Findleyb15dac22022-08-30 14:40:12 -04001003
1004 s.mu.Lock()
1005 defer s.mu.Unlock()
1006
1007 s.knownSubdirs.Destroy()
1008 s.knownSubdirs = newKnownDirsSet()
Alan Donovan2ec42992023-05-18 13:45:43 -04001009 s.knownSubdirsCache = nil
Robert Findleya7f033a2023-01-19 18:12:18 -05001010 s.files.Range(func(uri span.URI, fh source.FileHandle) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001011 s.addKnownSubdirLocked(uri, dirs)
1012 })
1013}
1014
1015func (s *snapshot) getKnownSubdirs(wsDirs []span.URI) knownDirsSet {
1016 s.mu.Lock()
1017 defer s.mu.Unlock()
1018
1019 // First, process any pending changes and update the set of known
1020 // subdirectories.
1021 s.applyKnownSubdirsChangesLocked(wsDirs)
1022
1023 return s.knownSubdirs.Clone()
1024}
1025
1026func (s *snapshot) applyKnownSubdirsChangesLocked(wsDirs []span.URI) {
1027 for _, c := range s.unprocessedSubdirChanges {
1028 if c.isUnchanged {
1029 continue
1030 }
1031 if !c.exists {
1032 s.removeKnownSubdirLocked(c.fileHandle.URI())
1033 } else {
1034 s.addKnownSubdirLocked(c.fileHandle.URI(), wsDirs)
1035 }
1036 }
1037 s.unprocessedSubdirChanges = nil
1038}
1039
1040func (s *snapshot) addKnownSubdirLocked(uri span.URI, dirs []span.URI) {
1041 dir := filepath.Dir(uri.Filename())
1042 // First check if the directory is already known, because then we can
1043 // return early.
1044 if s.knownSubdirs.Contains(span.URIFromPath(dir)) {
1045 return
1046 }
1047 var matched span.URI
1048 for _, wsDir := range dirs {
1049 if source.InDir(wsDir.Filename(), dir) {
1050 matched = wsDir
1051 break
1052 }
1053 }
1054 // Don't watch any directory outside of the workspace directories.
1055 if matched == "" {
1056 return
1057 }
1058 for {
1059 if dir == "" || dir == matched.Filename() {
1060 break
1061 }
1062 uri := span.URIFromPath(dir)
1063 if s.knownSubdirs.Contains(uri) {
1064 break
1065 }
1066 s.knownSubdirs.Insert(uri)
1067 dir = filepath.Dir(dir)
Alan Donovan2ec42992023-05-18 13:45:43 -04001068 s.knownSubdirsCache = nil
Robert Findleyb15dac22022-08-30 14:40:12 -04001069 }
1070}
1071
1072func (s *snapshot) removeKnownSubdirLocked(uri span.URI) {
1073 dir := filepath.Dir(uri.Filename())
1074 for dir != "" {
1075 uri := span.URIFromPath(dir)
1076 if !s.knownSubdirs.Contains(uri) {
1077 break
1078 }
1079 if info, _ := os.Stat(dir); info == nil {
1080 s.knownSubdirs.Remove(uri)
Alan Donovan2ec42992023-05-18 13:45:43 -04001081 s.knownSubdirsCache = nil
Robert Findleyb15dac22022-08-30 14:40:12 -04001082 }
1083 dir = filepath.Dir(dir)
1084 }
1085}
1086
1087// knownFilesInDir returns the files known to the given snapshot that are in
1088// the given directory. It does not respect symlinks.
1089func (s *snapshot) knownFilesInDir(ctx context.Context, dir span.URI) []span.URI {
1090 var files []span.URI
1091 s.mu.Lock()
1092 defer s.mu.Unlock()
1093
Robert Findleya7f033a2023-01-19 18:12:18 -05001094 s.files.Range(func(uri span.URI, fh source.FileHandle) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001095 if source.InDir(dir.Filename(), uri.Filename()) {
1096 files = append(files, uri)
1097 }
1098 })
1099 return files
1100}
1101
Alan Donovane8f417a2023-04-21 14:54:28 -04001102func (s *snapshot) WorkspaceMetadata(ctx context.Context) ([]*source.Metadata, error) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001103 if err := s.awaitLoaded(ctx); err != nil {
1104 return nil, err
1105 }
Alan Donovane8f417a2023-04-21 14:54:28 -04001106
1107 s.mu.Lock()
1108 defer s.mu.Unlock()
1109
1110 meta := make([]*source.Metadata, 0, len(s.workspacePackages))
1111 for id := range s.workspacePackages {
1112 meta = append(meta, s.meta.metadata[id])
1113 }
1114 return meta, nil
Robert Findleyb15dac22022-08-30 14:40:12 -04001115}
1116
Robert Findleye5b99482023-02-15 22:26:54 -05001117// Symbols extracts and returns symbol information for every file contained in
1118// a loaded package. It awaits snapshot loading.
1119//
1120// TODO(rfindley): move this to the top of cache/symbols.go
Rob Findley8e9b1852023-05-01 12:44:43 -04001121func (s *snapshot) Symbols(ctx context.Context, workspaceOnly bool) (map[span.URI][]source.Symbol, error) {
Robert Findleye5b99482023-02-15 22:26:54 -05001122 if err := s.awaitLoaded(ctx); err != nil {
1123 return nil, err
1124 }
1125
Rob Findley8e9b1852023-05-01 12:44:43 -04001126 var (
1127 meta []*source.Metadata
1128 err error
1129 )
1130 if workspaceOnly {
1131 meta, err = s.WorkspaceMetadata(ctx)
1132 } else {
1133 meta, err = s.AllMetadata(ctx)
1134 }
1135 if err != nil {
1136 return nil, fmt.Errorf("loading metadata: %v", err)
1137 }
Alan Donovan19fb30d2022-11-18 16:29:11 -05001138
Robert Findleye5b99482023-02-15 22:26:54 -05001139 goFiles := make(map[span.URI]struct{})
Rob Findley8e9b1852023-05-01 12:44:43 -04001140 for _, m := range meta {
Robert Findleye5b99482023-02-15 22:26:54 -05001141 for _, uri := range m.GoFiles {
1142 goFiles[uri] = struct{}{}
1143 }
1144 for _, uri := range m.CompiledGoFiles {
1145 goFiles[uri] = struct{}{}
1146 }
1147 }
1148
Alan Donovan19fb30d2022-11-18 16:29:11 -05001149 // Symbolize them in parallel.
Robert Findleyb15dac22022-08-30 14:40:12 -04001150 var (
1151 group errgroup.Group
Alan Donovan19fb30d2022-11-18 16:29:11 -05001152 nprocs = 2 * runtime.GOMAXPROCS(-1) // symbolize is a mix of I/O and CPU
Robert Findleyb15dac22022-08-30 14:40:12 -04001153 resultMu sync.Mutex
1154 result = make(map[span.URI][]source.Symbol)
1155 )
Alan Donovan19fb30d2022-11-18 16:29:11 -05001156 group.SetLimit(nprocs)
Robert Findleye5b99482023-02-15 22:26:54 -05001157 for uri := range goFiles {
1158 uri := uri
Robert Findleyb15dac22022-08-30 14:40:12 -04001159 group.Go(func() error {
Robert Findleye5b99482023-02-15 22:26:54 -05001160 symbols, err := s.symbolize(ctx, uri)
Robert Findleyb15dac22022-08-30 14:40:12 -04001161 if err != nil {
1162 return err
1163 }
1164 resultMu.Lock()
Robert Findleye5b99482023-02-15 22:26:54 -05001165 result[uri] = symbols
Robert Findleyb15dac22022-08-30 14:40:12 -04001166 resultMu.Unlock()
1167 return nil
1168 })
Alan Donovan19fb30d2022-11-18 16:29:11 -05001169 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001170 // Keep going on errors, but log the first failure.
1171 // Partial results are better than no symbol results.
1172 if err := group.Wait(); err != nil {
1173 event.Error(ctx, "getting snapshot symbols", err)
1174 }
Robert Findleye5b99482023-02-15 22:26:54 -05001175 return result, nil
Robert Findleyb15dac22022-08-30 14:40:12 -04001176}
1177
Alan Donovan763a0302022-12-12 15:07:03 -05001178func (s *snapshot) AllMetadata(ctx context.Context) ([]*source.Metadata, error) {
Robert Findley61280302022-10-17 16:35:50 -04001179 if err := s.awaitLoaded(ctx); err != nil {
1180 return nil, err
1181 }
1182
1183 s.mu.Lock()
Alan Donovanff22fab2022-11-18 14:47:36 -05001184 g := s.meta
1185 s.mu.Unlock()
Robert Findley61280302022-10-17 16:35:50 -04001186
Alan Donovanff22fab2022-11-18 14:47:36 -05001187 meta := make([]*source.Metadata, 0, len(g.metadata))
1188 for _, m := range g.metadata {
Robert Findley5a4eba52022-11-03 18:28:39 -04001189 meta = append(meta, m)
Robert Findley61280302022-10-17 16:35:50 -04001190 }
1191 return meta, nil
1192}
1193
Robert Findleybf5db812022-12-02 17:02:23 -05001194// TODO(rfindley): clarify that this is only active modules. Or update to just
1195// use findRootPattern.
Robert Findleyb15dac22022-08-30 14:40:12 -04001196func (s *snapshot) GoModForFile(uri span.URI) span.URI {
Robert Findley80879112022-12-16 09:11:12 -05001197 return moduleForURI(s.workspaceModFiles, uri)
Robert Findleyb15dac22022-08-30 14:40:12 -04001198}
1199
1200func moduleForURI(modFiles map[span.URI]struct{}, uri span.URI) span.URI {
1201 var match span.URI
1202 for modURI := range modFiles {
Rob Findley787e7202023-05-09 18:57:05 -04001203 if !source.InDir(filepath.Dir(modURI.Filename()), uri.Filename()) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001204 continue
1205 }
1206 if len(modURI) > len(match) {
1207 match = modURI
1208 }
1209 }
1210 return match
1211}
1212
Robert Findleyad4fc282023-02-15 23:48:30 -05001213// nearestModFile finds the nearest go.mod file contained in the directory
1214// containing uri, or a parent of that directory.
1215//
1216// The given uri must be a file, not a directory.
1217func nearestModFile(ctx context.Context, uri span.URI, fs source.FileSource) (span.URI, error) {
Robert Findleyad4fc282023-02-15 23:48:30 -05001218 dir := filepath.Dir(uri.Filename())
1219 mod, err := findRootPattern(ctx, dir, "go.mod", fs)
1220 if err != nil {
1221 return "", err
1222 }
1223 return span.URIFromPath(mod), nil
1224}
1225
Alan Donovan09ae2d52022-11-18 15:44:00 -05001226func (s *snapshot) Metadata(id PackageID) *source.Metadata {
Robert Findleyb15dac22022-08-30 14:40:12 -04001227 s.mu.Lock()
1228 defer s.mu.Unlock()
Robert Findleyb15dac22022-08-30 14:40:12 -04001229 return s.meta.metadata[id]
1230}
1231
1232// clearShouldLoad clears package IDs that no longer need to be reloaded after
1233// scopes has been loaded.
Robert Findleyb2533142022-10-10 13:50:45 -04001234func (s *snapshot) clearShouldLoad(scopes ...loadScope) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001235 s.mu.Lock()
1236 defer s.mu.Unlock()
1237
1238 for _, scope := range scopes {
1239 switch scope := scope.(type) {
Robert Findleyb2533142022-10-10 13:50:45 -04001240 case packageLoadScope:
1241 scopePath := PackagePath(scope)
Robert Findleyb15dac22022-08-30 14:40:12 -04001242 var toDelete []PackageID
1243 for id, pkgPaths := range s.shouldLoad {
1244 for _, pkgPath := range pkgPaths {
Robert Findleyb2533142022-10-10 13:50:45 -04001245 if pkgPath == scopePath {
Robert Findleyb15dac22022-08-30 14:40:12 -04001246 toDelete = append(toDelete, id)
1247 }
1248 }
1249 }
1250 for _, id := range toDelete {
1251 delete(s.shouldLoad, id)
1252 }
Robert Findleyb2533142022-10-10 13:50:45 -04001253 case fileLoadScope:
Robert Findleyb15dac22022-08-30 14:40:12 -04001254 uri := span.URI(scope)
1255 ids := s.meta.ids[uri]
1256 for _, id := range ids {
1257 delete(s.shouldLoad, id)
1258 }
1259 }
1260 }
1261}
1262
Robert Findleya7f033a2023-01-19 18:12:18 -05001263func (s *snapshot) FindFile(uri span.URI) source.FileHandle {
Robert Findleyae242ec2023-01-19 16:41:08 -05001264 s.view.markKnown(uri)
Robert Findleyb15dac22022-08-30 14:40:12 -04001265
1266 s.mu.Lock()
1267 defer s.mu.Unlock()
1268
Alan Donovand444fa32022-12-01 11:17:42 -05001269 result, _ := s.files.Get(uri)
Robert Findleyb15dac22022-08-30 14:40:12 -04001270 return result
1271}
1272
Alan Donovan36ed0b12023-03-13 14:20:23 -04001273// ReadFile returns a File for the given URI. If the file is unknown it is added
Robert Findleya7f033a2023-01-19 18:12:18 -05001274// to the managed set.
Robert Findleyb15dac22022-08-30 14:40:12 -04001275//
Alan Donovan36ed0b12023-03-13 14:20:23 -04001276// ReadFile succeeds even if the file does not exist. A non-nil error return
Robert Findleyb15dac22022-08-30 14:40:12 -04001277// indicates some type of internal error, for example if ctx is cancelled.
Alan Donovan36ed0b12023-03-13 14:20:23 -04001278func (s *snapshot) ReadFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001279 s.mu.Lock()
1280 defer s.mu.Unlock()
Alan Donovand444fa32022-12-01 11:17:42 -05001281
Alan Donovan36ed0b12023-03-13 14:20:23 -04001282 return lockedSnapshot{s}.ReadFile(ctx, uri)
Robert Findleyad4fc282023-02-15 23:48:30 -05001283}
1284
Robert Findleyc3edf5a2023-03-20 18:37:18 -04001285// preloadFiles delegates to the view FileSource to read the requested uris in
1286// parallel, without holding the snapshot lock.
1287func (s *snapshot) preloadFiles(ctx context.Context, uris []span.URI) {
1288 files := make([]source.FileHandle, len(uris))
1289 var wg sync.WaitGroup
1290 iolimit := make(chan struct{}, 20) // I/O concurrency limiting semaphore
1291 for i, uri := range uris {
1292 wg.Add(1)
1293 iolimit <- struct{}{}
1294 go func(i int, uri span.URI) {
1295 defer wg.Done()
1296 fh, err := s.view.fs.ReadFile(ctx, uri)
1297 <-iolimit
1298 if err != nil && ctx.Err() == nil {
1299 event.Error(ctx, fmt.Sprintf("reading %s", uri), err)
1300 return
1301 }
1302 files[i] = fh
1303 }(i, uri)
1304 }
1305 wg.Wait()
1306
1307 s.mu.Lock()
1308 defer s.mu.Unlock()
1309
1310 for i, fh := range files {
1311 if fh == nil {
1312 continue // error logged above
1313 }
1314 uri := uris[i]
1315 if _, ok := s.files.Get(uri); !ok {
1316 s.files.Set(uri, fh)
1317 }
1318 }
1319}
1320
Robert Findleyad4fc282023-02-15 23:48:30 -05001321// A lockedSnapshot implements the source.FileSource interface while holding
1322// the lock for the wrapped snapshot.
1323type lockedSnapshot struct{ wrapped *snapshot }
1324
Alan Donovan36ed0b12023-03-13 14:20:23 -04001325func (s lockedSnapshot) ReadFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
Robert Findleyad4fc282023-02-15 23:48:30 -05001326 s.wrapped.view.markKnown(uri)
1327 if fh, ok := s.wrapped.files.Get(uri); ok {
Alan Donovand444fa32022-12-01 11:17:42 -05001328 return fh, nil
1329 }
1330
Alan Donovan36ed0b12023-03-13 14:20:23 -04001331 fh, err := s.wrapped.view.fs.ReadFile(ctx, uri)
Alan Donovand444fa32022-12-01 11:17:42 -05001332 if err != nil {
1333 return nil, err
1334 }
Robert Findleyad4fc282023-02-15 23:48:30 -05001335 s.wrapped.files.Set(uri, fh)
Robert Findleya7f033a2023-01-19 18:12:18 -05001336 return fh, nil
Robert Findleyb15dac22022-08-30 14:40:12 -04001337}
1338
Robert Findleyb15dac22022-08-30 14:40:12 -04001339func (s *snapshot) IsOpen(uri span.URI) bool {
1340 s.mu.Lock()
1341 defer s.mu.Unlock()
Robert Findleyb15dac22022-08-30 14:40:12 -04001342
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001343 fh, _ := s.files.Get(uri)
1344 _, open := fh.(*Overlay)
Robert Findleyb15dac22022-08-30 14:40:12 -04001345 return open
1346}
1347
Robert Findley488ba862023-03-23 12:18:06 -04001348// TODO(rfindley): it would make sense for awaitLoaded to return metadata.
Robert Findleyb15dac22022-08-30 14:40:12 -04001349func (s *snapshot) awaitLoaded(ctx context.Context) error {
1350 loadErr := s.awaitLoadedAllErrors(ctx)
1351
Robert Findley5a4eba52022-11-03 18:28:39 -04001352 // TODO(rfindley): eliminate this function as part of simplifying
1353 // CriticalErrors.
Robert Findleyb15dac22022-08-30 14:40:12 -04001354 if loadErr != nil {
1355 return loadErr.MainError
1356 }
1357 return nil
1358}
1359
Alan Donovane8f417a2023-04-21 14:54:28 -04001360func (s *snapshot) CriticalError(ctx context.Context) *source.CriticalError {
Robert Findley80879112022-12-16 09:11:12 -05001361 // If we couldn't compute workspace mod files, then the load below is
1362 // invalid.
1363 //
1364 // TODO(rfindley): is this a clear error to present to the user?
1365 if s.workspaceModFilesErr != nil {
1366 return &source.CriticalError{MainError: s.workspaceModFilesErr}
Robert Findleyb15dac22022-08-30 14:40:12 -04001367 }
1368
1369 loadErr := s.awaitLoadedAllErrors(ctx)
1370 if loadErr != nil && errors.Is(loadErr.MainError, context.Canceled) {
1371 return nil
1372 }
1373
1374 // Even if packages didn't fail to load, we still may want to show
1375 // additional warnings.
1376 if loadErr == nil {
Alan Donovane8f417a2023-04-21 14:54:28 -04001377 active, _ := s.WorkspaceMetadata(ctx)
Alan Donovan18f76ec2022-12-08 09:48:21 -05001378 if msg := shouldShowAdHocPackagesWarning(s, active); msg != "" {
Robert Findleyb15dac22022-08-30 14:40:12 -04001379 return &source.CriticalError{
1380 MainError: errors.New(msg),
1381 }
1382 }
1383 // Even if workspace packages were returned, there still may be an error
1384 // with the user's workspace layout. Workspace packages that only have the
1385 // ID "command-line-arguments" are usually a symptom of a bad workspace
1386 // configuration.
1387 //
Robert Findley4f69bf32022-12-02 13:01:36 -05001388 // This heuristic is path-dependent: we only get command-line-arguments
1389 // packages when we've loaded using file scopes, which only occurs
1390 // on-demand or via orphaned file reloading.
1391 //
Robert Findleyb15dac22022-08-30 14:40:12 -04001392 // TODO(rfindley): re-evaluate this heuristic.
Alan Donovan18f76ec2022-12-08 09:48:21 -05001393 if containsCommandLineArguments(active) {
Robert Findleybf5db812022-12-02 17:02:23 -05001394 err, diags := s.workspaceLayoutError(ctx)
1395 if err != nil {
1396 if ctx.Err() != nil {
1397 return nil // see the API documentation for source.Snapshot
1398 }
1399 return &source.CriticalError{
1400 MainError: err,
1401 Diagnostics: diags,
1402 }
1403 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001404 }
1405 return nil
1406 }
1407
1408 if errMsg := loadErr.MainError.Error(); strings.Contains(errMsg, "cannot find main module") || strings.Contains(errMsg, "go.mod file not found") {
Robert Findleybf5db812022-12-02 17:02:23 -05001409 err, diags := s.workspaceLayoutError(ctx)
1410 if err != nil {
1411 if ctx.Err() != nil {
1412 return nil // see the API documentation for source.Snapshot
1413 }
1414 return &source.CriticalError{
1415 MainError: err,
1416 Diagnostics: diags,
1417 }
1418 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001419 }
1420 return loadErr
1421}
1422
Alan Donovan84299a02022-12-08 10:18:14 -05001423// A portion of this text is expected by TestBrokenWorkspace_OutsideModule.
Robert Findleyb15dac22022-08-30 14:40:12 -04001424const adHocPackagesWarning = `You are outside of a module and outside of $GOPATH/src.
1425If you are using modules, please open your editor to a directory in your module.
1426If you believe this warning is incorrect, please file an issue: https://github.com/golang/go/issues/new.`
1427
Rob Findleye5c8d4d2023-05-15 19:50:02 -04001428func shouldShowAdHocPackagesWarning(snapshot *snapshot, active []*source.Metadata) string {
1429 if !snapshot.validBuildConfiguration() {
Alan Donovan18f76ec2022-12-08 09:48:21 -05001430 for _, m := range active {
Alan Donovan84299a02022-12-08 10:18:14 -05001431 // A blank entry in DepsByImpPath
1432 // indicates a missing dependency.
1433 for _, importID := range m.DepsByImpPath {
1434 if importID == "" {
1435 return adHocPackagesWarning
1436 }
Alan Donovand39685a2022-11-22 13:18:01 -05001437 }
1438 }
1439 }
Alan Donovan84299a02022-12-08 10:18:14 -05001440 return ""
Alan Donovand39685a2022-11-22 13:18:01 -05001441}
1442
Alan Donovan18f76ec2022-12-08 09:48:21 -05001443func containsCommandLineArguments(metas []*source.Metadata) bool {
1444 for _, m := range metas {
1445 if source.IsCommandLineArguments(m.ID) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001446 return true
1447 }
1448 }
1449 return false
1450}
1451
1452func (s *snapshot) awaitLoadedAllErrors(ctx context.Context) *source.CriticalError {
1453 // Do not return results until the snapshot's view has been initialized.
1454 s.AwaitInitialized(ctx)
1455
1456 // TODO(rfindley): Should we be more careful about returning the
1457 // initialization error? Is it possible for the initialization error to be
1458 // corrected without a successful reinitialization?
Alan Donovanff22fab2022-11-18 14:47:36 -05001459 if err := s.getInitializationError(); err != nil {
1460 return err
Robert Findleyb15dac22022-08-30 14:40:12 -04001461 }
1462
1463 // TODO(rfindley): revisit this handling. Calling reloadWorkspace with a
1464 // cancelled context should have the same effect, so this preemptive handling
1465 // should not be necessary.
1466 //
1467 // Also: GetCriticalError ignores context cancellation errors. Should we be
1468 // returning nil here?
1469 if ctx.Err() != nil {
1470 return &source.CriticalError{MainError: ctx.Err()}
1471 }
1472
1473 // TODO(rfindley): reloading is not idempotent: if we try to reload or load
1474 // orphaned files below and fail, we won't try again. For that reason, we
1475 // could get different results from subsequent calls to this function, which
1476 // may cause critical errors to be suppressed.
1477
1478 if err := s.reloadWorkspace(ctx); err != nil {
1479 diags := s.extractGoCommandErrors(ctx, err)
1480 return &source.CriticalError{
1481 MainError: err,
1482 Diagnostics: diags,
1483 }
1484 }
1485
Robert Findley4f69bf32022-12-02 13:01:36 -05001486 if err := s.reloadOrphanedOpenFiles(ctx); err != nil {
Robert Findleyb15dac22022-08-30 14:40:12 -04001487 diags := s.extractGoCommandErrors(ctx, err)
1488 return &source.CriticalError{
1489 MainError: err,
1490 Diagnostics: diags,
1491 }
1492 }
1493 return nil
1494}
1495
Alan Donovan051f03f2022-10-21 11:10:37 -04001496func (s *snapshot) getInitializationError() *source.CriticalError {
Robert Findleyb15dac22022-08-30 14:40:12 -04001497 s.mu.Lock()
1498 defer s.mu.Unlock()
1499
1500 return s.initializedErr
1501}
1502
1503func (s *snapshot) AwaitInitialized(ctx context.Context) {
1504 select {
1505 case <-ctx.Done():
1506 return
1507 case <-s.view.initialWorkspaceLoad:
1508 }
1509 // We typically prefer to run something as intensive as the IWL without
1510 // blocking. I'm not sure if there is a way to do that here.
1511 s.initialize(ctx, false)
1512}
1513
1514// reloadWorkspace reloads the metadata for all invalidated workspace packages.
1515func (s *snapshot) reloadWorkspace(ctx context.Context) error {
Robert Findleyb2533142022-10-10 13:50:45 -04001516 var scopes []loadScope
Robert Findleyb15dac22022-08-30 14:40:12 -04001517 var seen map[PackagePath]bool
1518 s.mu.Lock()
1519 for _, pkgPaths := range s.shouldLoad {
1520 for _, pkgPath := range pkgPaths {
1521 if seen == nil {
1522 seen = make(map[PackagePath]bool)
1523 }
1524 if seen[pkgPath] {
1525 continue
1526 }
1527 seen[pkgPath] = true
Robert Findleyb2533142022-10-10 13:50:45 -04001528 scopes = append(scopes, packageLoadScope(pkgPath))
Robert Findleyb15dac22022-08-30 14:40:12 -04001529 }
1530 }
1531 s.mu.Unlock()
1532
1533 if len(scopes) == 0 {
1534 return nil
1535 }
1536
1537 // If the view's build configuration is invalid, we cannot reload by
1538 // package path. Just reload the directory instead.
Rob Findleye5c8d4d2023-05-15 19:50:02 -04001539 if !s.validBuildConfiguration() {
Robert Findleyb2533142022-10-10 13:50:45 -04001540 scopes = []loadScope{viewLoadScope("LOAD_INVALID_VIEW")}
Robert Findleyb15dac22022-08-30 14:40:12 -04001541 }
1542
1543 err := s.load(ctx, false, scopes...)
1544
1545 // Unless the context was canceled, set "shouldLoad" to false for all
1546 // of the metadata we attempted to load.
1547 if !errors.Is(err, context.Canceled) {
1548 s.clearShouldLoad(scopes...)
1549 }
1550
1551 return err
1552}
1553
Robert Findley04059e12023-03-29 17:05:52 -04001554// reloadOrphanedOpenFiles attempts to load a package for each open file that
1555// does not yet have an associated package. If loading finishes without being
1556// canceled, any files still not contained in a package are marked as unloadable.
1557//
1558// An error is returned if the load is canceled.
Robert Findley4f69bf32022-12-02 13:01:36 -05001559func (s *snapshot) reloadOrphanedOpenFiles(ctx context.Context) error {
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001560 s.mu.Lock()
1561 meta := s.meta
1562 s.mu.Unlock()
Robert Findleyb15dac22022-08-30 14:40:12 -04001563 // When we load ./... or a package path directly, we may not get packages
1564 // that exist only in overlays. As a workaround, we search all of the files
1565 // available in the snapshot and reload their metadata individually using a
1566 // file= query if the metadata is unavailable.
Rob Findleya13793e2023-05-12 14:22:10 -04001567 open := s.overlays()
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001568 var files []*Overlay
1569 for _, o := range open {
1570 uri := o.URI()
1571 if s.IsBuiltin(uri) || s.view.FileKind(o) != source.Go {
1572 continue
1573 }
1574 if len(meta.ids[uri]) == 0 {
1575 files = append(files, o)
1576 }
1577 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001578
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001579 // Filter to files that are not known to be unloadable.
1580 s.mu.Lock()
1581 loadable := files[:0]
1582 for _, file := range files {
1583 if _, unloadable := s.unloadableFiles[file.URI()]; !unloadable {
1584 loadable = append(loadable, file)
1585 }
1586 }
1587 files = loadable
1588 s.mu.Unlock()
1589
Rob Findley98f1b4d2023-05-31 17:58:19 -04001590 if len(files) == 0 {
1591 return nil
1592 }
1593
Rob Findleycd39d2b2023-05-11 13:05:03 -04001594 var uris []span.URI
1595 for _, file := range files {
1596 uris = append(uris, file.URI())
1597 }
1598
1599 event.Log(ctx, "reloadOrphanedFiles reloading", tag.Files.Of(uris))
1600
1601 var g errgroup.Group
1602
1603 cpulimit := runtime.GOMAXPROCS(0)
1604 g.SetLimit(cpulimit)
1605
1606 // Load files one-at-a-time. go/packages can return at most one
1607 // command-line-arguments package per query.
1608 for _, file := range files {
1609 file := file
1610 g.Go(func() error {
Rob Findleycd39d2b2023-05-11 13:05:03 -04001611 return s.load(ctx, false, fileLoadScope(file.URI()))
1612 })
1613 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001614
1615 // If we failed to load some files, i.e. they have no metadata,
1616 // mark the failures so we don't bother retrying until the file's
1617 // content changes.
1618 //
cui fliter165099b2023-04-27 20:32:55 +08001619 // TODO(rfindley): is it possible that the load stopped early for an
Robert Findley04059e12023-03-29 17:05:52 -04001620 // unrelated errors? If so, add a fallback?
Rob Findleycd39d2b2023-05-11 13:05:03 -04001621
1622 if err := g.Wait(); err != nil {
1623 // Check for context cancellation so that we don't incorrectly mark files
1624 // as unloadable, but don't return before setting all workspace packages.
1625 if ctx.Err() != nil {
1626 return ctx.Err()
1627 }
1628
1629 if !errors.Is(err, errNoPackages) {
1630 event.Error(ctx, "reloadOrphanedFiles: failed to load", err, tag.Files.Of(uris))
1631 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001632 }
Robert Findley04059e12023-03-29 17:05:52 -04001633
1634 // If the context was not canceled, we assume that the result of loading
1635 // packages is deterministic (though as we saw in golang/go#59318, it may not
1636 // be in the presence of bugs). Marking all unloaded files as unloadable here
1637 // prevents us from falling into recursive reloading where we only make a bit
1638 // of progress each time.
1639 s.mu.Lock()
Rob Findleycd39d2b2023-05-11 13:05:03 -04001640 defer s.mu.Unlock()
1641 for _, file := range files {
Robert Findley04059e12023-03-29 17:05:52 -04001642 // TODO(rfindley): instead of locking here, we should have load return the
1643 // metadata graph that resulted from loading.
Rob Findleycd39d2b2023-05-11 13:05:03 -04001644 uri := file.URI()
Rob Findleyb1609712023-07-17 23:09:28 -04001645 if len(s.meta.ids[uri]) == 0 {
Robert Findley04059e12023-03-29 17:05:52 -04001646 s.unloadableFiles[uri] = struct{}{}
1647 }
1648 }
Robert Findley04059e12023-03-29 17:05:52 -04001649
Robert Findleyb15dac22022-08-30 14:40:12 -04001650 return nil
1651}
1652
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001653// OrphanedFileDiagnostics reports diagnostics describing why open files have
1654// no packages or have only command-line-arguments packages.
1655//
1656// If the resulting diagnostic is nil, the file is either not orphaned or we
1657// can't produce a good diagnostic.
1658//
1659// TODO(rfindley): reconcile the definition of "orphaned" here with
1660// reloadOrphanedFiles. The latter does not include files with
1661// command-line-arguments packages.
Rob Findleya13793e2023-05-12 14:22:10 -04001662func (s *snapshot) OrphanedFileDiagnostics(ctx context.Context) (map[span.URI]*source.Diagnostic, error) {
1663 // Orphaned file diagnostics are queried from code actions to produce
1664 // quick-fixes (and may be queried many times, once for each file).
1665 //
1666 // Because they are non-trivial to compute, record them optimistically to
1667 // avoid most redundant work.
1668 //
1669 // This is a hacky workaround: in the future we should avoid recomputing
1670 // anything when codeActions provide a diagnostic: simply read the published
1671 // diagnostic, if it exists.
Robert Findleyb15dac22022-08-30 14:40:12 -04001672 s.mu.Lock()
Rob Findleya13793e2023-05-12 14:22:10 -04001673 existing := s.orphanedFileDiagnostics
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001674 s.mu.Unlock()
Rob Findleya13793e2023-05-12 14:22:10 -04001675 if existing != nil {
1676 return existing, nil
1677 }
1678
1679 if err := s.awaitLoaded(ctx); err != nil {
1680 return nil, err
1681 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001682
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001683 var files []*Overlay
1684
1685searchOverlays:
1686 for _, o := range s.overlays() {
1687 uri := o.URI()
1688 if s.IsBuiltin(uri) || s.view.FileKind(o) != source.Go {
1689 continue
Robert Findley4f69bf32022-12-02 13:01:36 -05001690 }
Rob Findleya13793e2023-05-12 14:22:10 -04001691 md, err := s.MetadataForFile(ctx, uri)
1692 if err != nil {
1693 return nil, err
1694 }
1695 for _, m := range md {
1696 if !source.IsCommandLineArguments(m.ID) || m.Standalone {
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001697 continue searchOverlays
1698 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001699 }
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001700 files = append(files, o)
1701 }
1702 if len(files) == 0 {
Rob Findleya13793e2023-05-12 14:22:10 -04001703 return nil, nil
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001704 }
1705
Rob Findleya13793e2023-05-12 14:22:10 -04001706 loadedModFiles := make(map[span.URI]struct{}) // all mod files, including dependencies
1707 ignoredFiles := make(map[span.URI]bool) // files reported in packages.Package.IgnoredFiles
1708
1709 meta, err := s.AllMetadata(ctx)
1710 if err != nil {
1711 return nil, err
1712 }
1713
1714 for _, meta := range meta {
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001715 if meta.Module != nil && meta.Module.GoMod != "" {
1716 gomod := span.URIFromPath(meta.Module.GoMod)
1717 loadedModFiles[gomod] = struct{}{}
Robert Findleyb15dac22022-08-30 14:40:12 -04001718 }
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001719 for _, ignored := range meta.IgnoredFiles {
1720 ignoredFiles[ignored] = true
Robert Findleyb15dac22022-08-30 14:40:12 -04001721 }
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001722 }
1723
1724 diagnostics := make(map[span.URI]*source.Diagnostic)
1725 for _, fh := range files {
1726 // Only warn about orphaned files if the file is well-formed enough to
1727 // actually be part of a package.
1728 //
1729 // Use ParseGo as for open files this is likely to be a cache hit (we'll have )
1730 pgf, err := s.ParseGo(ctx, fh, source.ParseHeader)
1731 if err != nil {
1732 continue
Robert Findleyb15dac22022-08-30 14:40:12 -04001733 }
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001734 if !pgf.File.Name.Pos().IsValid() {
1735 continue
1736 }
1737 rng, err := pgf.PosRange(pgf.File.Name.Pos(), pgf.File.Name.End())
1738 if err != nil {
1739 continue
1740 }
1741
Rob Findleya13793e2023-05-12 14:22:10 -04001742 var (
1743 msg string // if non-empty, report a diagnostic with this message
1744 suggestedFixes []source.SuggestedFix // associated fixes, if any
1745 )
1746
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001747 // If we have a relevant go.mod file, check whether the file is orphaned
1748 // due to its go.mod file being inactive. We could also offer a
1749 // prescriptive diagnostic in the case that there is no go.mod file, but it
1750 // is harder to be precise in that case, and less important.
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001751 if goMod, err := nearestModFile(ctx, fh.URI(), s); err == nil && goMod != "" {
1752 if _, ok := loadedModFiles[goMod]; !ok {
1753 modDir := filepath.Dir(goMod.Filename())
Rob Findleya13793e2023-05-12 14:22:10 -04001754 viewDir := s.view.folder.Filename()
1755
1756 // When the module is underneath the view dir, we offer
1757 // "use all modules" quick-fixes.
1758 inDir := source.InDir(viewDir, modDir)
1759
1760 if rel, err := filepath.Rel(viewDir, modDir); err == nil {
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001761 modDir = rel
1762 }
1763
1764 var fix string
1765 if s.view.goversion >= 18 {
1766 if s.view.gowork != "" {
1767 fix = fmt.Sprintf("To fix this problem, you can add this module to your go.work file (%s)", s.view.gowork)
Rob Findleya13793e2023-05-12 14:22:10 -04001768 if cmd, err := command.NewRunGoWorkCommandCommand("Run `go work use`", command.RunGoWorkArgs{
1769 ViewID: s.view.ID(),
1770 Args: []string{"use", modDir},
1771 }); err == nil {
1772 suggestedFixes = append(suggestedFixes, source.SuggestedFix{
1773 Title: "Use this module in your go.work file",
1774 Command: &cmd,
1775 ActionKind: protocol.QuickFix,
1776 })
1777 }
1778
1779 if inDir {
1780 if cmd, err := command.NewRunGoWorkCommandCommand("Run `go work use -r`", command.RunGoWorkArgs{
1781 ViewID: s.view.ID(),
1782 Args: []string{"use", "-r", "."},
1783 }); err == nil {
1784 suggestedFixes = append(suggestedFixes, source.SuggestedFix{
1785 Title: "Use all modules in your workspace",
1786 Command: &cmd,
1787 ActionKind: protocol.QuickFix,
1788 })
1789 }
1790 }
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001791 } else {
1792 fix = "To fix this problem, you can add a go.work file that uses this directory."
Rob Findleya13793e2023-05-12 14:22:10 -04001793
1794 if cmd, err := command.NewRunGoWorkCommandCommand("Run `go work init && go work use`", command.RunGoWorkArgs{
1795 ViewID: s.view.ID(),
1796 InitFirst: true,
1797 Args: []string{"use", modDir},
1798 }); err == nil {
1799 suggestedFixes = []source.SuggestedFix{
1800 {
1801 Title: "Add a go.work file using this module",
1802 Command: &cmd,
1803 ActionKind: protocol.QuickFix,
1804 },
1805 }
1806 }
1807
1808 if inDir {
1809 if cmd, err := command.NewRunGoWorkCommandCommand("Run `go work init && go work use -r`", command.RunGoWorkArgs{
1810 ViewID: s.view.ID(),
1811 InitFirst: true,
1812 Args: []string{"use", "-r", "."},
1813 }); err == nil {
1814 suggestedFixes = append(suggestedFixes, source.SuggestedFix{
1815 Title: "Add a go.work file using all modules in your workspace",
1816 Command: &cmd,
1817 ActionKind: protocol.QuickFix,
1818 })
1819 }
1820 }
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001821 }
1822 } else {
1823 fix = `To work with multiple modules simultaneously, please upgrade to Go 1.18 or
1824later, reinstall gopls, and use a go.work file.`
1825 }
Alan Donovan4d663242023-05-19 14:51:17 -04001826 msg = fmt.Sprintf(`This file is within module %q, which is not included in your workspace.
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001827%s
1828See the documentation for more information on setting up your workspace:
1829https://github.com/golang/tools/blob/master/gopls/doc/workspace.md.`, modDir, fix)
1830 }
1831 }
1832
1833 if msg == "" && ignoredFiles[fh.URI()] {
1834 // TODO(rfindley): use the constraint package to check if the file
1835 // _actually_ satisfies the current build context.
1836 hasConstraint := false
1837 walkConstraints(pgf.File, func(constraint.Expr) bool {
1838 hasConstraint = true
1839 return false
1840 })
1841 var fix string
1842 if hasConstraint {
1843 fix = `This file may be excluded due to its build tags; try adding "-tags=<build tag>" to your gopls "buildFlags" configuration
1844See the documentation for more information on working with build tags:
1845https://github.com/golang/tools/blob/master/gopls/doc/settings.md#buildflags-string.`
1846 } else if strings.Contains(filepath.Base(fh.URI().Filename()), "_") {
1847 fix = `This file may be excluded due to its GOOS/GOARCH, or other build constraints.`
1848 } else {
1849 fix = `This file is ignored by your gopls build.` // we don't know why
1850 }
1851 msg = fmt.Sprintf("No packages found for open file %s.\n%s", fh.URI().Filename(), fix)
1852 }
1853
1854 if msg != "" {
1855 // Only report diagnostics if we detect an actual exclusion.
1856 diagnostics[fh.URI()] = &source.Diagnostic{
Rob Findleya13793e2023-05-12 14:22:10 -04001857 URI: fh.URI(),
1858 Range: rng,
1859 Severity: protocol.SeverityWarning,
1860 Source: source.ListError,
1861 Message: msg,
1862 SuggestedFixes: suggestedFixes,
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001863 }
1864 }
1865 }
1866
Rob Findleya13793e2023-05-12 14:22:10 -04001867 s.mu.Lock()
1868 defer s.mu.Unlock()
1869 if s.orphanedFileDiagnostics == nil { // another thread may have won the race
1870 s.orphanedFileDiagnostics = diagnostics
1871 }
1872 return s.orphanedFileDiagnostics, nil
Robert Findleyb15dac22022-08-30 14:40:12 -04001873}
1874
Robert Findleyb15dac22022-08-30 14:40:12 -04001875// TODO(golang/go#53756): this function needs to consider more than just the
1876// absolute URI, for example:
1877// - the position of /vendor/ with respect to the relevant module root
1878// - whether or not go.work is in use (as vendoring isn't supported in workspace mode)
1879//
1880// Most likely, each call site of inVendor needs to be reconsidered to
1881// understand and correctly implement the desired behavior.
1882func inVendor(uri span.URI) bool {
Alan Donovane0b516b2022-11-28 23:41:43 -05001883 _, after, found := cut(string(uri), "/vendor/")
1884 // Only subdirectories of /vendor/ are considered vendored
Robert Findleyb15dac22022-08-30 14:40:12 -04001885 // (/vendor/a/foo.go is vendored, /vendor/foo.go is not).
Alan Donovane0b516b2022-11-28 23:41:43 -05001886 return found && strings.Contains(after, "/")
1887}
1888
1889// TODO(adonovan): replace with strings.Cut when we can assume go1.18.
1890func cut(s, sep string) (before, after string, found bool) {
1891 if i := strings.Index(s, sep); i >= 0 {
1892 return s[:i], s[i+len(sep):], true
Robert Findleyb15dac22022-08-30 14:40:12 -04001893 }
Alan Donovane0b516b2022-11-28 23:41:43 -05001894 return s, "", false
Robert Findleyb15dac22022-08-30 14:40:12 -04001895}
1896
1897// unappliedChanges is a file source that handles an uncloned snapshot.
1898type unappliedChanges struct {
1899 originalSnapshot *snapshot
1900 changes map[span.URI]*fileChange
1901}
1902
Alan Donovan36ed0b12023-03-13 14:20:23 -04001903func (ac *unappliedChanges) ReadFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001904 if c, ok := ac.changes[uri]; ok {
1905 return c.fileHandle, nil
1906 }
Alan Donovan36ed0b12023-03-13 14:20:23 -04001907 return ac.originalSnapshot.ReadFile(ctx, uri)
Robert Findleyb15dac22022-08-30 14:40:12 -04001908}
1909
1910func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileChange, forceReloadMetadata bool) (*snapshot, func()) {
Robert Findleyf7963612023-03-28 21:34:10 -04001911 ctx, done := event.Start(ctx, "cache.snapshot.clone")
Robert Findleyb15dac22022-08-30 14:40:12 -04001912 defer done()
1913
Robert Findley80879112022-12-16 09:11:12 -05001914 reinit := false
1915 wsModFiles, wsModFilesErr := s.workspaceModFiles, s.workspaceModFilesErr
1916
Rob Findleya13793e2023-05-12 14:22:10 -04001917 if workURI, _ := s.view.GOWORK(); workURI != "" {
Robert Findley80879112022-12-16 09:11:12 -05001918 if change, ok := changes[workURI]; ok {
1919 wsModFiles, wsModFilesErr = computeWorkspaceModFiles(ctx, s.view.gomod, workURI, s.view.effectiveGO111MODULE(), &unappliedChanges{
1920 originalSnapshot: s,
1921 changes: changes,
1922 })
1923 // TODO(rfindley): don't rely on 'isUnchanged' here. Use a content hash instead.
1924 reinit = change.fileHandle.Saved() && !change.isUnchanged
1925 }
1926 }
1927
1928 // Reinitialize if any workspace mod file has changed on disk.
1929 for uri, change := range changes {
1930 if _, ok := wsModFiles[uri]; ok && change.fileHandle.Saved() && !change.isUnchanged {
1931 reinit = true
1932 }
1933 }
1934
1935 // Finally, process sumfile changes that may affect loading.
1936 for uri, change := range changes {
1937 if !change.fileHandle.Saved() {
1938 continue // like with go.mod files, we only reinit when things are saved
1939 }
1940 if filepath.Base(uri.Filename()) == "go.work.sum" && s.view.gowork != "" {
1941 if filepath.Dir(uri.Filename()) == filepath.Dir(s.view.gowork) {
1942 reinit = true
1943 }
1944 }
1945 if filepath.Base(uri.Filename()) == "go.sum" {
1946 dir := filepath.Dir(uri.Filename())
1947 modURI := span.URIFromPath(filepath.Join(dir, "go.mod"))
1948 if _, active := wsModFiles[modURI]; active {
1949 reinit = true
1950 }
1951 }
1952 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001953
1954 s.mu.Lock()
1955 defer s.mu.Unlock()
1956
Alan Donovane0b516b2022-11-28 23:41:43 -05001957 // Changes to vendor tree may require reinitialization,
1958 // either because of an initialization error
1959 // (e.g. "inconsistent vendoring detected"), or because
1960 // one or more modules may have moved into or out of the
1961 // vendor tree after 'go mod vendor' or 'rm -fr vendor/'.
1962 for uri := range changes {
1963 if inVendor(uri) && s.initializedErr != nil ||
1964 strings.HasSuffix(string(uri), "/vendor/modules.txt") {
1965 reinit = true
1966 break
Robert Findleyb15dac22022-08-30 14:40:12 -04001967 }
1968 }
1969
1970 bgCtx, cancel := context.WithCancel(bgCtx)
1971 result := &snapshot{
Robert Findley0c71b562022-11-14 11:58:07 -05001972 sequenceID: s.sequenceID + 1,
1973 globalID: nextSnapshotID(),
Robert Findleyb15dac22022-08-30 14:40:12 -04001974 store: s.store,
1975 view: s.view,
1976 backgroundCtx: bgCtx,
1977 cancel: cancel,
1978 builtin: s.builtin,
1979 initialized: s.initialized,
1980 initializedErr: s.initializedErr,
1981 packages: s.packages.Clone(),
Robert Findley21d22562023-02-21 12:26:27 -05001982 activePackages: s.activePackages.Clone(),
Robert Findleyb15dac22022-08-30 14:40:12 -04001983 files: s.files.Clone(),
Robert Findleyb15dac22022-08-30 14:40:12 -04001984 symbolizeHandles: s.symbolizeHandles.Clone(),
1985 workspacePackages: make(map[PackageID]PackagePath, len(s.workspacePackages)),
1986 unloadableFiles: make(map[span.URI]struct{}, len(s.unloadableFiles)),
1987 parseModHandles: s.parseModHandles.Clone(),
1988 parseWorkHandles: s.parseWorkHandles.Clone(),
1989 modTidyHandles: s.modTidyHandles.Clone(),
1990 modWhyHandles: s.modWhyHandles.Clone(),
Robert Findley7cda55e2022-11-22 12:09:11 -05001991 modVulnHandles: s.modVulnHandles.Clone(),
Robert Findleyb15dac22022-08-30 14:40:12 -04001992 knownSubdirs: s.knownSubdirs.Clone(),
Robert Findley80879112022-12-16 09:11:12 -05001993 workspaceModFiles: wsModFiles,
1994 workspaceModFilesErr: wsModFilesErr,
Robert Findley488ba862023-03-23 12:18:06 -04001995 importGraph: s.importGraph,
Robert Findley1b2d1bd2023-03-16 18:52:13 -04001996 pkgIndex: s.pkgIndex,
Robert Findleyb15dac22022-08-30 14:40:12 -04001997 }
1998
1999 // The snapshot should be initialized if either s was uninitialized, or we've
2000 // detected a change that triggers reinitialization.
2001 if reinit {
2002 result.initialized = false
2003 }
2004
2005 // Create a lease on the new snapshot.
2006 // (Best to do this early in case the code below hides an
2007 // incref/decref operation that might destroy it prematurely.)
2008 release := result.Acquire()
2009
2010 // Copy the set of unloadable files.
2011 //
2012 // TODO(rfindley): this looks wrong. Shouldn't we clear unloadableFiles on
2013 // changes to environment or workspace layout, or more generally on any
2014 // metadata change?
Robert Findley23056f62022-11-03 19:06:31 -04002015 //
2016 // Maybe not, as major configuration changes cause a new view.
Robert Findleyb15dac22022-08-30 14:40:12 -04002017 for k, v := range s.unloadableFiles {
2018 result.unloadableFiles[k] = v
2019 }
2020
Robert Findleyb15dac22022-08-30 14:40:12 -04002021 // Add all of the known subdirectories, but don't update them for the
2022 // changed files. We need to rebuild the workspace module to know the
2023 // true set of known subdirectories, but we don't want to do that in clone.
2024 result.knownSubdirs = s.knownSubdirs.Clone()
Alan Donovan2ec42992023-05-18 13:45:43 -04002025 result.knownSubdirsCache = s.knownSubdirsCache
Robert Findleyb15dac22022-08-30 14:40:12 -04002026 for _, c := range changes {
2027 result.unprocessedSubdirChanges = append(result.unprocessedSubdirChanges, c)
2028 }
2029
2030 // directIDs keeps track of package IDs that have directly changed.
Alan Donovane0b516b2022-11-28 23:41:43 -05002031 // Note: this is not a set, it's a map from id to invalidateMetadata.
Robert Findleyb15dac22022-08-30 14:40:12 -04002032 directIDs := map[PackageID]bool{}
2033
2034 // Invalidate all package metadata if the workspace module has changed.
2035 if reinit {
2036 for k := range s.meta.metadata {
2037 directIDs[k] = true
2038 }
2039 }
2040
2041 // Compute invalidations based on file changes.
2042 anyImportDeleted := false // import deletions can resolve cycles
2043 anyFileOpenedOrClosed := false // opened files affect workspace packages
2044 anyFileAdded := false // adding a file can resolve missing dependencies
2045
2046 for uri, change := range changes {
Robert Findley21d22562023-02-21 12:26:27 -05002047 // Invalidate go.mod-related handles.
2048 result.modTidyHandles.Delete(uri)
2049 result.modWhyHandles.Delete(uri)
2050 result.modVulnHandles.Delete(uri)
2051
2052 // Invalidate handles for cached symbols.
2053 result.symbolizeHandles.Delete(uri)
2054
Robert Findleyb15dac22022-08-30 14:40:12 -04002055 // The original FileHandle for this URI is cached on the snapshot.
2056 originalFH, _ := s.files.Get(uri)
2057 var originalOpen, newOpen bool
Robert Findleya7f033a2023-01-19 18:12:18 -05002058 _, originalOpen = originalFH.(*Overlay)
2059 _, newOpen = change.fileHandle.(*Overlay)
Robert Findleyb15dac22022-08-30 14:40:12 -04002060 anyFileOpenedOrClosed = anyFileOpenedOrClosed || (originalOpen != newOpen)
2061 anyFileAdded = anyFileAdded || (originalFH == nil && change.fileHandle != nil)
2062
2063 // If uri is a Go file, check if it has changed in a way that would
2064 // invalidate metadata. Note that we can't use s.view.FileKind here,
2065 // because the file type that matters is not what the *client* tells us,
2066 // but what the Go command sees.
2067 var invalidateMetadata, pkgFileChanged, importDeleted bool
2068 if strings.HasSuffix(uri.Filename(), ".go") {
2069 invalidateMetadata, pkgFileChanged, importDeleted = metadataChanges(ctx, s, originalFH, change.fileHandle)
2070 }
2071
2072 invalidateMetadata = invalidateMetadata || forceReloadMetadata || reinit
2073 anyImportDeleted = anyImportDeleted || importDeleted
2074
2075 // Mark all of the package IDs containing the given file.
2076 filePackageIDs := invalidatedPackageIDs(uri, s.meta.ids, pkgFileChanged)
2077 for id := range filePackageIDs {
Alan Donovane0b516b2022-11-28 23:41:43 -05002078 directIDs[id] = directIDs[id] || invalidateMetadata // may insert 'false'
Robert Findleyb15dac22022-08-30 14:40:12 -04002079 }
2080
2081 // Invalidate the previous modTidyHandle if any of the files have been
2082 // saved or if any of the metadata has been invalidated.
2083 if invalidateMetadata || fileWasSaved(originalFH, change.fileHandle) {
Rob Findley43b02ea2023-05-19 16:18:30 -04002084 // Only invalidate mod tidy results for the most relevant modfile in the
2085 // workspace. This is a potentially lossy optimization for workspaces
2086 // with many modules (such as google-cloud-go, which has 145 modules as
2087 // of writing).
2088 //
2089 // While it is theoretically possible that a change in workspace module A
2090 // could affect the mod-tidiness of workspace module B (if B transitively
2091 // requires A), such changes are probably unlikely and not worth the
2092 // penalty of re-running go mod tidy for everything. Note that mod tidy
2093 // ignores GOWORK, so the two modules would have to be related by a chain
2094 // of replace directives.
2095 //
2096 // We could improve accuracy by inspecting replace directives, using
2097 // overlays in go mod tidy, and/or checking for metadata changes from the
2098 // on-disk content.
2099 //
2100 // Note that we iterate the modTidyHandles map here, rather than e.g.
2101 // using nearestModFile, because we don't have access to an accurate
2102 // FileSource at this point in the snapshot clone.
2103 const onlyInvalidateMostRelevant = true
2104 if onlyInvalidateMostRelevant {
2105 deleteMostRelevantModFile(result.modTidyHandles, uri)
2106 } else {
2107 result.modTidyHandles.Clear()
2108 }
2109
2110 // TODO(rfindley): should we apply the above heuristic to mod vuln
2111 // or mod handles as well?
2112 //
2113 // TODO(rfindley): no tests fail if I delete the below line.
Robert Findleyb15dac22022-08-30 14:40:12 -04002114 result.modWhyHandles.Clear()
Robert Findley7cda55e2022-11-22 12:09:11 -05002115 result.modVulnHandles.Clear()
Robert Findleyb15dac22022-08-30 14:40:12 -04002116 }
2117
2118 result.parseModHandles.Delete(uri)
2119 result.parseWorkHandles.Delete(uri)
2120 // Handle the invalidated file; it may have new contents or not exist.
2121 if !change.exists {
2122 result.files.Delete(uri)
2123 } else {
Robert Findley59742582023-05-26 12:59:48 -04002124 // TODO(golang/go#57558): the line below is strictly necessary to ensure
2125 // that snapshots have each overlay, but it is problematic that we must
2126 // set any content in snapshot.clone: if the file has changed, let it be
2127 // re-read.
Robert Findleyb15dac22022-08-30 14:40:12 -04002128 result.files.Set(uri, change.fileHandle)
2129 }
2130
2131 // Make sure to remove the changed file from the unloadable set.
Rob Findleyb1609712023-07-17 23:09:28 -04002132 //
2133 // TODO(rfindley): this also looks wrong, as typing in an unloadable file
2134 // will result in repeated reloads. We should only delete if metadata
2135 // changed.
Robert Findleyb15dac22022-08-30 14:40:12 -04002136 delete(result.unloadableFiles, uri)
2137 }
2138
2139 // Deleting an import can cause list errors due to import cycles to be
2140 // resolved. The best we can do without parsing the list error message is to
2141 // hope that list errors may have been resolved by a deleted import.
2142 //
2143 // We could do better by parsing the list error message. We already do this
2144 // to assign a better range to the list error, but for such critical
2145 // functionality as metadata, it's better to be conservative until it proves
2146 // impractical.
2147 //
2148 // We could also do better by looking at which imports were deleted and
2149 // trying to find cycles they are involved in. This fails when the file goes
2150 // from an unparseable state to a parseable state, as we don't have a
2151 // starting point to compare with.
2152 if anyImportDeleted {
2153 for id, metadata := range s.meta.metadata {
2154 if len(metadata.Errors) > 0 {
2155 directIDs[id] = true
2156 }
2157 }
2158 }
2159
2160 // Adding a file can resolve missing dependencies from existing packages.
2161 //
2162 // We could be smart here and try to guess which packages may have been
2163 // fixed, but until that proves necessary, just invalidate metadata for any
2164 // package with missing dependencies.
2165 if anyFileAdded {
2166 for id, metadata := range s.meta.metadata {
Alan Donovan21f61272022-10-20 14:32:57 -04002167 for _, impID := range metadata.DepsByImpPath {
2168 if impID == "" { // missing import
2169 directIDs[id] = true
2170 break
2171 }
Robert Findleyb15dac22022-08-30 14:40:12 -04002172 }
2173 }
2174 }
2175
2176 // Invalidate reverse dependencies too.
2177 // idsToInvalidate keeps track of transitive reverse dependencies.
2178 // If an ID is present in the map, invalidate its types.
2179 // If an ID's value is true, invalidate its metadata too.
2180 idsToInvalidate := map[PackageID]bool{}
2181 var addRevDeps func(PackageID, bool)
2182 addRevDeps = func(id PackageID, invalidateMetadata bool) {
2183 current, seen := idsToInvalidate[id]
2184 newInvalidateMetadata := current || invalidateMetadata
2185
2186 // If we've already seen this ID, and the value of invalidate
2187 // metadata has not changed, we can return early.
2188 if seen && current == newInvalidateMetadata {
2189 return
2190 }
2191 idsToInvalidate[id] = newInvalidateMetadata
2192 for _, rid := range s.meta.importedBy[id] {
2193 addRevDeps(rid, invalidateMetadata)
2194 }
2195 }
2196 for id, invalidateMetadata := range directIDs {
2197 addRevDeps(id, invalidateMetadata)
2198 }
2199
Robert Findley1b2d1bd2023-03-16 18:52:13 -04002200 // Invalidated package information.
2201 for id, invalidateMetadata := range idsToInvalidate {
2202 if _, ok := directIDs[id]; ok || invalidateMetadata {
2203 result.packages.Delete(id)
2204 } else {
2205 if entry, hit := result.packages.Get(id); hit {
2206 ph := entry.(*packageHandle).clone(false)
2207 result.packages.Set(id, ph, nil)
2208 }
2209 }
Robert Findley21d22562023-02-21 12:26:27 -05002210 result.activePackages.Delete(id)
Alan Donovanff22fab2022-11-18 14:47:36 -05002211 }
2212
Robert Findleyb15dac22022-08-30 14:40:12 -04002213 // If a file has been deleted, we must delete metadata for all packages
2214 // containing that file.
2215 //
2216 // TODO(rfindley): why not keep invalid metadata in this case? If we
2217 // otherwise allow operate on invalid metadata, why not continue to do so,
2218 // skipping the missing file?
2219 skipID := map[PackageID]bool{}
2220 for _, c := range changes {
2221 if c.exists {
2222 continue
2223 }
2224 // The file has been deleted.
2225 if ids, ok := s.meta.ids[c.fileHandle.URI()]; ok {
2226 for _, id := range ids {
2227 skipID[id] = true
2228 }
2229 }
2230 }
2231
2232 // Any packages that need loading in s still need loading in the new
2233 // snapshot.
2234 for k, v := range s.shouldLoad {
2235 if result.shouldLoad == nil {
2236 result.shouldLoad = make(map[PackageID][]PackagePath)
2237 }
2238 result.shouldLoad[k] = v
2239 }
2240
Robert Findleyb15dac22022-08-30 14:40:12 -04002241 // Compute which metadata updates are required. We only need to invalidate
2242 // packages directly containing the affected file, and only if it changed in
2243 // a relevant way.
Alan Donovan85bf7a82022-11-18 12:03:11 -05002244 metadataUpdates := make(map[PackageID]*source.Metadata)
Robert Findleyb15dac22022-08-30 14:40:12 -04002245 for k, v := range s.meta.metadata {
2246 invalidateMetadata := idsToInvalidate[k]
2247
2248 // For metadata that has been newly invalidated, capture package paths
2249 // requiring reloading in the shouldLoad map.
Alan Donovan3c3713e2022-11-10 13:02:38 -05002250 if invalidateMetadata && !source.IsCommandLineArguments(v.ID) {
Robert Findleyb15dac22022-08-30 14:40:12 -04002251 if result.shouldLoad == nil {
2252 result.shouldLoad = make(map[PackageID][]PackagePath)
2253 }
2254 needsReload := []PackagePath{v.PkgPath}
2255 if v.ForTest != "" && v.ForTest != v.PkgPath {
2256 // When reloading test variants, always reload their ForTest package as
2257 // well. Otherwise, we may miss test variants in the resulting load.
2258 //
2259 // TODO(rfindley): is this actually sufficient? Is it possible that
2260 // other test variants may be invalidated? Either way, we should
2261 // determine exactly what needs to be reloaded here.
2262 needsReload = append(needsReload, v.ForTest)
2263 }
2264 result.shouldLoad[k] = needsReload
2265 }
2266
2267 // Check whether the metadata should be deleted.
Robert Findley5a4eba52022-11-03 18:28:39 -04002268 if skipID[k] || invalidateMetadata {
Robert Findleyb15dac22022-08-30 14:40:12 -04002269 metadataUpdates[k] = nil
2270 continue
2271 }
Robert Findleyb15dac22022-08-30 14:40:12 -04002272 }
2273
2274 // Update metadata, if necessary.
2275 result.meta = s.meta.Clone(metadataUpdates)
2276
2277 // Update workspace and active packages, if necessary.
2278 if result.meta != s.meta || anyFileOpenedOrClosed {
2279 result.workspacePackages = computeWorkspacePackagesLocked(result, result.meta)
Robert Findley21d22562023-02-21 12:26:27 -05002280 result.resetActivePackagesLocked()
Robert Findleyb15dac22022-08-30 14:40:12 -04002281 } else {
2282 result.workspacePackages = s.workspacePackages
2283 }
2284
2285 // Don't bother copying the importedBy graph,
2286 // as it changes each time we update metadata.
2287
Robert Findley5a4eba52022-11-03 18:28:39 -04002288 // TODO(rfindley): consolidate the this workspace mode detection with
2289 // workspace invalidation.
2290 workspaceModeChanged := s.workspaceMode() != result.workspaceMode()
2291
Robert Findleyb15dac22022-08-30 14:40:12 -04002292 // If the snapshot's workspace mode has changed, the packages loaded using
2293 // the previous mode are no longer relevant, so clear them out.
2294 if workspaceModeChanged {
2295 result.workspacePackages = map[PackageID]PackagePath{}
2296 }
Robert Findleyb15dac22022-08-30 14:40:12 -04002297 return result, release
2298}
2299
Rob Findley43b02ea2023-05-19 16:18:30 -04002300// deleteMostRelevantModFile deletes the mod file most likely to be the mod
2301// file for the changed URI, if it exists.
2302//
2303// Specifically, this is the longest mod file path in a directory containing
2304// changed. This might not be accurate if there is another mod file closer to
2305// changed that happens not to be present in the map, but that's OK: the goal
2306// of this function is to guarantee that IF the nearest mod file is present in
2307// the map, it is invalidated.
2308func deleteMostRelevantModFile(m *persistent.Map, changed span.URI) {
2309 var mostRelevant span.URI
2310 changedFile := changed.Filename()
2311
2312 m.Range(func(key, value interface{}) {
2313 modURI := key.(span.URI)
2314 if len(modURI) > len(mostRelevant) {
2315 if source.InDir(filepath.Dir(modURI.Filename()), changedFile) {
2316 mostRelevant = modURI
2317 }
2318 }
2319 })
2320 if mostRelevant != "" {
2321 m.Delete(mostRelevant)
2322 }
2323}
2324
Robert Findleyb15dac22022-08-30 14:40:12 -04002325// invalidatedPackageIDs returns all packages invalidated by a change to uri.
2326// If we haven't seen this URI before, we guess based on files in the same
2327// directory. This is of course incorrect in build systems where packages are
2328// not organized by directory.
2329//
2330// If packageFileChanged is set, the file is either a new file, or has a new
2331// package name. In this case, all known packages in the directory will be
2332// invalidated.
2333func invalidatedPackageIDs(uri span.URI, known map[span.URI][]PackageID, packageFileChanged bool) map[PackageID]struct{} {
2334 invalidated := make(map[PackageID]struct{})
2335
2336 // At a minimum, we invalidate packages known to contain uri.
2337 for _, id := range known[uri] {
2338 invalidated[id] = struct{}{}
2339 }
2340
2341 // If the file didn't move to a new package, we should only invalidate the
2342 // packages it is currently contained inside.
2343 if !packageFileChanged && len(invalidated) > 0 {
2344 return invalidated
2345 }
2346
2347 // This is a file we don't yet know about, or which has moved packages. Guess
2348 // relevant packages by considering files in the same directory.
2349
2350 // Cache of FileInfo to avoid unnecessary stats for multiple files in the
2351 // same directory.
2352 stats := make(map[string]struct {
2353 os.FileInfo
2354 error
2355 })
2356 getInfo := func(dir string) (os.FileInfo, error) {
2357 if res, ok := stats[dir]; ok {
2358 return res.FileInfo, res.error
2359 }
2360 fi, err := os.Stat(dir)
2361 stats[dir] = struct {
2362 os.FileInfo
2363 error
2364 }{fi, err}
2365 return fi, err
2366 }
2367 dir := filepath.Dir(uri.Filename())
2368 fi, err := getInfo(dir)
2369 if err == nil {
2370 // Aggregate all possibly relevant package IDs.
2371 for knownURI, ids := range known {
2372 knownDir := filepath.Dir(knownURI.Filename())
2373 knownFI, err := getInfo(knownDir)
2374 if err != nil {
2375 continue
2376 }
2377 if os.SameFile(fi, knownFI) {
2378 for _, id := range ids {
2379 invalidated[id] = struct{}{}
2380 }
2381 }
2382 }
2383 }
2384 return invalidated
2385}
2386
Robert Findleyb15dac22022-08-30 14:40:12 -04002387// fileWasSaved reports whether the FileHandle passed in has been saved. It
2388// accomplishes this by checking to see if the original and current FileHandles
2389// are both overlays, and if the current FileHandle is saved while the original
2390// FileHandle was not saved.
2391func fileWasSaved(originalFH, currentFH source.FileHandle) bool {
Robert Findleya7f033a2023-01-19 18:12:18 -05002392 c, ok := currentFH.(*Overlay)
Robert Findleyb15dac22022-08-30 14:40:12 -04002393 if !ok || c == nil {
2394 return true
2395 }
Robert Findleya7f033a2023-01-19 18:12:18 -05002396 o, ok := originalFH.(*Overlay)
Robert Findleyb15dac22022-08-30 14:40:12 -04002397 if !ok || o == nil {
2398 return c.saved
2399 }
2400 return !o.saved && c.saved
2401}
2402
2403// metadataChanges detects features of the change from oldFH->newFH that may
2404// affect package metadata.
2405//
2406// It uses lockedSnapshot to access cached parse information. lockedSnapshot
2407// must be locked.
2408//
2409// The result parameters have the following meaning:
2410// - invalidate means that package metadata for packages containing the file
2411// should be invalidated.
2412// - pkgFileChanged means that the file->package associates for the file have
2413// changed (possibly because the file is new, or because its package name has
2414// changed).
2415// - importDeleted means that an import has been deleted, or we can't
2416// determine if an import was deleted due to errors.
2417func metadataChanges(ctx context.Context, lockedSnapshot *snapshot, oldFH, newFH source.FileHandle) (invalidate, pkgFileChanged, importDeleted bool) {
2418 if oldFH == nil || newFH == nil { // existential changes
2419 changed := (oldFH == nil) != (newFH == nil)
2420 return changed, changed, (newFH == nil) // we don't know if an import was deleted
2421 }
2422
2423 // If the file hasn't changed, there's no need to reload.
2424 if oldFH.FileIdentity() == newFH.FileIdentity() {
2425 return false, false, false
2426 }
2427
Robert Findleyb2225202023-03-05 11:23:52 -05002428 fset := token.NewFileSet()
Robert Findleyb15dac22022-08-30 14:40:12 -04002429 // Parse headers to compare package names and imports.
Rob Findley4f747862023-06-14 13:53:41 -04002430 oldHeads, oldErr := lockedSnapshot.view.parseCache.parseFiles(ctx, fset, source.ParseHeader, false, oldFH)
2431 newHeads, newErr := lockedSnapshot.view.parseCache.parseFiles(ctx, fset, source.ParseHeader, false, newFH)
Robert Findleyb15dac22022-08-30 14:40:12 -04002432
2433 if oldErr != nil || newErr != nil {
Robert Findleyae056092023-02-19 21:14:18 -05002434 // TODO(rfindley): we can get here if newFH does not exist. There is
2435 // asymmetry, in that newFH may be non-nil even if the underlying file does
2436 // not exist.
Robert Findleyb15dac22022-08-30 14:40:12 -04002437 //
2438 // We should not produce a non-nil filehandle for a file that does not exist.
2439 errChanged := (oldErr == nil) != (newErr == nil)
2440 return errChanged, errChanged, (newErr != nil) // we don't know if an import was deleted
2441 }
2442
Robert Findleyae056092023-02-19 21:14:18 -05002443 oldHead := oldHeads[0]
2444 newHead := newHeads[0]
2445
Robert Findleyb15dac22022-08-30 14:40:12 -04002446 // `go list` fails completely if the file header cannot be parsed. If we go
2447 // from a non-parsing state to a parsing state, we should reload.
2448 if oldHead.ParseErr != nil && newHead.ParseErr == nil {
2449 return true, true, true // We don't know what changed, so fall back on full invalidation.
2450 }
2451
2452 // If a package name has changed, the set of package imports may have changed
2453 // in ways we can't detect here. Assume an import has been deleted.
2454 if oldHead.File.Name.Name != newHead.File.Name.Name {
2455 return true, true, true
2456 }
2457
2458 // Check whether package imports have changed. Only consider potentially
2459 // valid imports paths.
2460 oldImports := validImports(oldHead.File.Imports)
2461 newImports := validImports(newHead.File.Imports)
2462
2463 for path := range newImports {
2464 if _, ok := oldImports[path]; ok {
2465 delete(oldImports, path)
2466 } else {
2467 invalidate = true // a new, potentially valid import was added
2468 }
2469 }
2470
2471 if len(oldImports) > 0 {
2472 invalidate = true
2473 importDeleted = true
2474 }
2475
2476 // If the change does not otherwise invalidate metadata, get the full ASTs in
2477 // order to check magic comments.
2478 //
2479 // Note: if this affects performance we can probably avoid parsing in the
2480 // common case by first scanning the source for potential comments.
2481 if !invalidate {
Rob Findley4f747862023-06-14 13:53:41 -04002482 origFulls, oldErr := lockedSnapshot.view.parseCache.parseFiles(ctx, fset, source.ParseFull, false, oldFH)
2483 newFulls, newErr := lockedSnapshot.view.parseCache.parseFiles(ctx, fset, source.ParseFull, false, newFH)
Robert Findleyb15dac22022-08-30 14:40:12 -04002484 if oldErr == nil && newErr == nil {
Robert Findleyae056092023-02-19 21:14:18 -05002485 invalidate = magicCommentsChanged(origFulls[0].File, newFulls[0].File)
Robert Findleyb15dac22022-08-30 14:40:12 -04002486 } else {
2487 // At this point, we shouldn't ever fail to produce a ParsedGoFile, as
2488 // we're already past header parsing.
2489 bug.Reportf("metadataChanges: unparseable file %v (old error: %v, new error: %v)", oldFH.URI(), oldErr, newErr)
2490 }
2491 }
2492
2493 return invalidate, pkgFileChanged, importDeleted
2494}
2495
Robert Findleyb15dac22022-08-30 14:40:12 -04002496func magicCommentsChanged(original *ast.File, current *ast.File) bool {
2497 oldComments := extractMagicComments(original)
2498 newComments := extractMagicComments(current)
2499 if len(oldComments) != len(newComments) {
2500 return true
2501 }
2502 for i := range oldComments {
2503 if oldComments[i] != newComments[i] {
2504 return true
2505 }
2506 }
2507 return false
2508}
2509
2510// validImports extracts the set of valid import paths from imports.
2511func validImports(imports []*ast.ImportSpec) map[string]struct{} {
2512 m := make(map[string]struct{})
2513 for _, spec := range imports {
2514 if path := spec.Path.Value; validImportPath(path) {
2515 m[path] = struct{}{}
2516 }
2517 }
2518 return m
2519}
2520
2521func validImportPath(path string) bool {
2522 path, err := strconv.Unquote(path)
2523 if err != nil {
2524 return false
2525 }
2526 if path == "" {
2527 return false
2528 }
2529 if path[len(path)-1] == '/' {
2530 return false
2531 }
2532 return true
2533}
2534
2535var buildConstraintOrEmbedRe = regexp.MustCompile(`^//(go:embed|go:build|\s*\+build).*`)
2536
2537// extractMagicComments finds magic comments that affect metadata in f.
2538func extractMagicComments(f *ast.File) []string {
2539 var results []string
2540 for _, cg := range f.Comments {
2541 for _, c := range cg.List {
2542 if buildConstraintOrEmbedRe.MatchString(c.Text) {
2543 results = append(results, c.Text)
2544 }
2545 }
2546 }
2547 return results
2548}
2549
2550func (s *snapshot) BuiltinFile(ctx context.Context) (*source.ParsedGoFile, error) {
2551 s.AwaitInitialized(ctx)
2552
2553 s.mu.Lock()
2554 builtin := s.builtin
2555 s.mu.Unlock()
2556
2557 if builtin == "" {
2558 return nil, fmt.Errorf("no builtin package for view %s", s.view.name)
2559 }
2560
Alan Donovan36ed0b12023-03-13 14:20:23 -04002561 fh, err := s.ReadFile(ctx, builtin)
Robert Findleyb15dac22022-08-30 14:40:12 -04002562 if err != nil {
2563 return nil, err
2564 }
Robert Findley912861f2023-03-03 20:38:55 -05002565 // For the builtin file only, we need syntactic object resolution
2566 // (since we can't type check).
2567 mode := source.ParseFull &^ source.SkipObjectResolution
Rob Findley4f747862023-06-14 13:53:41 -04002568 pgfs, err := s.view.parseCache.parseFiles(ctx, token.NewFileSet(), mode, false, fh)
Robert Findley4b112032023-03-28 20:06:14 -04002569 if err != nil {
2570 return nil, err
2571 }
2572 return pgfs[0], nil
Robert Findleyb15dac22022-08-30 14:40:12 -04002573}
2574
Rob Findley1c9fe3f2023-05-11 14:33:05 -04002575func (s *snapshot) IsBuiltin(uri span.URI) bool {
Robert Findleyb15dac22022-08-30 14:40:12 -04002576 s.mu.Lock()
2577 defer s.mu.Unlock()
2578 // We should always get the builtin URI in a canonical form, so use simple
2579 // string comparison here. span.CompareURI is too expensive.
2580 return uri == s.builtin
2581}
2582
2583func (s *snapshot) setBuiltin(path string) {
2584 s.mu.Lock()
2585 defer s.mu.Unlock()
2586
2587 s.builtin = span.URIFromPath(path)
2588}