Dead code analysis and other experimental analyses for ReScript.
- Dead Code Elimination (DCE) - Detect unused values, types, and modules
- Exception Analysis - Track potential exceptions through call chains
- Termination Analysis - Experimental analysis for detecting non-terminating functions
# Run DCE analysis on current project (reads rescript.json)
rescript-tools reanalyze -config
# Run DCE analysis on specific CMT directory
rescript-tools reanalyze -dce-cmt path/to/lib/bs
# Run all analyses
rescript-tools reanalyze -allAnalysis is reactive: processed file data is cached and unchanged files are skipped on subsequent runs. This matters for repeated analysis, such as a watch mode or the server:
| Run | CMT Processing | Total | Speedup |
|---|---|---|---|
| Cold | 0.78s | 1.01s | 1x |
| Warm | 0.01s | 0.20s | 5x |
Run analysis multiple times to measure cache effectiveness:
rescript-tools reanalyze -config -timing -runs 3| Flag | Description |
|---|---|
-config |
Read analysis mode from rescript.json |
-dce |
Run dead code analysis |
-exception |
Run exception analysis |
-termination |
Run termination analysis |
-all |
Run all analyses |
-runs n |
Run analysis n times (for benchmarking) |
-churn n |
Remove/re-add n random files between runs (incremental correctness/perf testing) |
-timing |
Report timing of analysis phases |
-mermaid |
Output Mermaid diagram of reactive pipeline (to stderr) |
-transitive |
Force transitive reporting (overrides rescript.json) |
-no-transitive |
Disable transitive reporting (overrides rescript.json) |
-debug |
Print debug information |
-json |
Output in JSON format |
-ci |
Internal flag for CI mode |
See ARCHITECTURE.md for details on the analysis pipeline.
analysis/reanalyze/diagrams/reactive-pipeline-full.mmd is generated from the live reactive graph printer (Reactive.to_mermaid()), and we check in the non-transitive (-no-transitive) variant because that is where cross-file hasRefBelow suppression is relevant (and where reactive invalidation bugs are easiest to spot).
To regenerate it:
# Run from any ReScript project (so -config works), then capture stderr:
rescript-tools reanalyze -config -no-transitive -mermaid \
>/dev/null 2> analysis/reanalyze/diagrams/reactive-pipeline-full.mmdThe DCE analysis is structured as a pure pipeline:
- MAP - Process each
.cmtfile independently → per-file data - MERGE - Combine all per-file data → project-wide view
- SOLVE - Compute dead/live status → issues
- REPORT - Output issues
This design enables order-independence and incremental updates.
The analysis caches processed per-file results and efficiently skips unchanged files on subsequent runs:
- First run: All files are processed and results cached
- Subsequent runs: Only changed files are re-processed
- Unchanged files: Return cached
file_dataimmediately (no I/O or unmarshalling)
This is the foundation for the reanalyze-server — a persistent analysis service that keeps reactive state warm across requests.
A long-lived server process that keeps reactive analysis state warm across multiple requests. This enables fast incremental analysis for editor integration.
When a server is running on the default socket (<projectRoot>/.rescript-reanalyze.sock), the regular reanalyze command automatically delegates to it. This means:
- Start the server once (in the background)
- Use the editor normally — all
reanalyzecalls go through the server - Enjoy fast incremental analysis — typically 10x faster after the first run
This works transparently with the VS Code extension's "Start Code Analyzer" command.
# From anywhere inside your project, start the server:
rescript-tools reanalyze-server
# Now any reanalyze call will automatically use the server:
rescript-tools reanalyze -json # → delegates to serverrescript-tools reanalyze-server [--socket <path>]Options:
--socket <path>— Unix domain socket path (default:<projectRoot>/.rescript-reanalyze.sock)
Examples:
# Start server with default socket (recommended)
rescript-tools reanalyze-server \
# With custom socket path
rescript-tools reanalyze-server \
--socket /tmp/my-custom.sock \- Transparent delegation: Regular
reanalyzecalls automatically use the server if running - Default socket:
<projectRoot>/.rescript-reanalyze.sock(used by both server and client) - Socket location invariant: socket is always in the project root;
reanalyzemay be called from anywhere inside the project - Same output: stdout/stderr/exit-code match what a direct CLI invocation would produce
- Incremental updates: When source files change and the project is rebuilt, subsequent requests reflect the updated analysis
- Start server (once, in background)
- Edit source files
- Rebuild project (
yarn build/rescript build) - Use editor — analysis requests automatically go through the server
- Stop server when done (or leave running)
# Run reanalyze tests
make test-reanalyze
# Run with shuffled file order (order-independence test)
make test-reanalyze-order-independenceThe order-independence test uses the test-only CLI flag -test-shuffle, which randomizes the per-file processing order to ensure results don’t depend on traversal order.
The benchmark project generates ~5000 files to measure analysis performance:
cd tests/analysis_tests/tests-reanalyze/deadcode-benchmark
# Generate files, build, and run benchmark
make benchmark
# Compare CMT cache effectiveness (cold vs warm)
make time-cache
# Benchmark reactive mode (shows speedup on repeated runs)
make time-reactiveThe make time-reactive target runs:
- Standard mode (baseline) - Full analysis every time
- 3 runs - the first is cold (processes all files), subsequent runs are warm (skip unchanged files)
Example output:
=== Reactive mode benchmark ===
Standard (baseline):
CMT processing: 0.78s
Total: 1.01s
Reactive mode (3 runs):
=== Run 1/3 ===
CMT processing: 0.78s
Total: 1.02s
=== Run 2/3 ===
CMT processing: 0.01s <-- 74x faster
Total: 0.20s <-- 5x faster
=== Run 3/3 ===
CMT processing: 0.01s
Total: 0.20s