Skip to main content

Module parse_cache

Module parse_cache 

Source
Expand description

P8.4 (#1515): a durable path↔FileId interning table plus one shared, content-keyed parse cache — settled by ADR 0413 as the fix for R3.13’s own file level. Replaces bynk-ide’s PROJECT_UNIT_CACHE (bynk-check cannot depend on bynk-ide, so the cache the diagnostics path (bynk_check::analysis::analyse_project → phase_parse → crate::discovery::parse_sources) actually needs has to live here, one layer down) and gives bynk_check::project_model::phase_parse a FileId that survives across separate analysis calls instead of resetting to zero on every one ([DECISION B]).

[DECISION A] (ADR 0413): one cache, Ast(FileId), not a separate Tokens(FileId) cache — neither consumer this slice migrates reads a token stream independently of the parse it feeds.

[DECISION B]: the interning table is path ↔ FileId (HashMap<PathBuf, FileId>, no index_vec crate — matching P8.1’s own “no new indexing infrastructure” posture); the parse cache keys on the interned FileId and invalidates by content equality, mirroring PROJECT_UNIT_CACHE’s own proven scheme exactly.

[DECISION C]: one slice, one cache. PROJECT_UNIT_CACHE is deleted (bynk-ide/src/completion.rs), not left running alongside this one — two independently-invalidated caches of the same fact is the exact “no fact in two hand-synced copies” defect this trajectory’s phase 1 already named as a standing invariant.

[DECISION D] (new — neither the issue nor ADR 0413 examined this): ExprId must be durably allocated too, from a counter that never resets, for the same reason FileId must be. parser:: parse_units_with_warnings_from‘s own doc comment names the hazard this closes one level up: a multi-file commons merges sibling files’ methods into one check_record call, and two independently zero-based files would collide on the same ExprId in the same expr_types map (collect_unit_methods, caught live by finding #28). That hazard is usually avoided by threading one counter across every file within a single phase_parse call. Caching a file’s parsed SourceUnit across calls reopens it one level up: if call 2 serves file A from cache (keeping its ExprIds from call 1’s counter position) while freshly parsing changed file B from a counter that started over at 0, A’s and B’s ExprIds collide in call 2’s own expr_types map — the identical defect class, now triggered by caching rather than by two files in one call. Fixed the same way FileId is: next_expr_id lives in this module’s own durable state, advanced only on an actual parse (a cache hit consumes no new ids, since the cached SourceUnit’s own ids are already fixed), never reset. Global uniqueness across the whole process trivially implies uniqueness within any one call, so this is a strict strengthening of the existing guarantee, not a new one.

[DECISION E] (new): this cache stores the strict parse (parser::parse_units_with_warnings_from, recover_mode: false) — the one the build/diagnostics path needs, since a build must never silently succeed on broken syntax by reading a best-effort recovered AST. #1710 amends this without weakening it: when the strict parse fails in the parser, the entry also keeps the recovering parse of the same tokens (parser::parse_units_recovering_from, ids from the same durable counter), read through cached_recovery by the project path so a file’s surviving declarations are still checked. The strict result is unchanged and still the Err the build reads; the recovered units are for diagnostics only. bynk -ide::completion’s own recovery-tolerant parsing (parser::parse_unit_with_recovery) is a genuinely different parser configuration, not just a different entry point over the same result — for syntactically clean source the two produce the identical AST (there is nothing to recover from), so completion reads this cache directly for the common case; only when the cached/fresh strict result actually carries errors does completion fall back to its own local, uncached recovery-parse for that one file (bynk_ide::completion‘s own parse_source_unit, calling parser::parse_unit_with_recovery directly — not part of this crate) — the rare case (another project file mid-edit elsewhere, not the buffer under the cursor, which this cache was never in the path for to begin with) traded for never caching two different parser configurations’ output under one key.

Functions§

cached_parse
The strict parse of path’s content — parser::parse_units_with_warnings_from, cached by content equality and keyed on the durable FileId file_id_for assigns path. On a cache hit, no new ExprIds are consumed (the cached units already carry their own, fixed at whenever they were last actually parsed); on a miss, [DECISION D]’s durable counter advances by however many the fresh parse used.
cached_recovery
#1710: the recovering parse of path’s content, when its strict parse fails in the parser — None for clean source, and for source that doesn’t lex. Cached with the strict parse (one entry, one content check), so a file that stays broken keeps the same ExprIds across calls, and the strict result is still the Err the build reads ([DECISION E]).
file_id_for
The durable FileId for path — assigned once, on first use, and stable for the life of the process from then on, even across content edits to that same path (a FileId is a path identity, not a content one; cached_parse’s own content-keyed cache is what tracks edits).