1# pruneNonReactiveDependencies23## File4`src/ReactiveScopes/PruneNonReactiveDependencies.ts`56## Purpose7This pass removes dependencies from reactive scopes that are guaranteed to be **non-reactive** (i.e., their values cannot change between renders). This optimization reduces unnecessary memoization invalidations by ensuring scopes only depend on values that can actually change.89The pass complements `PropagateScopeDependencies`, which infers dependencies without considering reactivity. This subsequent pruning step filters out dependencies that are semantically constant.1011## Input Invariants12- The function has been converted to a ReactiveFunction structure13- `InferReactivePlaces` has annotated places with `{reactive: true}` where values can change14- Each `ReactiveScopeBlock` has a `scope.dependencies` set populated by `PropagateScopeDependenciesHIR`15- Type inference has run, so identifiers have type information for `isStableType` checks1617## Output Guarantees18- **Non-reactive dependencies removed**: All dependencies in `scope.dependencies` are reactive after this pass19- **Scope outputs marked reactive if needed**: If a scope has any reactive dependencies remaining, all its outputs are marked reactive20- **Stable types remain non-reactive through property loads**: When loading properties from stable types (like `useReducer` dispatch functions), the result is not added to the reactive set2122## Algorithm2324### Phase 1: Collect Reactive Identifiers25The `collectReactiveIdentifiers` helper builds the initial set of reactive identifiers by:261. Visiting all places in the ReactiveFunction272. Adding any place marked `{reactive: true}` to the set283. For pruned scopes, adding declarations that are not primitives and not stable ref types2930### Phase 2: Propagate Reactivity and Prune Dependencies31The main `Visitor` class traverses the ReactiveFunction and:32331. **For Instructions** - Propagates reactivity through data flow:34 - `LoadLocal`: If source is reactive, mark the lvalue as reactive35 - `StoreLocal`: If source value is reactive, mark both the local variable and lvalue as reactive36 - `Destructure`: If source is reactive, mark all pattern operands as reactive (except stable types)37 - `PropertyLoad`: If object is reactive AND result is not a stable type, mark result as reactive38 - `ComputedLoad`: If object OR property is reactive, mark result as reactive39402. **For Scopes** - Prunes non-reactive dependencies and propagates outputs:41 - Delete each dependency from `scope.dependencies` if its identifier is not in the reactive set42 - If any dependencies remain after pruning, mark all scope outputs as reactive4344### Key Insight: Stable Types45The pass leverages `isStableType` to prevent reactivity from flowing through certain React-provided stable values:4647```typescript48function isStableType(id: Identifier): boolean {49 return (50 isSetStateType(id) || // useState setter51 isSetActionStateType(id) || // useActionState setter52 isDispatcherType(id) || // useReducer dispatcher53 isUseRefType(id) || // useRef result54 isStartTransitionType(id) ||// useTransition startTransition55 isSetOptimisticType(id) // useOptimistic setter56 );57}58```5960## Edge Cases6162### Unmemoized Values Spanning Hook Calls63A value created before a hook call and mutated after cannot be memoized. However, if it's non-reactive, it still should not appear as a dependency of downstream scopes.6465### Stable Types from Reactive Containers66When `useReducer` returns `[state, dispatch]`, `state` is reactive but `dispatch` is stable. The pass correctly handles this.6768### Pruned Scopes with Reactive Content69The `CollectReactiveIdentifiers` pass also examines pruned scopes and adds their non-primitive, non-stable-ref declarations to the reactive set.7071### Transitive Reactivity Through Scopes72When a scope retains at least one reactive dependency, ALL its outputs become reactive.7374## TODOs75None in the source file.7677## Example7879### Fixture: `unmemoized-nonreactive-dependency-is-pruned-as-dependency.js`8081**Input:**82```javascript83function Component(props) {84 const x = [];85 useNoAlias();86 mutate(x);8788 return <div>{x}</div>;89}90```9192**Before PruneNonReactiveDependencies:**93```94scope @2 dependencies=[x$15_@0:TObject<BuiltInArray>] declarations=[$23_@2]95```9697**After PruneNonReactiveDependencies:**98```99scope @2 dependencies=[] declarations=[$23_@2]100```101102The dependency on `x` is removed because `x` is created locally and therefore non-reactive.103104### Fixture: `useReducer-returned-dispatcher-is-non-reactive.js`105106**Input:**107```javascript108function f() {109 const [state, dispatch] = useReducer();110111 const onClick = () => {112 dispatch();113 };114115 return <div onClick={onClick} />;116}117```118119**Generated Code:**120```javascript121function f() {122 const $ = _c(1);123 const [, dispatch] = useReducer();124 let t0;125 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {126 const onClick = () => {127 dispatch();128 };129 t0 = <div onClick={onClick} />;130 $[0] = t0;131 } else {132 t0 = $[0];133 }134 return t0;135}136```137138The `onClick` function only captures `dispatch`, which is a stable type. Therefore, `onClick` is non-reactive, and the JSX element can be memoized with zero dependencies.
Findings
✓ No findings reported for this file.