-
-
Notifications
You must be signed in to change notification settings - Fork 257
Reactive analysis blog. #1163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
cristianoc
wants to merge
8
commits into
master
Choose a base branch
from
reactive
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Reactive analysis blog. #1163
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
2e6274d
Create reactive-analysis.mdx
cristianoc 71bdd02
Update reactive-analysis.mdx
cristianoc 9aa154e
fix reactive analysis post metadata/links
cristianoc 969c9f7
Update reactive analysis post with monorepo support and simplified in…
cristianoc 56a94ce
extension released
cristianoc 73ea861
Clarify reactive analysis availability and version gating
cristianoc 5b58cc3
Refine reactive analysis blog narrative and diagrams
cristianoc 3ba27c7
Fix link checker false positives for SVG diagrams in blog post
cristianoc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| --- | ||
| author: rescript-team | ||
| date: "2025-12-28" | ||
| title: "Real-Time Analysis is Coming to ReScript" | ||
| badge: roadmap | ||
| description: | | ||
| ReScript's static analyzer is going reactive. Dead code detection that updates instantly as you edit, powered by novel reactive combinators. | ||
| --- | ||
|
|
||
| ## Introduction | ||
|
|
||
| Imagine editing a ReScript file and seeing dead code warnings update almost immediately as you work. No waiting for a full re-analysis command. Just continuous feedback about which parts of your code are actually used. | ||
|
|
||
| This is what we're bringing to ReScript. | ||
|
|
||
| The static analyzer that powers dead code detection is being rebuilt on a **reactive foundation**. When you add a reference to a function, the "unused" warning vanishes quickly. When you remove the last use of a module, the dead code warning appears right away. The analysis stays in sync with your code, updating in real time. | ||
|
|
||
| ## Why This Matters | ||
|
|
||
| Traditional static analyzers work in batch mode: gather all files, analyze everything, report results. This works fine when you run analysis as a CI check or a manual command. But it's not what you want when you're actively editing code. | ||
|
|
||
| Batch analysis has an awkward tradeoff: | ||
|
|
||
| - **Run it rarely** and feedback comes too late to be useful | ||
| - **Run it often** and you're constantly waiting | ||
|
|
||
| What developers actually want is _continuous_ feedback that keeps up with their typing speed. That's exactly what reactive analysis provides. | ||
|
|
||
| ## The Reactive Approach | ||
|
|
||
| Instead of re-analyzing your entire project on every change, the reactive analyzer represents the analysis as a **computation graph**. Each piece of data—declarations, references, liveness information—flows through this graph. When a file changes, only the affected parts of the graph recompute. | ||
|
|
||
| The result: analysis that completes in milliseconds for typical edits, even in large codebases. | ||
|
|
||
| ### A Concrete Update Flow | ||
|
|
||
| Suppose you have a call graph where `main` calls `helper`, and `helper` calls `format` and `validate`. In your edit, `validate` starts calling `old`. | ||
|
|
||
| With batch analysis, the analyzer scans everything again. With reactive analysis: | ||
|
|
||
| 1. The edited `.cmt` updates — **FlatMap** extracts the new reference. The compiler already resolved it: the ref knows its target (`old`) and its source location (inside `validate`) | ||
| 2. **Join** maps that source position to its containing declaration, producing a new graph edge: _validate → old_ | ||
| 3. **Fixpoint** propagates reachability from roots (`main`) along edges — `old` now becomes reachable and enters the live set | ||
| 4. A final **Classify** step (a join of declarations with the live set) marks `old` as live — so its dead-code warning disappears | ||
|
|
||
| The rest of the project graph stays untouched. | ||
|
|
||
| ### A Glimpse Under the Hood | ||
|
|
||
| Here is a simplified view of the reactive dead code pipeline: | ||
|
|
||
|  | ||
|
|
||
| The analysis pipeline is built from composable operators: | ||
|
|
||
| - **Sources** hold the raw data from your `.cmt` files | ||
| - **FlatMap** extracts declarations, references, and annotations from each file's data | ||
| - **Union** merges collections — for example, combining value references, type references, and exception references into a unified set | ||
| - **Join** maps data across collections — for example, mapping each reference to the declaration that contains it, or partitioning declarations into dead and live using the fixpoint result | ||
| - **Fixpoint** computes transitive reachability — starting from root declarations (entry points, annotated `@live`, or externally referenced), following edges to find everything that's live | ||
|
|
||
| That last one, **fixpoint**, is particularly interesting. Dead code detection needs to know which declarations are _reachable_ from your entry points. This is a classic graph traversal—but doing it incrementally is hard. When you add one edge to a graph, how do you efficiently update the set of reachable nodes without recomputing from scratch? | ||
|
|
||
| In the running example above, only one new edge (`validate → old`) is added, and fixpoint propagates liveness from that delta. | ||
|
|
||
|  | ||
|
|
||
| The reactive fixpoint operator solves exactly this problem. It takes a set of roots and a set of edges, and maintains the full reachable set. In this example, once `old` becomes reachable through `validate`, fixpoint only propagates through neighbors affected by that new edge, without revisiting unrelated parts of the graph. This is what makes the analysis fast enough for real-time use. | ||
|
|
||
| ### Glitch-Free by Design | ||
|
|
||
| There's a subtle correctness issue with reactive systems: **glitches**. If node A depends on both B and C, and both B and C update, node A might briefly see an inconsistent state—B's new value with C's old value, or vice versa. | ||
|
|
||
| For analysis, glitches mean incorrect results. A function might briefly appear dead because the reference to it hasn't propagated yet, even though it's actually used. | ||
|
|
||
| The reactive scheduler prevents this with a simple principle: **accumulate, then propagate**. All updates at each level of the graph are collected before any downstream nodes run. Nodes process in topological order, wave by wave. The analysis never sees partial updates. | ||
|
|
||
| ## What's Shipping | ||
|
|
||
| The reactive analysis infrastructure is landing now. Here's what it enables: | ||
|
|
||
| ### For Developers | ||
|
|
||
| - **Editor integration**: Available now in the latest extension, with reactive dead code updates during active development | ||
| - **Monorepo support**: The analyzer now works correctly in monorepo setups, running from the workspace root with proper binary lookup | ||
| - **Faster CI analysis**: Incremental runs are dramatically faster than full runs | ||
| - **Immediate feedback loop**: See the impact of your changes instantly | ||
|
|
||
| ### For Tooling Authors | ||
|
|
||
| The reactive primitives are general-purpose. The same infrastructure that powers dead code detection can express other analyses: | ||
|
|
||
| - Dependency graph visualization | ||
| - Unused export detection | ||
| - Reference counting and hotspot identification | ||
| - Custom project-specific checks | ||
|
|
||
| ## The Road Ahead | ||
|
|
||
| This is the beginning of a larger shift. The same reactive foundation will extend to other parts of the editor experience: | ||
|
|
||
| - **Type checking**: Incremental type feedback without waiting for builds | ||
| - **Navigation**: Jump-to-definition that stays accurate as files change | ||
| - **Refactoring**: Real-time previews of rename and move operations | ||
|
|
||
| The goal is an editor experience where the tooling _keeps up with you_—no waiting, no stale results, just continuous assistance. | ||
|
|
||
| ## Try It Today | ||
|
|
||
| **Requirements:** | ||
|
|
||
| Reactive analysis is a newer capability of Editor Code Analysis and requires a newer extension version. | ||
|
|
||
| - ReScript compiler >= 12.1.0 | ||
| - VSCode or Cursor with rescript-vscode extension >= 1.73.9 (pre-release) | ||
|
|
||
| 1. Start the build watcher (accept the prompt when opening a ReScript file, or run **"ReScript: Start Build"**) | ||
| 2. Run **"ReScript: Start Code Analyzer"** | ||
|
|
||
| Dead code warnings appear in your editor and update reactively while you develop (currently driven by save/build updates). For configuration options and usage details, see the [Dead Code Analysis guide](../docs/manual/editor-code-analysis.mdx). | ||
|
|
||
| ## Acknowledgments | ||
|
|
||
| The reactive primitives are based on work by [SkipLabs](https://skiplabs.io). Their reactive collections library provides the foundation for glitch-free, incremental computation that makes real-time analysis possible. | ||
|
|
||
| --- | ||
|
|
||
| We're excited to bring this to the ReScript community. Static analysis that runs continuously, in the background, without you having to think about it—that's the experience we're building toward. | ||
|
|
||
| Stay tuned for broader reactive tooling updates. And as always, we welcome feedback on [GitHub](https://github.com/rescript-lang/rescript) and on the [forum](https://forum.rescript-lang.org). | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| %%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#e8f5e9', 'primaryTextColor': '#102a43', 'primaryBorderColor': '#4caf50', 'lineColor': '#486581'}}}%% | ||
| flowchart LR | ||
| A_main["<b>main</b><br/><small>root</small>"] | ||
| A_helper["helper"] | ||
| A_format["format"] | ||
| A_validate["validate"] | ||
| A_old["old"] | ||
|
|
||
| A_main -- "wave 1" --> A_helper | ||
| A_helper -- "wave 2" --> A_format | ||
| A_helper -- "wave 2" --> A_validate | ||
| A_validate -. "new edge: validate → old" .-> A_old | ||
|
|
||
| classDef rootNode fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px,color:#102a43 | ||
| classDef liveNode fill:#e8f5e9,stroke:#4caf50,stroke-width:2px,color:#102a43 | ||
| classDef newlyLive fill:#fff9c4,stroke:#f9a825,stroke-width:2.5px,color:#102a43 | ||
|
|
||
| class A_main rootNode | ||
| class A_helper,A_format,A_validate liveNode | ||
| class A_old newlyLive |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's worth adding that it's "project wide dead code warnings". Other tools (eslint etc) has plenty of dead code warnings etc for the current file. But this is for the entire project, something that is notoriously bad/slow in most solutions.