Skip to content

Commit c70d86f

Browse files
committed
internal/lsp: match files by identity
Instead of using a simple path map we now attempt to match files with os.SameFile with fast paths for exact path matches. This should fix issues both with symlinked directories (the mac tmp folder) and with case sensitivity (windows) Change-Id: I014dd01f89d08a348e7de7697cbc3a2512a6e5b3 Reviewed-on: https://go-review.googlesource.com/c/tools/+/169699 Reviewed-by: Rebecca Stambler <[email protected]>
1 parent dbeab5a commit c70d86f

File tree

4 files changed

+95
-51
lines changed

4 files changed

+95
-51
lines changed

internal/lsp/cache/check.go

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
"golang.org/x/tools/internal/span"
2020
)
2121

22-
func (v *View) parse(ctx context.Context, uri span.URI) ([]packages.Error, error) {
22+
func (v *View) parse(ctx context.Context, f *File) ([]packages.Error, error) {
2323
v.mcache.mu.Lock()
2424
defer v.mcache.mu.Unlock()
2525

@@ -28,12 +28,6 @@ func (v *View) parse(ctx context.Context, uri span.URI) ([]packages.Error, error
2828
return nil, err
2929
}
3030

31-
f := v.files[uri]
32-
33-
// This should never happen.
34-
if f == nil {
35-
return nil, fmt.Errorf("no file for %v", uri)
36-
}
3731
// If the package for the file has not been invalidated by the application
3832
// of the pending changes, there is no need to continue.
3933
if f.isPopulated() {
@@ -45,7 +39,7 @@ func (v *View) parse(ctx context.Context, uri span.URI) ([]packages.Error, error
4539
return errs, err
4640
}
4741
if f.meta == nil {
48-
return nil, fmt.Errorf("no metadata found for %v", uri)
42+
return nil, fmt.Errorf("no metadata found for %v", f.filename)
4943
}
5044
imp := &importer{
5145
view: v,
@@ -65,7 +59,7 @@ func (v *View) parse(ctx context.Context, uri span.URI) ([]packages.Error, error
6559

6660
// If we still have not found the package for the file, something is wrong.
6761
if f.pkg == nil {
68-
return nil, fmt.Errorf("no package found for %v", uri)
62+
return nil, fmt.Errorf("parse: no package found for %v", f.filename)
6963
}
7064
return nil, nil
7165
}
@@ -83,7 +77,11 @@ func (v *View) cachePackage(pkg *Package) {
8377
continue
8478
}
8579
fURI := span.FileURI(tok.Name())
86-
f := v.getFile(fURI)
80+
f, err := v.getFile(fURI)
81+
if err != nil {
82+
log.Printf("no file: %v", err)
83+
continue
84+
}
8785
f.token = tok
8886
f.ast = file
8987
f.imports = f.ast.Imports
@@ -92,17 +90,13 @@ func (v *View) cachePackage(pkg *Package) {
9290
}
9391

9492
func (v *View) checkMetadata(ctx context.Context, f *File) ([]packages.Error, error) {
95-
filename, err := f.uri.Filename()
96-
if err != nil {
97-
return nil, err
98-
}
99-
if v.reparseImports(ctx, f, filename) {
93+
if v.reparseImports(ctx, f, f.filename) {
10094
cfg := v.Config
10195
cfg.Mode = packages.LoadImports
102-
pkgs, err := packages.Load(&cfg, fmt.Sprintf("file=%s", filename))
96+
pkgs, err := packages.Load(&cfg, fmt.Sprintf("file=%s", f.filename))
10397
if len(pkgs) == 0 {
10498
if err == nil {
105-
err = fmt.Errorf("no packages found for %s", filename)
99+
err = fmt.Errorf("no packages found for %s", f.filename)
106100
}
107101
return nil, err
108102
}
@@ -156,7 +150,7 @@ func (v *View) link(pkgPath string, pkg *packages.Package, parent *metadata) *me
156150
m.name = pkg.Name
157151
m.files = pkg.CompiledGoFiles
158152
for _, filename := range m.files {
159-
if f, ok := v.files[span.FileURI(filename)]; ok {
153+
if f := v.findFile(span.FileURI(filename)); f != nil {
160154
f.meta = m
161155
}
162156
}
@@ -347,7 +341,7 @@ func (v *View) parseFiles(filenames []string) ([]*ast.File, []error) {
347341
}
348342

349343
// First, check if we have already cached an AST for this file.
350-
f := v.files[span.FileURI(filename)]
344+
f := v.findFile(span.FileURI(filename))
351345
var fAST *ast.File
352346
if f != nil {
353347
fAST = f.ast

internal/lsp/cache/file.go

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,19 @@ import (
99
"go/ast"
1010
"go/token"
1111
"io/ioutil"
12+
"path/filepath"
13+
"strings"
1214

1315
"golang.org/x/tools/internal/lsp/source"
1416
"golang.org/x/tools/internal/span"
1517
)
1618

1719
// File holds all the information we know about a file.
1820
type File struct {
19-
uri span.URI
21+
uris []span.URI
22+
filename string
23+
basename string
24+
2025
view *View
2126
active bool
2227
content []byte
@@ -27,8 +32,12 @@ type File struct {
2732
imports []*ast.ImportSpec
2833
}
2934

35+
func basename(filename string) string {
36+
return strings.ToLower(filepath.Base(filename))
37+
}
38+
3039
func (f *File) URI() span.URI {
31-
return f.uri
40+
return f.uris[0]
3241
}
3342

3443
// GetContent returns the contents of the file, reading it from file system if needed.
@@ -52,7 +61,7 @@ func (f *File) GetToken(ctx context.Context) *token.File {
5261
defer f.view.mu.Unlock()
5362

5463
if f.token == nil || len(f.view.contentChanges) > 0 {
55-
if _, err := f.view.parse(ctx, f.uri); err != nil {
64+
if _, err := f.view.parse(ctx, f); err != nil {
5665
return nil
5766
}
5867
}
@@ -64,7 +73,7 @@ func (f *File) GetAST(ctx context.Context) *ast.File {
6473
defer f.view.mu.Unlock()
6574

6675
if f.ast == nil || len(f.view.contentChanges) > 0 {
67-
if _, err := f.view.parse(ctx, f.uri); err != nil {
76+
if _, err := f.view.parse(ctx, f); err != nil {
6877
return nil
6978
}
7079
}
@@ -74,9 +83,8 @@ func (f *File) GetAST(ctx context.Context) *ast.File {
7483
func (f *File) GetPackage(ctx context.Context) source.Package {
7584
f.view.mu.Lock()
7685
defer f.view.mu.Unlock()
77-
7886
if f.pkg == nil || len(f.view.contentChanges) > 0 {
79-
errs, err := f.view.parse(ctx, f.uri)
87+
errs, err := f.view.parse(ctx, f)
8088
if err != nil {
8189
// Create diagnostics for errors if we are able to.
8290
if len(errs) > 0 {
@@ -105,11 +113,7 @@ func (f *File) read(ctx context.Context) {
105113
}
106114
}
107115
// We don't know the content yet, so read it.
108-
filename, err := f.uri.Filename()
109-
if err != nil {
110-
return
111-
}
112-
content, err := ioutil.ReadFile(filename)
116+
content, err := ioutil.ReadFile(f.filename)
113117
if err != nil {
114118
return
115119
}

internal/lsp/cache/view.go

Lines changed: 66 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package cache
77
import (
88
"context"
99
"go/token"
10+
"os"
1011
"sync"
1112

1213
"golang.org/x/tools/go/packages"
@@ -30,8 +31,10 @@ type View struct {
3031
// go/packages API. It is shared across all views.
3132
Config packages.Config
3233

33-
// files caches information for opened files in a view.
34-
files map[span.URI]*File
34+
// keep track of files by uri and by basename, a single file may be mapped
35+
// to multiple uris, and the same basename may map to multiple files
36+
filesByURI map[span.URI]*File
37+
filesByBase map[string][]*File
3538

3639
// contentChanges saves the content changes for a given state of the view.
3740
// When type information is requested by the view, all of the dirty changes
@@ -76,7 +79,8 @@ func NewView(config *packages.Config) *View {
7679
backgroundCtx: ctx,
7780
cancel: cancel,
7881
Config: *config,
79-
files: make(map[span.URI]*File),
82+
filesByURI: make(map[span.URI]*File),
83+
filesByBase: make(map[string][]*File),
8084
contentChanges: make(map[span.URI]func()),
8185
mcache: &metadataCache{
8286
packages: make(map[string]*metadata),
@@ -137,7 +141,10 @@ func (v *View) applyContentChanges(ctx context.Context) error {
137141
// setContent applies a content update for a given file. It assumes that the
138142
// caller is holding the view's mutex.
139143
func (v *View) applyContentChange(uri span.URI, content []byte) {
140-
f := v.getFile(uri)
144+
f, err := v.getFile(uri)
145+
if err != nil {
146+
return
147+
}
141148
f.content = content
142149

143150
// TODO(rstambler): Should we recompute these here?
@@ -153,16 +160,12 @@ func (v *View) applyContentChange(uri span.URI, content []byte) {
153160
case f.active && content == nil:
154161
// The file was active, so we need to forget its content.
155162
f.active = false
156-
if filename, err := f.uri.Filename(); err == nil {
157-
delete(f.view.Config.Overlay, filename)
158-
}
163+
delete(f.view.Config.Overlay, f.filename)
159164
f.content = nil
160165
case content != nil:
161166
// This is an active overlay, so we update the map.
162167
f.active = true
163-
if filename, err := f.uri.Filename(); err == nil {
164-
f.view.Config.Overlay[filename] = f.content
165-
}
168+
f.view.Config.Overlay[f.filename] = f.content
166169
}
167170
}
168171

@@ -180,7 +183,7 @@ func (v *View) remove(pkgPath string) {
180183
// All of the files in the package may also be holding a pointer to the
181184
// invalidated package.
182185
for _, filename := range m.files {
183-
if f, ok := v.files[span.FileURI(filename)]; ok {
186+
if f := v.findFile(span.FileURI(filename)); f != nil {
184187
f.pkg = nil
185188
}
186189
}
@@ -197,18 +200,61 @@ func (v *View) GetFile(ctx context.Context, uri span.URI) (source.File, error) {
197200
return nil, ctx.Err()
198201
}
199202

200-
return v.getFile(uri), nil
203+
return v.getFile(uri)
201204
}
202205

203206
// getFile is the unlocked internal implementation of GetFile.
204-
func (v *View) getFile(uri span.URI) *File {
205-
f, found := v.files[uri]
206-
if !found {
207-
f = &File{
208-
uri: uri,
209-
view: v,
207+
func (v *View) getFile(uri span.URI) (*File, error) {
208+
if f := v.findFile(uri); f != nil {
209+
return f, nil
210+
}
211+
filename, err := uri.Filename()
212+
if err != nil {
213+
return nil, err
214+
}
215+
f := &File{
216+
view: v,
217+
filename: filename,
218+
}
219+
v.mapFile(uri, f)
220+
return f, nil
221+
}
222+
223+
func (v *View) findFile(uri span.URI) *File {
224+
if f := v.filesByURI[uri]; f != nil {
225+
// a perfect match
226+
return f
227+
}
228+
// no exact match stored, time to do some real work
229+
// check for any files with the same basename
230+
fname, err := uri.Filename()
231+
if err != nil {
232+
return nil
233+
}
234+
basename := basename(fname)
235+
if candidates := v.filesByBase[basename]; candidates != nil {
236+
pathStat, err := os.Stat(fname)
237+
if err == nil {
238+
return nil
239+
}
240+
for _, c := range candidates {
241+
if cStat, err := os.Stat(c.filename); err == nil {
242+
if os.SameFile(pathStat, cStat) {
243+
// same file, map it
244+
v.mapFile(uri, c)
245+
return c
246+
}
247+
}
210248
}
211-
v.files[uri] = f
212249
}
213-
return f
250+
return nil
251+
}
252+
253+
func (v *View) mapFile(uri span.URI, f *File) {
254+
v.filesByURI[uri] = f
255+
f.uris = append(f.uris, uri)
256+
if f.basename == "" {
257+
f.basename = basename(f.filename)
258+
v.filesByBase[f.basename] = append(v.filesByBase[f.basename], f)
259+
}
214260
}

internal/lsp/source/diagnostics.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func Diagnostics(ctx context.Context, v View, uri span.URI) (map[span.URI][]Diag
5858
}
5959
pkg := f.GetPackage(ctx)
6060
if pkg == nil {
61-
return nil, fmt.Errorf("no package found for %v", f.URI())
61+
return nil, fmt.Errorf("diagnostics: no package found for %v", f.URI())
6262
}
6363
// Prepare the reports we will send for this package.
6464
reports := make(map[span.URI][]Diagnostic)

0 commit comments

Comments
 (0)