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’scontent—parser::parse_units_with_warnings_from, cached by content equality and keyed on the durableFileIdfile_id_forassignspath. On a cache hit, no newExprIds 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’scontent, when its strict parse fails in the parser —Nonefor 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 sameExprIds across calls, and the strict result is still theErrthe build reads ([DECISION E]). - file_
id_ for - The durable
FileIdforpath— assigned once, on first use, and stable for the life of the process from then on, even across content edits to that same path (aFileIdis a path identity, not a content one;cached_parse’s own content-keyed cache is what tracks edits).