From c9619e8fac0c794c0683e392d8728c5de9edf4bb Mon Sep 17 00:00:00 2001 From: Heschi Kreinick Date: Sun, 26 Jul 2020 18:01:39 -0400 Subject: [PATCH] internal/lsp: separate LSP files from FS files FileHandle currently includes LSP-level information about Version and Session. That's dangerous, because the cache operates in terms of URIs and content only -- we explicitly want to share results across sessions and versions if they happen to be the same. Split the LSP information into separate types, VersionedFileHandle and VersionedFileIdentity. Change-Id: I158646b783375b58245468599301e2a29c657e71 Reviewed-on: https://go-review.googlesource.com/c/tools/+/245058 Run-TryBot: Heschi Kreinick TryBot-Result: Gobot Gobot Reviewed-by: Rebecca Stambler Reviewed-by: Robert Findley --- internal/lsp/cache/cache.go | 12 ++--- internal/lsp/cache/check.go | 3 +- internal/lsp/cache/mod.go | 13 +++--- internal/lsp/cache/mod_tidy.go | 4 +- internal/lsp/cache/parse.go | 4 +- internal/lsp/cache/session.go | 67 ++++++++++++++++++++------- internal/lsp/cache/snapshot.go | 21 +++++---- internal/lsp/cache/view.go | 4 +- internal/lsp/code_action.go | 2 +- internal/lsp/debug/serve.go | 2 +- internal/lsp/diagnostics.go | 9 ++-- internal/lsp/general.go | 2 +- internal/lsp/lsp_test.go | 2 +- internal/lsp/mod/diagnostics.go | 8 ++-- internal/lsp/server.go | 3 +- internal/lsp/source/command.go | 2 +- internal/lsp/source/diagnostics.go | 34 +++++++------- internal/lsp/source/gc_annotations.go | 7 ++- internal/lsp/source/view.go | 55 +++++++++++----------- 19 files changed, 141 insertions(+), 113 deletions(-) diff --git a/internal/lsp/cache/cache.go b/internal/lsp/cache/cache.go index 530a313bc3..11ee52cc98 100644 --- a/internal/lsp/cache/cache.go +++ b/internal/lsp/cache/cache.go @@ -139,15 +139,15 @@ func (h *fileHandle) Kind() source.FileKind { return source.DetectLanguage("", h.uri.Filename()) } -func (h *fileHandle) Version() float64 { - return 0 +func (h *fileHandle) Hash() string { + return h.hash } -func (h *fileHandle) Identity() source.FileIdentity { +func (h *fileHandle) FileIdentity() source.FileIdentity { return source.FileIdentity{ - URI: h.uri, - Identifier: h.hash, - Kind: h.Kind(), + URI: h.uri, + Hash: h.hash, + Kind: h.Kind(), } } diff --git a/internal/lsp/cache/check.go b/internal/lsp/cache/check.go index ec997481b8..05a173fb47 100644 --- a/internal/lsp/cache/check.go +++ b/internal/lsp/cache/check.go @@ -174,7 +174,8 @@ func checkPackageKey(ctx context.Context, id packageID, pghs []*parseGoHandle, c b.WriteString(string(dep)) } for _, cgf := range pghs { - b.WriteString(cgf.file.Identity().String()) + b.WriteString(string(cgf.file.URI())) + b.WriteString(cgf.file.FileIdentity().Hash) } return packageHandleKey(hashContents(b.Bytes())) } diff --git a/internal/lsp/cache/mod.go b/internal/lsp/cache/mod.go index f31fa4fe64..395c0e3294 100644 --- a/internal/lsp/cache/mod.go +++ b/internal/lsp/cache/mod.go @@ -54,7 +54,7 @@ func (s *snapshot) ParseMod(ctx context.Context, modFH source.FileHandle) (*sour return handle.parse(ctx, s) } - h := s.view.session.cache.store.Bind(modFH.Identity().String(), func(ctx context.Context, _ memoize.Arg) interface{} { + h := s.view.session.cache.store.Bind(modFH.FileIdentity(), func(ctx context.Context, _ memoize.Arg) interface{} { _, done := event.Start(ctx, "cache.ParseModHandle", tag.URI.Of(modFH.URI())) defer done() @@ -96,7 +96,7 @@ func (s *snapshot) sumFH(ctx context.Context, modFH source.FileHandle) (source.F // cache. Avoid (*snapshot).GetFile here, as we don't want to add // nonexistent file handles to the snapshot if the file does not exist. sumURI := span.URIFromPath(sumFilename(modFH.URI())) - sumFH := s.FindFile(sumURI) + var sumFH source.FileHandle = s.FindFile(sumURI) if sumFH == nil { var err error sumFH, err = s.view.session.cache.getFile(ctx, sumURI) @@ -158,8 +158,9 @@ func extractModParseErrors(uri span.URI, m *protocol.ColumnMapper, parseErr erro // modKey is uniquely identifies cached data for `go mod why` or dependencies // to upgrade. type modKey struct { - sessionID, cfg, mod, view string - verb modAction + sessionID, cfg, view string + mod source.FileIdentity + verb modAction } type modAction int @@ -201,7 +202,7 @@ func (s *snapshot) ModWhy(ctx context.Context) (map[string]string, error) { key := modKey{ sessionID: s.view.session.id, cfg: hashConfig(s.config(ctx)), - mod: fh.Identity().String(), + mod: fh.FileIdentity(), view: s.view.root.Filename(), verb: why, } @@ -281,7 +282,7 @@ func (s *snapshot) ModUpgrade(ctx context.Context) (map[string]string, error) { key := modKey{ sessionID: s.view.session.id, cfg: hashConfig(cfg), - mod: fh.Identity().String(), + mod: fh.FileIdentity(), view: s.view.root.Filename(), verb: upgrade, } diff --git a/internal/lsp/cache/mod_tidy.go b/internal/lsp/cache/mod_tidy.go index 2ecfc77e29..f9e57ccf9a 100644 --- a/internal/lsp/cache/mod_tidy.go +++ b/internal/lsp/cache/mod_tidy.go @@ -25,7 +25,7 @@ import ( type modTidyKey struct { sessionID string cfg string - gomod string + gomod source.FileIdentity imports string unsavedOverlays string view string @@ -85,7 +85,7 @@ func (s *snapshot) ModTidy(ctx context.Context) (*source.TidiedModule, error) { view: s.view.root.Filename(), imports: importHash, unsavedOverlays: overlayHash, - gomod: modFH.Identity().String(), + gomod: modFH.FileIdentity(), cfg: hashConfig(cfg), } h := s.view.session.cache.store.Bind(key, func(ctx context.Context, arg memoize.Arg) interface{} { diff --git a/internal/lsp/cache/parse.go b/internal/lsp/cache/parse.go index 0bb1519eb0..8f92b37bf7 100644 --- a/internal/lsp/cache/parse.go +++ b/internal/lsp/cache/parse.go @@ -25,7 +25,7 @@ import ( // parseKey uniquely identifies a parsed Go file. type parseKey struct { - file string // FileIdentity.String() + file source.FileIdentity mode source.ParseMode } @@ -53,7 +53,7 @@ type parseGoData struct { func (c *Cache) parseGoHandle(ctx context.Context, fh source.FileHandle, mode source.ParseMode) *parseGoHandle { key := parseKey{ - file: fh.Identity().String(), + file: fh.FileIdentity(), mode: mode, } parseHandle := c.store.Bind(key, func(ctx context.Context, arg memoize.Arg) interface{} { diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go index 3feddfb669..37115f6f15 100644 --- a/internal/lsp/cache/session.go +++ b/internal/lsp/cache/session.go @@ -46,7 +46,7 @@ type overlay struct { version float64 kind source.FileKind - // saved is true if a file has been saved on disk, + // saved is true if a file matches the state on disk, // and therefore does not need to be part of the overlay sent to go/packages. saved bool } @@ -55,13 +55,19 @@ func (o *overlay) Read() ([]byte, error) { return o.text, nil } -func (o *overlay) Identity() source.FileIdentity { +func (o *overlay) FileIdentity() source.FileIdentity { return source.FileIdentity{ - URI: o.uri, - Identifier: o.hash, - SessionID: o.session.id, - Version: o.version, - Kind: o.kind, + URI: o.uri, + Hash: o.hash, + Kind: o.kind, + } +} + +func (o *overlay) VersionedFileIdentity() source.VersionedFileIdentity { + return source.VersionedFileIdentity{ + URI: o.uri, + SessionID: o.session.id, + Version: o.version, } } @@ -77,9 +83,34 @@ func (o *overlay) Version() float64 { return o.version } -func (o *overlay) Session() source.Session { return o.session } -func (o *overlay) Saved() bool { return o.saved } -func (o *overlay) Data() []byte { return o.text } +func (o *overlay) Session() string { + return o.session.id +} + +func (o *overlay) Saved() bool { + return o.saved +} + +// closedFile implements LSPFile for a file that the editor hasn't told us about. +type closedFile struct { + source.FileHandle +} + +func (c *closedFile) VersionedFileIdentity() source.VersionedFileIdentity { + return source.VersionedFileIdentity{ + URI: c.FileHandle.URI(), + SessionID: "", + Version: 0, + } +} + +func (c *closedFile) Session() string { + return "" +} + +func (c *closedFile) Version() float64 { + return 0 +} func (s *Session) ID() string { return s.id } func (s *Session) String() string { return s.id } @@ -146,7 +177,7 @@ func (s *Session) createView(ctx context.Context, name string, folder span.URI, packages: make(map[packageKey]*packageHandle), ids: make(map[span.URI][]packageID), metadata: make(map[packageID]*metadata), - files: make(map[span.URI]source.FileHandle), + files: make(map[span.URI]source.VersionedFileHandle), importedBy: make(map[packageID][]packageID), actions: make(map[actionKey]*actionHandle), workspacePackages: make(map[packageID]packagePath), @@ -332,7 +363,7 @@ func (s *Session) ModifyFiles(ctx context.Context, changes []source.FileModifica } func (s *Session) DidModifyFiles(ctx context.Context, changes []source.FileModification) ([]source.Snapshot, []func(), []span.URI, error) { - views := make(map[*View]map[span.URI]source.FileHandle) + views := make(map[*View]map[span.URI]source.VersionedFileHandle) // Keep track of deleted files so that we can clear their diagnostics. // A file might be re-created after deletion, so only mark files that @@ -362,21 +393,21 @@ func (s *Session) DidModifyFiles(ctx context.Context, changes []source.FileModif return nil, nil, nil, err } if _, ok := views[view]; !ok { - views[view] = make(map[span.URI]source.FileHandle) + views[view] = make(map[span.URI]source.VersionedFileHandle) } var ( - fh source.FileHandle - ok bool - err error + fh source.VersionedFileHandle + ok bool ) if fh, ok = overlays[c.URI]; ok { views[view][c.URI] = fh delete(deletions, c.URI) } else { - fh, err = s.cache.getFile(ctx, c.URI) + fsFile, err := s.cache.getFile(ctx, c.URI) if err != nil { return nil, nil, nil, err } + fh = &closedFile{fsFile} views[view][c.URI] = fh if _, err := fh.Read(); err != nil { deletions[c.URI] = struct{}{} @@ -488,7 +519,7 @@ func (s *Session) updateOverlays(ctx context.Context, changes []source.FileModif return nil, err } _, readErr := fh.Read() - sameContentOnDisk = (readErr == nil && fh.Identity().Identifier == hash) + sameContentOnDisk = (readErr == nil && fh.FileIdentity().Hash == hash) } o = &overlay{ session: s, diff --git a/internal/lsp/cache/snapshot.go b/internal/lsp/cache/snapshot.go index 5538a60118..217e5a8cb6 100644 --- a/internal/lsp/cache/snapshot.go +++ b/internal/lsp/cache/snapshot.go @@ -58,7 +58,7 @@ type snapshot struct { // files maps file URIs to their corresponding FileHandles. // It may invalidated when a file's content changes. - files map[span.URI]source.FileHandle + files map[span.URI]source.VersionedFileHandle // packages maps a packageKey to a set of packageHandles to which that file belongs. // It may be invalidated when a file's content changes. @@ -224,7 +224,7 @@ func (s *snapshot) buildOverlay() map[string][]byte { return overlays } -func hashUnsavedOverlays(files map[span.URI]source.FileHandle) string { +func hashUnsavedOverlays(files map[span.URI]source.VersionedFileHandle) string { var unsaved []string for uri, fh := range files { if overlay, ok := fh.(*overlay); ok && !overlay.saved { @@ -574,7 +574,7 @@ func (s *snapshot) isWorkspacePackage(id packageID) (packagePath, bool) { return scope, ok } -func (s *snapshot) FindFile(uri span.URI) source.FileHandle { +func (s *snapshot) FindFile(uri span.URI) source.VersionedFileHandle { f, err := s.view.getFile(uri) if err != nil { return nil @@ -588,7 +588,7 @@ func (s *snapshot) FindFile(uri span.URI) source.FileHandle { // GetFile returns a File for the given URI. It will always succeed because it // adds the file to the managed set if needed. -func (s *snapshot) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) { +func (s *snapshot) GetFile(ctx context.Context, uri span.URI) (source.VersionedFileHandle, error) { f, err := s.view.getFile(uri) if err != nil { return nil, err @@ -605,8 +605,9 @@ func (s *snapshot) GetFile(ctx context.Context, uri span.URI) (source.FileHandle if err != nil { return nil, err } - s.files[f.URI()] = fh - return fh, nil + closed := &closedFile{fh} + s.files[f.URI()] = closed + return closed, nil } func (s *snapshot) IsOpen(uri span.URI) bool { @@ -748,7 +749,7 @@ func contains(views []*View, view *View) bool { return false } -func (s *snapshot) clone(ctx context.Context, withoutURIs map[span.URI]source.FileHandle, forceReloadMetadata bool) *snapshot { +func (s *snapshot) clone(ctx context.Context, withoutURIs map[span.URI]source.VersionedFileHandle, forceReloadMetadata bool) *snapshot { s.mu.Lock() defer s.mu.Unlock() @@ -761,7 +762,7 @@ func (s *snapshot) clone(ctx context.Context, withoutURIs map[span.URI]source.Fi metadata: make(map[packageID]*metadata), packages: make(map[packageKey]*packageHandle), actions: make(map[actionKey]*actionHandle), - files: make(map[span.URI]source.FileHandle), + files: make(map[span.URI]source.VersionedFileHandle), workspacePackages: make(map[packageID]packagePath), unloadableFiles: make(map[span.URI]struct{}), parseModHandles: make(map[span.URI]*parseModHandle), @@ -955,7 +956,7 @@ func (s *snapshot) shouldInvalidateMetadata(ctx context.Context, originalFH, cur return currentFH.Kind() == source.Go } // If the file hasn't changed, there's no need to reload. - if originalFH.Identity().String() == currentFH.Identity().String() { + if originalFH.FileIdentity() == currentFH.FileIdentity() { return false } // If a go.mod file's contents have changed, always invalidate metadata. @@ -1017,7 +1018,7 @@ func (s *snapshot) buildBuiltinPackage(ctx context.Context, goFiles []string) er if err != nil { return err } - h := s.view.session.cache.store.Bind(fh.Identity(), func(ctx context.Context, arg memoize.Arg) interface{} { + h := s.view.session.cache.store.Bind(fh.FileIdentity(), func(ctx context.Context, arg memoize.Arg) interface{} { snapshot := arg.(*snapshot) pgh := snapshot.view.session.cache.parseGoHandle(ctx, fh, source.ParseFull) diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go index 14672011d5..3fbaa683a4 100644 --- a/internal/lsp/cache/view.go +++ b/internal/lsp/cache/view.go @@ -326,7 +326,7 @@ func (v *View) RunProcessEnvFunc(ctx context.Context, fn func(*imports.Options) if err != nil { return err } - modFileIdentifier = modFH.Identity().Identifier + modFileIdentifier = modFH.FileIdentity().Hash } if v.sumURI != "" { sumFH, err = v.session.cache.getFile(ctx, v.sumURI) @@ -695,7 +695,7 @@ func (v *View) awaitInitialized(ctx context.Context) { // invalidateContent invalidates the content of a Go file, // including any position and type information that depends on it. // It returns true if we were already tracking the given file, false otherwise. -func (v *View) invalidateContent(ctx context.Context, uris map[span.URI]source.FileHandle, forceReloadMetadata bool) (source.Snapshot, func()) { +func (v *View) invalidateContent(ctx context.Context, uris map[span.URI]source.VersionedFileHandle, forceReloadMetadata bool) (source.Snapshot, func()) { // Detach the context so that content invalidation cannot be canceled. ctx = xcontext.Detach(ctx) diff --git a/internal/lsp/code_action.go b/internal/lsp/code_action.go index 584b0cc6a2..425bfe35db 100644 --- a/internal/lsp/code_action.go +++ b/internal/lsp/code_action.go @@ -440,7 +440,7 @@ func extractionFixes(ctx context.Context, snapshot source.Snapshot, pkg source.P return actions, nil } -func documentChanges(fh source.FileHandle, edits []protocol.TextEdit) []protocol.TextDocumentEdit { +func documentChanges(fh source.VersionedFileHandle, edits []protocol.TextEdit) []protocol.TextDocumentEdit { return []protocol.TextDocumentEdit{ { TextDocument: protocol.VersionedTextDocumentIdentifier{ diff --git a/internal/lsp/debug/serve.go b/internal/lsp/debug/serve.go index 03d5c66b44..8d25d8a66c 100644 --- a/internal/lsp/debug/serve.go +++ b/internal/lsp/debug/serve.go @@ -267,7 +267,7 @@ func (i *Instance) getFile(r *http.Request) interface{} { return nil } for _, o := range s.Overlays() { - if o.Identity().Identifier == identifier { + if o.FileIdentity().Hash == identifier { return o } } diff --git a/internal/lsp/diagnostics.go b/internal/lsp/diagnostics.go index ef7aec6527..3c16f08d62 100644 --- a/internal/lsp/diagnostics.go +++ b/internal/lsp/diagnostics.go @@ -25,7 +25,7 @@ import ( // idWithAnalysis is used to track if the diagnostics for a given file were // computed with analyses. type idWithAnalysis struct { - id source.FileIdentity + id source.VersionedFileIdentity withAnalysis bool } @@ -226,8 +226,7 @@ func (s *Server) publishReports(ctx context.Context, snapshot source.Snapshot, r } source.SortDiagnostics(diagnostics) toSend := sentDiagnostics{ - version: key.id.Version, - identifier: key.id.Identifier, + id: key.id, sorted: diagnostics, withAnalysis: key.withAnalysis, snapshotID: snapshot.ID(), @@ -255,7 +254,7 @@ func (s *Server) publishReports(ctx context.Context, snapshot source.Snapshot, r // If we've already delivered diagnostics for this file, at this // snapshot, with analyses, do not send diagnostics without analyses. - if delivered.snapshotID == toSend.snapshotID && delivered.version == toSend.version && + if delivered.snapshotID == toSend.snapshotID && delivered.id == toSend.id && delivered.withAnalysis && !toSend.withAnalysis { // Do not update the delivered map since it already contains better diagnostics. continue @@ -366,7 +365,7 @@ See https://github.com/golang/go/issues/39164 for more detail on this issue.`, return false } s.publishReports(ctx, snapshot, map[idWithAnalysis]map[string]*source.Diagnostic{ - {id: fh.Identity()}: {diagnosticKey(diag): diag}, + {id: fh.VersionedFileIdentity()}: {diagnosticKey(diag): diag}, }) return true } diff --git a/internal/lsp/general.go b/internal/lsp/general.go index 83d90aa0cb..caab6d2631 100644 --- a/internal/lsp/general.go +++ b/internal/lsp/general.go @@ -305,7 +305,7 @@ func (s *Server) fetchConfig(ctx context.Context, name string, folder span.URI, // it to a snapshot. // We don't want to return errors for benign conditions like wrong file type, // so callers should do if !ok { return err } rather than if err != nil. -func (s *Server) beginFileRequest(ctx context.Context, pURI protocol.DocumentURI, expectKind source.FileKind) (source.Snapshot, source.FileHandle, bool, func(), error) { +func (s *Server) beginFileRequest(ctx context.Context, pURI protocol.DocumentURI, expectKind source.FileKind) (source.Snapshot, source.VersionedFileHandle, bool, func(), error) { uri := pURI.SpanURI() if !uri.IsFile() { // Not a file URI. Stop processing the request, but don't return an error. diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go index 032e092ae2..48d1431a32 100644 --- a/internal/lsp/lsp_test.go +++ b/internal/lsp/lsp_test.go @@ -474,7 +474,7 @@ func (r *runner) SuggestedFix(t *testing.T, spn span.Span, actionKinds []string) } } -func commandToEdits(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, rng protocol.Range, cmd string) ([]protocol.TextDocumentEdit, error) { +func commandToEdits(ctx context.Context, snapshot source.Snapshot, fh source.VersionedFileHandle, rng protocol.Range, cmd string) ([]protocol.TextDocumentEdit, error) { var command *source.Command for _, c := range source.Commands { if c.Name == cmd { diff --git a/internal/lsp/mod/diagnostics.go b/internal/lsp/mod/diagnostics.go index 43fd95edda..9c7ca4105b 100644 --- a/internal/lsp/mod/diagnostics.go +++ b/internal/lsp/mod/diagnostics.go @@ -21,7 +21,7 @@ import ( "golang.org/x/tools/internal/span" ) -func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.FileIdentity][]*source.Diagnostic, error) { +func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.VersionedFileIdentity][]*source.Diagnostic, error) { uri := snapshot.View().ModFile() if uri == "" { return nil, nil @@ -38,8 +38,8 @@ func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.File if err == source.ErrTmpModfileUnsupported { return nil, nil } - reports := map[source.FileIdentity][]*source.Diagnostic{ - fh.Identity(): {}, + reports := map[source.VersionedFileIdentity][]*source.Diagnostic{ + fh.VersionedFileIdentity(): {}, } if err != nil { return nil, err @@ -59,7 +59,7 @@ func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.File if err != nil { return nil, err } - reports[fh.Identity()] = append(reports[fh.Identity()], diag) + reports[fh.VersionedFileIdentity()] = append(reports[fh.VersionedFileIdentity()], diag) } return reports, nil } diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 0084493a6f..fba9fc254f 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -92,8 +92,7 @@ type Server struct { // sentDiagnostics is used to cache diagnostics that have been sent for a given file. type sentDiagnostics struct { - version float64 - identifier string + id source.VersionedFileIdentity sorted []*source.Diagnostic withAnalysis bool snapshotID uint64 diff --git a/internal/lsp/source/command.go b/internal/lsp/source/command.go index edb2492a04..17e1d685c2 100644 --- a/internal/lsp/source/command.go +++ b/internal/lsp/source/command.go @@ -152,7 +152,7 @@ func (c *Command) IsSuggestedFix() bool { // SuggestedFix applies the command's suggested fix to the given file and // range, returning the resulting edits. -func (c *Command) SuggestedFix(ctx context.Context, snapshot Snapshot, fh FileHandle, pRng protocol.Range) ([]protocol.TextDocumentEdit, error) { +func (c *Command) SuggestedFix(ctx context.Context, snapshot Snapshot, fh VersionedFileHandle, pRng protocol.Range) ([]protocol.TextDocumentEdit, error) { if c.suggestedFixFn == nil { return nil, fmt.Errorf("no suggested fix function for %s", c.Name) } diff --git a/internal/lsp/source/diagnostics.go b/internal/lsp/source/diagnostics.go index a76d9d1bac..0ca38de8c2 100644 --- a/internal/lsp/source/diagnostics.go +++ b/internal/lsp/source/diagnostics.go @@ -38,7 +38,7 @@ type RelatedInformation struct { Message string } -func Diagnostics(ctx context.Context, snapshot Snapshot, pkg Package, withAnalysis bool) (map[FileIdentity][]*Diagnostic, bool, error) { +func Diagnostics(ctx context.Context, snapshot Snapshot, pkg Package, withAnalysis bool) (map[VersionedFileIdentity][]*Diagnostic, bool, error) { onlyIgnoredFiles := true for _, pgf := range pkg.CompiledGoFiles() { onlyIgnoredFiles = onlyIgnoredFiles && snapshot.View().IgnoredFile(pgf.URI) @@ -60,7 +60,7 @@ func Diagnostics(ctx context.Context, snapshot Snapshot, pkg Package, withAnalys warn = true } // Prepare the reports we will send for the files in this package. - reports := make(map[FileIdentity][]*Diagnostic) + reports := make(map[VersionedFileIdentity][]*Diagnostic) for _, pgf := range pkg.CompiledGoFiles() { clearReports(ctx, snapshot, reports, pgf.URI) } @@ -124,31 +124,31 @@ func pickAnalyzers(snapshot Snapshot, hadTypeErrors bool) map[string]Analyzer { return analyzers } -func FileDiagnostics(ctx context.Context, snapshot Snapshot, uri span.URI) (FileIdentity, []*Diagnostic, error) { +func FileDiagnostics(ctx context.Context, snapshot Snapshot, uri span.URI) (VersionedFileIdentity, []*Diagnostic, error) { fh, err := snapshot.GetFile(ctx, uri) if err != nil { - return FileIdentity{}, nil, err + return VersionedFileIdentity{}, nil, err } pkg, _, err := getParsedFile(ctx, snapshot, fh, NarrowestPackage) if err != nil { - return FileIdentity{}, nil, err + return VersionedFileIdentity{}, nil, err } reports, _, err := Diagnostics(ctx, snapshot, pkg, true) if err != nil { - return FileIdentity{}, nil, err + return VersionedFileIdentity{}, nil, err } - diagnostics, ok := reports[fh.Identity()] + diagnostics, ok := reports[fh.VersionedFileIdentity()] if !ok { - return FileIdentity{}, nil, errors.Errorf("no diagnostics for %s", uri) + return VersionedFileIdentity{}, nil, errors.Errorf("no diagnostics for %s", uri) } - return fh.Identity(), diagnostics, nil + return fh.VersionedFileIdentity(), diagnostics, nil } type diagnosticSet struct { listErrors, parseErrors, typeErrors []*Diagnostic } -func diagnostics(ctx context.Context, snapshot Snapshot, reports map[FileIdentity][]*Diagnostic, pkg Package, hasMissingDeps bool) (bool, bool, error) { +func diagnostics(ctx context.Context, snapshot Snapshot, reports map[VersionedFileIdentity][]*Diagnostic, pkg Package, hasMissingDeps bool) (bool, bool, error) { ctx, done := event.Start(ctx, "source.diagnostics", tag.Package.Of(pkg.ID())) _ = ctx // circumvent SA4006 defer done() @@ -198,7 +198,7 @@ func diagnostics(ctx context.Context, snapshot Snapshot, reports map[FileIdentit return nonEmptyDiagnostics, hasTypeErrors, nil } -func analyses(ctx context.Context, snapshot Snapshot, reports map[FileIdentity][]*Diagnostic, pkg Package, analyses map[string]Analyzer) error { +func analyses(ctx context.Context, snapshot Snapshot, reports map[VersionedFileIdentity][]*Diagnostic, pkg Package, analyses map[string]Analyzer) error { var analyzers []*analysis.Analyzer for _, a := range analyses { if !a.Enabled(snapshot.View()) { @@ -241,20 +241,20 @@ func analyses(ctx context.Context, snapshot Snapshot, reports map[FileIdentity][ return nil } -func clearReports(ctx context.Context, snapshot Snapshot, reports map[FileIdentity][]*Diagnostic, uri span.URI) { +func clearReports(ctx context.Context, snapshot Snapshot, reports map[VersionedFileIdentity][]*Diagnostic, uri span.URI) { fh := snapshot.FindFile(uri) if fh == nil { return } - reports[fh.Identity()] = []*Diagnostic{} + reports[fh.VersionedFileIdentity()] = []*Diagnostic{} } -func addReports(ctx context.Context, snapshot Snapshot, reports map[FileIdentity][]*Diagnostic, uri span.URI, diagnostics ...*Diagnostic) error { +func addReports(ctx context.Context, snapshot Snapshot, reports map[VersionedFileIdentity][]*Diagnostic, uri span.URI, diagnostics ...*Diagnostic) error { fh := snapshot.FindFile(uri) if fh == nil { return nil } - existingDiagnostics, ok := reports[fh.Identity()] + existingDiagnostics, ok := reports[fh.VersionedFileIdentity()] if !ok { return fmt.Errorf("diagnostics for unexpected file %s", uri) } @@ -268,12 +268,12 @@ func addReports(ctx context.Context, snapshot Snapshot, reports map[FileIdentity if d1.Message != d2.Message { continue } - reports[fh.Identity()][i].Tags = append(reports[fh.Identity()][i].Tags, d1.Tags...) + reports[fh.VersionedFileIdentity()][i].Tags = append(reports[fh.VersionedFileIdentity()][i].Tags, d1.Tags...) } return nil } } - reports[fh.Identity()] = append(reports[fh.Identity()], diagnostics...) + reports[fh.VersionedFileIdentity()] = append(reports[fh.VersionedFileIdentity()], diagnostics...) return nil } diff --git a/internal/lsp/source/gc_annotations.go b/internal/lsp/source/gc_annotations.go index 3dd8be8de1..d4219922fb 100644 --- a/internal/lsp/source/gc_annotations.go +++ b/internal/lsp/source/gc_annotations.go @@ -18,7 +18,7 @@ import ( "golang.org/x/tools/internal/span" ) -func GCOptimizationDetails(ctx context.Context, snapshot Snapshot, pkgDir span.URI) (map[FileIdentity][]*Diagnostic, error) { +func GCOptimizationDetails(ctx context.Context, snapshot Snapshot, pkgDir span.URI) (map[VersionedFileIdentity][]*Diagnostic, error) { outDir := filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.details", os.Getpid())) if err := os.MkdirAll(outDir, 0700); err != nil { return nil, err @@ -32,7 +32,7 @@ func GCOptimizationDetails(ctx context.Context, snapshot Snapshot, pkgDir span.U if err != nil { return nil, err } - reports := make(map[FileIdentity][]*Diagnostic) + reports := make(map[VersionedFileIdentity][]*Diagnostic) var parseError error for _, fn := range files { fname, v, err := parseDetailsFile(fn) @@ -48,8 +48,7 @@ func GCOptimizationDetails(ctx context.Context, snapshot Snapshot, pkgDir span.U if x == nil { continue } - k := x.Identity() - reports[k] = v + reports[x.VersionedFileIdentity()] = v } return reports, parseError } diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go index eaabf3f5ea..d3e8eec0d3 100644 --- a/internal/lsp/source/view.go +++ b/internal/lsp/source/view.go @@ -32,11 +32,11 @@ type Snapshot interface { // FindFile returns the FileHandle for the given URI, if it is already // in the given snapshot. - FindFile(uri span.URI) FileHandle + FindFile(uri span.URI) VersionedFileHandle // GetFile returns the FileHandle for a given URI, initializing it // if it is not already part of the snapshot. - GetFile(ctx context.Context, uri span.URI) (FileHandle, error) + GetFile(ctx context.Context, uri span.URI) (VersionedFileHandle, error) // IsOpen returns whether the editor currently has a file open. IsOpen(uri span.URI) bool @@ -255,17 +255,10 @@ type Session interface { // Overlay is the type for a file held in memory on a session. type Overlay interface { - // Session returns the session this overlay belongs to. - Session() Session - - // Identity returns the FileIdentity for the overlay. - Identity() FileIdentity + VersionedFileHandle // Saved returns whether this overlay has been saved to disk. Saved() bool - - // Data is the contents of the overlay held in memory. - Data() []byte } // FileModification represents a modification to a file. @@ -357,17 +350,35 @@ const ( ParseFull ) +type VersionedFileHandle interface { + FileHandle + Version() float64 + Session() string + + // LSPIdentity returns the version identity of a file. + VersionedFileIdentity() VersionedFileIdentity +} + +type VersionedFileIdentity struct { + URI span.URI + + // SessionID is the ID of the LSP session. + SessionID string + + // Version is the version of the file, as specified by the client. It should + // only be set in combination with SessionID. + Version float64 +} + // FileHandle represents a handle to a specific version of a single file. type FileHandle interface { URI() span.URI Kind() FileKind - Version() float64 // Identity returns a FileIdentity for the file, even if there was an error // reading it. // It is a fatal error to call Identity on a file that has not yet been read. - Identity() FileIdentity - + FileIdentity() FileIdentity // Read reads the contents of a file. // If the file is not available, returns a nil slice and an error. Read() ([]byte, error) @@ -377,27 +388,13 @@ type FileHandle interface { type FileIdentity struct { URI span.URI - // SessionID is the ID of the LSP session. - SessionID string - - // Version is the version of the file, as specified by the client. It should - // only be set in combination with SessionID. - Version float64 - - // Identifier represents a unique identifier for the file. - // It could be a file's modification time or its SHA1 hash if it is not on disk. - Identifier string + // Identifier represents a unique identifier for the file's content. + Hash string // Kind is the file's kind. Kind FileKind } -func (fileID FileIdentity) String() string { - // Version is not part of the FileIdentity string, - // as it can remain change even if the file does not. - return fmt.Sprintf("%s%s%s", fileID.URI, fileID.Identifier, fileID.Kind) -} - // FileKind describes the kind of the file in question. // It can be one of Go, mod, or sum. type FileKind int