Skip to content

[display] honor request-provided file contents#12979

Open
kLabz wants to merge 2 commits into
developmentfrom
fix/display-unsaved-contents
Open

[display] honor request-provided file contents#12979
kLabz wants to merge 2 commits into
developmentfrom
fix/display-unsaved-contents

Conversation

@kLabz

@kLabz kLabz commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Unsaved editor buffers arrive as 'contents' in display requests (haxe language server sends them on every hover), but all three server cache layers validated freshness by disk mtime only, which does not change while the user types:

  • parse_file serves the cached disk parse on mtime match without consulting com.file_contents (and dms_per_file could cache a contents-parse keyed by disk mtime, poisoning later requests);
  • check_module deems the module clean (mtime unchanged), so the stale typed module is served and the display position is never visited (empty hover) or resolved against old content (wrong-symbol hover);
  • check_display_file types the cached c_decls directly with no freshness validation at all.

Haxe language server sends server/invalidate on each edit, which evicts these caches and usually hides the problem. But nothing invalidates again when a compilation or a diagnostics run re-populates them from disk: with unsaved edits still pending, the next display request is answered from the freshly cached disk state instead of the buffer.

kLabz added 2 commits July 4, 2026 17:18
…idated caches

Unsaved editor buffers arrive as 'contents' in display requests
(haxe-language-server sends them on every hover), but all three server
cache layers validate freshness by disk mtime only, which does not change
while the user types:

- parse_file serves the cached disk parse on mtime match without consulting
  com.file_contents (and dms_per_file could cache a contents-parse keyed by
  disk mtime, poisoning later requests);
- check_module deems the module clean (mtime unchanged), so the stale typed
  module is served and the display position is either never visited (empty
  hover) or resolved against old content (wrong-symbol hover);
- check_display_file types the cached c_decls directly with no freshness
  validation at all.

haxe-language-server sends server/invalidate on each edit, which evicts
these caches and usually hides the problem. But nothing invalidates again
when a compilation or a diagnostics run re-populates them from disk: with
unsaved edits still pending, the next display request is answered from the
freshly cached disk state instead of the buffer.

Fix: request contents take priority. parse_file bypasses the parse cache
(and skips caching) for files with provided contents; check_module compares
the contents-parse against the cached declarations (not gated by
NoFileSystemCheck — no file system involved); check_display_file re-parses
and falls back to fresh typing when the declarations differ (needed even
with the check_module fix: a display module already loaded fresh this
request hits module_lut directly, skipping the hook).

Regression test: cases/UnsavedContentsStale — hover carrying newer contents
after a compile with no invalidate in between. Unfixed, it resolves the
offset against the stale disk parse and returns the type of the identifier
that previously occupied it (Int from the old `aaa` instead of String from
the new `bbb`).
Two optimizations over the previous commit, measured on a pathological
351KB / 3000-decl display file (typical files are 10-30x smaller); without
them the freshness checks re-parsed the buffer at every check site on every
hover (~180ms at this size).

- displayJson drops request contents that are byte-identical to the on-disk
  file (the common case: hover with no unsaved edits). The mtime-validated
  caches then stay fully usable. Clean-buffer hover: 35ms vs 33ms unfixed —
  the fix-attributable cost is one file read + string compare (~1ms even at
  this size); the ~22ms both pay over the 13ms no-contents baseline is JSON
  payload decode of the contents string, which pre-exists.

- context_cache grows a tmp_parse_cache (cleared per request alongside
  tmp_binary_cache): the contents-parse is computed once and shared by
  check_module, check_display_file and module typing instead of re-parsing
  at each site. Dirty-buffer hover (unsaved edits pending): ~100ms at this
  size = one parse + full fresh display typing — the price of a correct
  answer where the unfixed server returned a wrong-symbol result.
@kLabz kLabz changed the title [server] display: honor request-provided file contents [display] honor request-provided file contents Jul 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant