You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: preserve Built stage and FullCompile intent across project
rebuilds
Extract FileBuiltState struct from CompilationStage::Built for reuse in
hash-based promotion snapshots. When project_build reinitializes state,
compare source/AST/CMI/CMT hashes (including interface hashes) to
promote
unchanged modules directly back to Built without recompilation.
For modules whose hashes changed or that were at
CompileError(FullCompile),
persist intent in a full_compile_intent set on PendingState, drained as
step 4 inside build_batch on subsequent flushes with file saves.
Filter errored files from post-build recheck to prevent buffer
typechecks
from overwriting compile error diagnostics. Extract record_error_count
helper shared across file_build and project_build.
-`compile_files` — files needing incremental build (saved to disk)
259
259
-`build_projects` — a `PendingProjectBuilds` struct containing:
260
260
-`created_files` — file paths whose creation requires a full project rebuild
261
261
-`deleted_files` — file paths whose deletion requires a full project rebuild
262
262
-`config_changed` — `rescript.json` paths requiring full project re-initialization
263
+
-`full_compile_intent` — module names (keyed by project root) that need `FullCompile` (JS emission) but couldn't be compiled yet. Populated by project rebuilds when hash-based promotion fails; drained by incremental builds when modules reach `TypeChecked`
264
+
-`recently_deleted` — file paths deleted in the previous flush, used for cross-flush delete+create detection
263
265
264
266
The merge function applies promotion rules to consolidate per-file state. When a `FileCreated` or `FileDeleted` event arrives, any pending per-file typecheck or build for that same file is removed since the full rebuild will cover it. A `ConfigChanged` event clears pending typechecks for the same project (since the rebuild will produce fresh type information) but keeps pending compile_files. After 100ms of silence the batch is flushed sequentially:
265
267
266
-
1.**Full builds first** — re-initialize affected projects from scratch (same flow as the initial build), replacing the old `BuildCommandState`. Clears stale diagnostics for files that no longer exist.
267
-
2.**Incremental builds second** — saved files get a full incremental build (compile dependencies + typecheck dependents). Uses a take-build-replace pattern: each project's `BuildCommandState` is removed from the `ProjectMap` under a brief lock, built without holding the mutex, then inserted back. This keeps LSP handlers (hover, completions, etc.) unblocked during compilation.
268
+
1.**Full builds first** — re-initialize affected projects from scratch (same flow as the initial build), replacing the old `BuildCommandState`. Before replacing the state, snapshots `Built` modules (as `FileBuiltState`) and `CompileError(FullCompile)` module names. After the new state is created, applies hash-based promotion: modules whose implementation/interface/cmi/cmt hashes match the snapshot are promoted directly back to `Built` (no bsc invocation needed). Modules that can't be promoted are added to `full_compile_intent` for deferred JS emission. Clears stale diagnostics for files that no longer exist.
269
+
2.**Incremental builds second** — saved files get a full incremental build (compile dependencies + typecheck dependents + compile resolved errors + drain intent). Uses a take-build-replace pattern: each project's `BuildCommandState` is removed from the `ProjectMap` under a brief lock, built without holding the mutex, then inserted back. This keeps LSP handlers (hover, completions, etc.) unblocked during compilation. Also drains `full_compile_intent` as step 4: modules already compiled in steps 1–3 are skipped (already at `Built`), remaining `TypeChecked` modules get `FullCompile`, and modules not yet ready (e.g. still at `CompileError`) are kept for future flushes.
268
270
3.**Typechecks third** — unsaved edits get a lightweight typecheck via `bsc -bs-read-stdin`, with brief lock for arg extraction only.
269
-
4.**Post-build recheck** — if a saved file also had unsaved buffer content (didChange + didSave in the same debounce window), a typecheck pass runs from the buffer so diagnostics match the editor.
271
+
4.**Post-build recheck** — if a saved file also had unsaved buffer content (didChange + didSave in the same debounce window), a typecheck pass runs from the buffer so diagnostics match the editor. Files whose build produced errors are skipped to avoid overwriting compile error diagnostics with clean buffer typecheck results.
270
272
5.**`buildFinished` notification** — sent when full builds or incremental builds ran.
271
273
272
274
Sequential execution within one consumer eliminates all races on `lib/lsp/` artifacts.
Each stage from `Parsed` onward also carries optional parse/compile warnings and optional interface hashes. This data model enables several optimizations that are not yet implemented:
662
+
Each stage from `Parsed` onward also carries optional parse/compile warnings and optional interface hashes. `Built` wraps a `FileBuiltState` struct that is also used for snapshots during hash-based promotion across project rebuilds.
663
+
664
+
**Already implemented:**
665
+
666
+
-**Hash-based promotion across project rebuilds**: When `project_build` reinitializes a project (e.g. due to atomic file writes from LLMs or git), it snapshots `FileBuiltState` for all `Built` modules before replacing the state. After the new `TypecheckOnly` build, modules whose implementation/interface/cmi/cmt hashes match the snapshot are promoted directly back to `Built` — no bsc invocation needed. Modules that can't be promoted (hash mismatch or were at `CompileError(FullCompile)`) are added to `full_compile_intent` for deferred JS emission on subsequent flushes.
667
+
668
+
**Not yet implemented:**
652
669
653
670
-**No-op save detection**: Compare the on-disk `source_hash` against the hash stored in the module's stage. If they match, the file hasn't actually changed (e.g., user hit save without editing). Parsing and compilation can be skipped entirely.
654
671
-**CMI-based skip in `TypecheckDependents`**: After compiling a saved file, compare its new `cmi_hash` against the pre-save value from the stage. If the interface is unchanged, no dependents need re-typechecking — saving an entire typecheck pass over the dependent closure.
0 commit comments