From 6758bd1ac4dce53ba737efbd180697d30afa7add Mon Sep 17 00:00:00 2001 From: karitham Date: Sun, 23 Aug 2026 00:10:42 +0200 Subject: [PATCH] cache: drop vestigial cache wrapper --- lsp/cache/bench_test.go | 2 +- lsp/cache/cache.go | 35 ------------------------------- lsp/cache/fs_memoized.go | 6 ++++++ lsp/cache/invalidation_test.go | 2 +- lsp/cache/resolver_test.go | 2 +- lsp/cache/session.go | 18 +++++++++++----- lsp/cache/session_test.go | 6 +++--- lsp/cache/snapshot.go | 2 +- lsp/config_test.go | 16 +++++++------- lsp/didchange_test.go | 4 ++-- lsp/impl_test.go | 4 ++-- lsp/include_paths_test.go | 2 +- lsp/initialize.go | 4 ++-- lsp/log_test.go | 4 ++-- lsp/server.go | 6 ++---- lsp/source/completion_test.go | 2 +- lsp/source/cycle_detect_test.go | 2 +- lsp/source/folding_test.go | 2 +- lsp/source/include_action_test.go | 2 +- lsp/source/workspace_test.go | 8 +++---- lsp/stream.go | 6 +++--- main.go | 4 ++-- 22 files changed, 58 insertions(+), 81 deletions(-) delete mode 100644 lsp/cache/cache.go diff --git a/lsp/cache/bench_test.go b/lsp/cache/bench_test.go index 6948f9d..a9e7398 100644 --- a/lsp/cache/bench_test.go +++ b/lsp/cache/bench_test.go @@ -31,7 +31,7 @@ func benchChain(b *testing.B, n int) (*View, []*FileChange) { }) } - fs := NewOverlayFS(New()) + fs := NewOverlayFS(NewMemoizedFS()) if err := fs.Update(context.Background(), files); err != nil { b.Fatal(err) } diff --git a/lsp/cache/cache.go b/lsp/cache/cache.go deleted file mode 100644 index 3dc8146..0000000 --- a/lsp/cache/cache.go +++ /dev/null @@ -1,35 +0,0 @@ -package cache - -import ( - "context" - - "go.lsp.dev/uri" -) - -// Cache is the process-wide file store, backed by a FileSource (the disk -// in production, an in-memory tree in tests). Include paths are not -// global: each view resolves its own from its workspace folder's config -// at creation. -type Cache struct { - fs FileSource -} - -// New returns a disk-backed cache. -func New() *Cache { - return NewWithFS(&memoizedFS{filesByID: map[FileID][]*DiskFile{}}) -} - -// NewWithFS returns a cache backed by fs, for tests and embedding. -func NewWithFS(fs FileSource) *Cache { - return &Cache{fs: fs} -} - -// ReadFile implements FileSource by delegating to the backing source. -func (c *Cache) ReadFile(ctx context.Context, u uri.URI) (FileHandle, error) { - return c.fs.ReadFile(ctx, u) -} - -// WalkFiles implements FileSource by delegating to the backing source. -func (c *Cache) WalkFiles(ctx context.Context, root uri.URI, fn func(uri.URI) error) error { - return c.fs.WalkFiles(ctx, root, fn) -} diff --git a/lsp/cache/fs_memoized.go b/lsp/cache/fs_memoized.go index 7358f25..365c70d 100644 --- a/lsp/cache/fs_memoized.go +++ b/lsp/cache/fs_memoized.go @@ -21,6 +21,12 @@ type memoizedFS struct { filesByID map[FileID][]*DiskFile } +// NewMemoizedFS returns the production disk file source: reads stat first +// and memoize by inode+mtime. +func NewMemoizedFS() FileSource { + return &memoizedFS{filesByID: map[FileID][]*DiskFile{}} +} + // A DiskFile is a file on the filesystem, or a failure to read one. // It implements the source.FileHandle interface. type DiskFile struct { diff --git a/lsp/cache/invalidation_test.go b/lsp/cache/invalidation_test.go index 777913c..7e9e54d 100644 --- a/lsp/cache/invalidation_test.go +++ b/lsp/cache/invalidation_test.go @@ -61,7 +61,7 @@ type viewHarness struct { func newViewHarness(t *testing.T, files []*FileChange) *viewHarness { t.Helper() - c := New() + c := NewMemoizedFS() fs := NewOverlayFS(c) if err := fs.Update(t.Context(), files); err != nil { diff --git a/lsp/cache/resolver_test.go b/lsp/cache/resolver_test.go index 17dd032..5f86425 100644 --- a/lsp/cache/resolver_test.go +++ b/lsp/cache/resolver_test.go @@ -29,7 +29,7 @@ func TestResolver(t *testing.T) { err = os.WriteFile(sharedThrift, []byte(""), 0o644) assert.NoError(t, err) - c := New() + c := NewMemoizedFS() fs := NewOverlayFS(c) view := NewView(uri.File(tmpDir), fs, []string{sharedDir}, options.Patch{}) diff --git a/lsp/cache/session.go b/lsp/cache/session.go index 1785a64..4dcd64f 100644 --- a/lsp/cache/session.go +++ b/lsp/cache/session.go @@ -11,8 +11,9 @@ import ( ) type Session struct { - // cache is shared global - cache *Cache + // fs is the underlying file source (disk in production, in-memory in + // tests); the embedded overlayFS serves open-editor content over it. + fs FileSource viewMu sync.Mutex views []*View @@ -23,12 +24,12 @@ type Session struct { *overlayFS } -func NewSession(cache *Cache) *Session { +func NewSession(fs FileSource) *Session { sess := &Session{ - cache: cache, + fs: fs, views: make([]*View, 0), viewMap: make(map[uri.URI]*View), - overlayFS: NewOverlayFS(cache), + overlayFS: NewOverlayFS(fs), } return sess @@ -123,3 +124,10 @@ func (s *Session) ViewOf(fileURI uri.URI) (*View, error) { func (s *Session) UpdateOverlayFS(ctx context.Context, changes []*FileChange) error { return s.Update(ctx, changes) } + +// WalkFiles enumerates the file source under root. Open overlays are +// already known to the session via didOpen, so this walks the underlying +// source (the disk in production). +func (s *Session) WalkFiles(ctx context.Context, root uri.URI, fn func(uri.URI) error) error { + return s.overlayFS.WalkFiles(ctx, root, fn) +} diff --git a/lsp/cache/session_test.go b/lsp/cache/session_test.go index 41bb1ae..6e94337 100644 --- a/lsp/cache/session_test.go +++ b/lsp/cache/session_test.go @@ -60,7 +60,7 @@ func TestSessionViews(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := NewSession(New()) + s := NewSession(NewMemoizedFS()) tt.setup(s) views := s.Views() @@ -76,7 +76,7 @@ func TestSessionViews(t *testing.T) { } func TestSessionAddViewDedups(t *testing.T) { - s := NewSession(New()) + s := NewSession(NewMemoizedFS()) folder := uri.File("/tmp/a") first := s.AddView(folder, nil, options.Patch{}) @@ -87,7 +87,7 @@ func TestSessionAddViewDedups(t *testing.T) { } func TestSessionRemoveViewForgetsMappings(t *testing.T) { - s := NewSession(New()) + s := NewSession(NewMemoizedFS()) folder := uri.File("/tmp/a") other := uri.File("/tmp/b") diff --git a/lsp/cache/snapshot.go b/lsp/cache/snapshot.go index 61ad699..bf76759 100644 --- a/lsp/cache/snapshot.go +++ b/lsp/cache/snapshot.go @@ -149,7 +149,7 @@ func BuildViewForTest(files []*FileChange) *View { // BuildViewForTestWithPaths is BuildViewForTest with configured include // paths, for cross-project include resolution tests. func BuildViewForTestWithPaths(includePaths []string, files []*FileChange) *View { - c := New() + c := NewMemoizedFS() fs := NewOverlayFS(c) _ = fs.Update(context.TODO(), files) diff --git a/lsp/config_test.go b/lsp/config_test.go index 03d94e1..449320b 100644 --- a/lsp/config_test.go +++ b/lsp/config_test.go @@ -83,7 +83,7 @@ func TestConfigDiscoveryPerWorkspaceFolder(t *testing.T) { writeConfig(t, dirA, `{"printWidth": 30}`) writeConfig(t, dirB, `{"printWidth": 100}`) - srv := NewServer(cache.New(), nil, Options{}) + srv := NewServer(cache.NewMemoizedFS(), nil, Options{}) initWorkspace(t, srv, []uri.URI{uri.File(dirA), uri.File(dirB)}, nil) // One server, two folders: each formats with its own config. @@ -100,7 +100,7 @@ func TestConfigDiscoverySingleFileMode(t *testing.T) { dir := t.TempDir() writeConfig(t, dir, `{"printWidth": 30}`) - srv := NewServer(cache.New(), nil, Options{}) + srv := NewServer(cache.NewMemoizedFS(), nil, Options{}) initWorkspace(t, srv, nil, nil) assert.Equal(t, probeBroken, openAndFormat(t, srv, filepath.Join(dir, "app.thrift"))) @@ -115,7 +115,7 @@ func TestConfigDiscoveryExplicitPathPins(t *testing.T) { dir := t.TempDir() writeConfig(t, dir, `{"printWidth": 30}`) - srv := NewServer(cache.New(), nil, Options{ + srv := NewServer(cache.NewMemoizedFS(), nil, Options{ Config: options.Default(), ConfigPath: "/pinned/thrift-ls.json", }) @@ -137,7 +137,7 @@ func TestConfigDiscoveryDefaultsWhenNoConfig(t *testing.T) { width := 30 startup.PrintWidth = &width - srv := NewServer(cache.New(), nil, Options{Config: startup}) + srv := NewServer(cache.NewMemoizedFS(), nil, Options{Config: startup}) initWorkspace(t, srv, []uri.URI{uri.File(dir)}, nil) assert.Equal(t, probeOneLine, openAndFormat(t, srv, filepath.Join(dir, "a.thrift"))) @@ -152,7 +152,7 @@ func TestConfigDiscoveryWorkspaceSettingsOverlay(t *testing.T) { dir := t.TempDir() writeConfig(t, dir, `{"printWidth": 30}`) - srv := NewServer(cache.New(), nil, Options{}) + srv := NewServer(cache.NewMemoizedFS(), nil, Options{}) initWorkspace(t, srv, []uri.URI{uri.File(dir)}, []byte(`{"printWidth": 100}`)) file := filepath.Join(dir, "a.thrift") @@ -180,7 +180,7 @@ func TestConfigDiscoveryLogLevel(t *testing.T) { dir := t.TempDir() writeConfig(t, dir, `{"logLevel": 5}`) - srv := NewServer(cache.New(), nil, Options{}) + srv := NewServer(cache.NewMemoizedFS(), nil, Options{}) initWorkspace(t, srv, nil, nil) openAndFormat(t, srv, filepath.Join(dir, "app.thrift")) @@ -200,7 +200,7 @@ func TestConfigDiscoveryInvalidFileKeepsDefaults(t *testing.T) { dir := t.TempDir() writeConfig(t, dir, `{"printWidth": "wide"}`) - srv := NewServer(cache.New(), nil, Options{}) + srv := NewServer(cache.NewMemoizedFS(), nil, Options{}) initWorkspace(t, srv, []uri.URI{uri.File(dir)}, nil) assert.Equal(t, probeOneLine, openAndFormat(t, srv, filepath.Join(dir, "a.thrift"))) @@ -217,7 +217,7 @@ func TestConfigDiscoveryNestedFolder(t *testing.T) { nested := filepath.Join(root, "packages", "app") require.NoError(t, os.MkdirAll(nested, 0o755)) - srv := NewServer(cache.New(), nil, Options{}) + srv := NewServer(cache.NewMemoizedFS(), nil, Options{}) initWorkspace(t, srv, []uri.URI{uri.File(nested)}, nil) assert.Equal(t, probeBroken, openAndFormat(t, srv, filepath.Join(nested, "a.thrift"))) diff --git a/lsp/didchange_test.go b/lsp/didchange_test.go index 7046819..481f699 100644 --- a/lsp/didchange_test.go +++ b/lsp/didchange_test.go @@ -56,14 +56,14 @@ func (c *recordingClient) count(file uri.URI) int { } func newTestServer(client protocol.Client) *Server { - return NewServer(cache.New(), client, Options{}) + return NewServer(cache.NewMemoizedFS(), client, Options{}) } // newMemServer returns a server backed by an in-memory file source, so // the workspace walk and file reads never touch the real disk. Files may // be seeded by URI; opened documents are served from the overlay. func newMemServer(files map[uri.URI][]byte) *Server { - return NewServer(cache.NewWithFS(cache.NewMemFS(files)), nil, Options{}) + return NewServer(cache.NewMemFS(files), nil, Options{}) } func writeFile(t *testing.T, path, content string) { diff --git a/lsp/impl_test.go b/lsp/impl_test.go index 7078a90..4346fee 100644 --- a/lsp/impl_test.go +++ b/lsp/impl_test.go @@ -414,7 +414,7 @@ func Test_DidChangeWorkspaceFolders(t *testing.T) { require.NoError(t, os.WriteFile(filepath.Join(dirA, "a.thrift"), []byte("struct FromA {}"), 0o644)) require.NoError(t, os.WriteFile(filepath.Join(dirB, "b.thrift"), []byte("struct FromB {}"), 0o644)) - srv := NewServer(cache.New(), nil, Options{}) + srv := NewServer(cache.NewMemoizedFS(), nil, Options{}) // Adding folders walks them and registers their thrift files. err := srv.DidChangeWorkspaceFolders(ctx, &protocol.DidChangeWorkspaceFoldersParams{ @@ -479,7 +479,7 @@ func Test_InitializeDefersTheWorkspaceWalk(t *testing.T) { require.NoError(t, os.MkdirAll(filepath.Join(dir, "nested"), 0o755)) require.NoError(t, os.WriteFile(filepath.Join(dir, "nested", "b.thrift"), []byte("struct FromB {}"), 0o644)) - srv := NewServer(cache.New(), nil, Options{}) + srv := NewServer(cache.NewMemoizedFS(), nil, Options{}) _, err := srv.Initialize(t.Context(), &protocol.InitializeParams{ WorkspaceFoldersInitializeParams: protocol.WorkspaceFoldersInitializeParams{ diff --git a/lsp/include_paths_test.go b/lsp/include_paths_test.go index f474d5d..b60e720 100644 --- a/lsp/include_paths_test.go +++ b/lsp/include_paths_test.go @@ -29,7 +29,7 @@ func TestConfigFileIncludePaths(t *testing.T) { require.NoError(t, os.WriteFile(shared, []byte("struct Shared {}"), 0o644)) writeConfig(t, dir, `{"includePaths": ["base"]}`) - srv := NewServer(cache.New(), nil, Options{}) + srv := NewServer(cache.NewMemoizedFS(), nil, Options{}) _, err := srv.Initialize(ctx, &protocol.InitializeParams{ WorkspaceFoldersInitializeParams: protocol.WorkspaceFoldersInitializeParams{ WorkspaceFolders: protocol.NewNullable([]protocol.WorkspaceFolder{{URI: uri.File(dir)}}), diff --git a/lsp/initialize.go b/lsp/initialize.go index 64e7fdf..f6f1f53 100644 --- a/lsp/initialize.go +++ b/lsp/initialize.go @@ -112,10 +112,10 @@ func (s *Server) walkFoldersThriftFile(folder uri.URI) { // resolve to it via ContainsFile; addFolderView resolves its config. s.addFolderView(folder) - // Walk the folder through the cache's file source: the disk in + // Walk the folder through the session's file source: the disk in // production, an in-memory tree in tests. WalkDir walks with lexical // order; the fs implementations handle their own entry errors. - _ = s.cache.WalkFiles(context.TODO(), folder, func(fileURI uri.URI) error { + _ = s.session.WalkFiles(context.TODO(), folder, func(fileURI uri.URI) error { if !strings.HasSuffix(fileURI.Path(), ".thrift") { return nil } diff --git a/lsp/log_test.go b/lsp/log_test.go index 8bc2ab6..d1854b9 100644 --- a/lsp/log_test.go +++ b/lsp/log_test.go @@ -53,7 +53,7 @@ func TestLoggerForwardsToClientAfterHandshake(t *testing.T) { defer setLogClient(nil) client := &logClient{} - srv := NewServer(cache.New(), client, Options{}) + srv := NewServer(cache.NewMemoizedFS(), client, Options{}) slog.Info("pre-handshake") assert.Empty(t, client.got()) @@ -96,7 +96,7 @@ func TestLoggerForwardingSurvivesConfigRelevel(t *testing.T) { writeConfig(t, dir, `{"logLevel": 5}`) client := &logClient{} - srv := NewServer(cache.New(), client, Options{}) + srv := NewServer(cache.NewMemoizedFS(), client, Options{}) initWorkspace(t, srv, []uri.URI{uri.File(dir)}, nil) slog.Error("after config re-level") diff --git a/lsp/server.go b/lsp/server.go index ae5dc4f..816d0d0 100644 --- a/lsp/server.go +++ b/lsp/server.go @@ -17,7 +17,6 @@ import ( ) type Server struct { - cache *cache.Cache session *cache.Session client protocol.Client @@ -58,10 +57,9 @@ type Server struct { // NewServer returns a Server resolving configuration per view. The options // are expected to validate; workspace settings overlay each view's config // at initialize time and on didChangeConfiguration. -func NewServer(c *cache.Cache, client protocol.Client, opts Options) *Server { +func NewServer(fs cache.FileSource, client protocol.Client, opts Options) *Server { return &Server{ - cache: c, - session: cache.NewSession(c), + session: cache.NewSession(fs), client: client, explicit: opts.Config, configPath: opts.ConfigPath, diff --git a/lsp/source/completion_test.go b/lsp/source/completion_test.go index 8bcf9e1..a68c8be 100644 --- a/lsp/source/completion_test.go +++ b/lsp/source/completion_test.go @@ -17,7 +17,7 @@ import ( func buildSnapshot(t *testing.T, includePaths []string, files ...*cache.FileChange) *cache.View { t.Helper() - c := cache.New() + c := cache.NewMemoizedFS() fs := cache.NewOverlayFS(c) _ = fs.Update(t.Context(), files) view := cache.NewView(uri.File("/tmp"), fs, includePaths, options.Patch{}) diff --git a/lsp/source/cycle_detect_test.go b/lsp/source/cycle_detect_test.go index 4dd1ea9..48407a6 100644 --- a/lsp/source/cycle_detect_test.go +++ b/lsp/source/cycle_detect_test.go @@ -18,7 +18,7 @@ import ( func buildSnapshotForTest(t *testing.T, files []*cache.FileChange) *cache.View { t.Helper() - c := cache.New() + c := cache.NewMemoizedFS() fs := cache.NewOverlayFS(c) _ = fs.Update(t.Context(), files) diff --git a/lsp/source/folding_test.go b/lsp/source/folding_test.go index e5a4113..12d160c 100644 --- a/lsp/source/folding_test.go +++ b/lsp/source/folding_test.go @@ -29,7 +29,7 @@ func foldingRanges(t *testing.T, src string) []protocol.FoldingRange { file := uri.File(filepath.Join(dir, "test.thrift")) require.NoError(t, os.WriteFile(filepath.Join(dir, "test.thrift"), []byte(src), 0o644)) - view := cache.NewView(uri.File(dir), cache.NewOverlayFS(cache.New()), nil, options.Patch{}) + view := cache.NewView(uri.File(dir), cache.NewOverlayFS(cache.NewMemoizedFS()), nil, options.Patch{}) view.Update(t.Context(), &cache.FileChange{ URI: file, Version: 0, diff --git a/lsp/source/include_action_test.go b/lsp/source/include_action_test.go index 22dfd2f..df01a4a 100644 --- a/lsp/source/include_action_test.go +++ b/lsp/source/include_action_test.go @@ -20,7 +20,7 @@ import ( func buildFolderSnapshotForTest(t *testing.T, folder string, files []*cache.FileChange) *cache.View { t.Helper() - c := cache.New() + c := cache.NewMemoizedFS() fs := cache.NewOverlayFS(c) _ = fs.Update(t.Context(), files) diff --git a/lsp/source/workspace_test.go b/lsp/source/workspace_test.go index 90c16cf..2190d2d 100644 --- a/lsp/source/workspace_test.go +++ b/lsp/source/workspace_test.go @@ -192,7 +192,7 @@ struct C { 1: string x }`, t.Run(tt.name, func(t *testing.T) { dir := writeTree(t, tt.files) - session := cache.NewSession(cache.New()) + session := cache.NewSession(cache.NewMemoizedFS()) if tt.nested { // Each top-level directory is a workspace folder. @@ -241,7 +241,7 @@ const i32 DEFAULT_HP = 100, typedef string PilotName`, }) - session := cache.NewSession(cache.New()) + session := cache.NewSession(cache.NewMemoizedFS()) openTree(t, session, dir, nil) file := uri.File(filepath.Join(dir, "shapes.thrift")) @@ -299,7 +299,7 @@ service Federation { }`, }) - session := cache.NewSession(cache.New()) + session := cache.NewSession(cache.NewMemoizedFS()) openTree(t, session, dir, nil) tests := []struct { @@ -353,7 +353,7 @@ exception BayFull { }`, }) - session := cache.NewSession(cache.New()) + session := cache.NewSession(cache.NewMemoizedFS()) openTree(t, session, dir, nil) syms := allWorkspaceSymbols(t.Context(), session, "", 0) diff --git a/lsp/stream.go b/lsp/stream.go index 63bdcf3..e4c0434 100644 --- a/lsp/stream.go +++ b/lsp/stream.go @@ -11,7 +11,7 @@ import ( ) type StreamServer struct { - cache *cache.Cache + fs cache.FileSource config *Options } @@ -30,7 +30,7 @@ type Options struct { func NewStreamServer(opts *Options) *StreamServer { return &StreamServer{ - cache: cache.New(), + fs: cache.NewMemoizedFS(), config: opts, } } @@ -38,7 +38,7 @@ func NewStreamServer(opts *Options) *StreamServer { func (s *StreamServer) ServeStream(ctx context.Context, conn jsonrpc2.Conn) error { client := protocol.ClientDispatcher(conn) - server := NewServer(s.cache, client, *s.config) + server := NewServer(s.fs, client, *s.config) // Clients may or may not send a shutdown message. Make sure the server is // shut down. defer func() { diff --git a/main.go b/main.go index 1275dab..536a6bf 100644 --- a/main.go +++ b/main.go @@ -359,8 +359,8 @@ func checkAction(ctx context.Context, cmd *cli.Command) error { // semantic analysis, and lints — over files opened in a session rooted at // folder, and returns the diagnostics per file, keyed by absolute path. func checkFiles(ctx context.Context, files []string, folder string, includePaths []string) (map[string][]protocol.Diagnostic, error) { - c := cache.New() - sess := cache.NewSession(c) + fs := cache.NewMemoizedFS() + sess := cache.NewSession(fs) sess.AddView(uri.File(folder), includePaths, options.Patch{}) changes := make([]*cache.FileChange, 0, len(files)) -- 2.51.2