Mutability Inference & Aliasing
The compiler infers how values are created, passed, frozen, mutated, and related through aliases or captures. It records these facts as externally visible effects for each function.
This exists because mutation is not only a property of one variable: a write can affect direct aliases, captured values, or values reached through uncertain data flow. Later compiler passes use these results to decide which values can be safely memoized and which writes should produce diagnostics.
Sources: compiler/packages/babel-plugin-react-compiler/src/Inference/AliasingEffects.ts:29-175, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts:98-224, compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts:48-127
Core concepts
Aliasing effects
An aliasing effect is a typed description of value creation, data flow, freezing, mutation, or possible mutation.
The important distinction is between a plain mutation and an aliasing relationship. Mutate writes a value and its direct aliases; Capture records information flowing into another value without making local mutation of the destination mutate the source; and Alias records shared identity through which mutation can flow.
| Effect | Plain meaning | Mutation propagation |
|---|---|---|
Mutate | Write the value and its direct aliases. | Direct aliases are affected. |
MutateTransitive | Write the value, direct aliases, and captured values. | Captures are also affected. |
Capture | Store information from one value inside another. | Destination mutation does not imply source mutation. |
Alias | Record that two places may refer to the same value. | Mutation can flow between both places. |
MaybeAlias | Record a possible alias from uncertain data flow. | Propagated mutations become conditional. |
MaybeAlias represents a possible relationship for unknown calls and makes mutations flowing through that relationship conditional.
Sources: compiler/packages/babel-plugin-react-compiler/src/Inference/AliasingEffects.ts:29-175
Abstract value kinds
An abstract value is a value kind plus the reasons supporting that classification.
InferenceState keeps the abstract kind of each allocation and the set of values currently referenced by each identifier. A set accommodates phi points, where a variable may have different values on different control-flow paths.
When states merge, the compiler merges both abstract kinds and their reasons. For example, combining frozen and mutable values produces MaybeFrozen.
Sources: compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts:2796-2799, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts:1310-1668, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts:1545-1602, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts:2950-2990
Mutation ranges
Mutation-range inference tracks each node's last mutation position and extends mutableRange.end when a mutation is propagated. MutationKind distinguishes no mutation, conditional mutation, and definite mutation.
This lets later checks ask whether a function expression's parameters may remain mutable beyond their starting point.
Sources: compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges.ts:573-577, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges.ts:579-598, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges.ts:704-842, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts:2521-2576
How effects are inferred
inferMutationAliasingEffects starts with an empty InferenceState, initializes context values, classifies parameters, and queues the function entry block. It merges states for control-flow paths and processes queued blocks until no queued state remains.
Within a block, inferBlock computes or retrieves an instruction signature, applies that signature to the current state, and stores the resulting effects on the instruction.
Arrays and objects create mutable values and capture their elements or properties. An Await expression creates a mutable result, conditionally mutates its awaited value transitively, and captures data from that value into the result.
The state distinguishes replacing an identifier's values from appending an alias. assign sets one identifier's value set to a copy of another identifier's values, while appendAlias sets the destination to the union of its previous values and the source values.
A value can also become frozen. freeze freezes mutable, context, or maybe-frozen values, while freezeValue records the frozen classification and may recursively freeze a function's captured context.
The data flow below shows the ordered hand-off from function analysis through effect and range inference to reactive-scope inference.
Evidence
- function-analysiscompiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts:48
- effect-inferencecompiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts:48
- effect-inferencecompiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts:98
- range-inferencecompiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts:48
- range-inferencecompiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges.ts:75
- reactive-scopescompiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts:48
lowerWithMutationAliasing runs effect inference, dead-code elimination, range inference, reassignment rewriting, and reactive-scope inference in that order; it then stores the function's externally visible effects.
Sources: compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts:98-224, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts:1545-1602, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts:486-561, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts:572-671, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts:1724-2316, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts:1391-1400, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts:1402-1415, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts:1435-1459, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts:1461-1475, compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts:48-127
How mutation ranges propagate through aliases
inferMutationAliasingRanges first builds an abstract model of values, alias and capture edges, and mutations. It delays processing mutations until all blocks and phi connections have been visited, allowing mutations to account for values flowing both into and out of a variable.
AliasingState stores one Node per identifier. Each node records created-from links, captures, definite aliases, possible aliases, ordered edges, local and transitive mutation kinds, the last mutation position, and the mutation reason.
createFrom, capture, assign, and maybeAlias add different edge types. createFrom and assign add alias edges, capture adds a capture edge, and maybeAlias adds a possible-alias edge.
When mutate processes a mutation, it records the mutation position, updates the node's mutable-range end, and walks related edges with a work queue. Forward traversal follows capture and alias edges; backward traversal follows aliases and created-from relationships. Traversing a maybeAlias edge downgrades the propagated mutation to conditional.
The separate treatment of Capture and Alias is why aliasing cannot be reduced to plain mutation: capture records information flow, while aliasing records a relationship through which a mutation can propagate.
Sources: compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges.ts:75-557, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges.ts:579-598, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges.ts:599-843, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges.ts:704-842, compiler/packages/babel-plugin-react-compiler/src/Inference/AliasingEffects.ts:29-175
What depends on the result
The function-analysis pipeline places inferMutationAliasingRanges before inferReactiveScopeVariables and stores the resulting effects on the function. It also marks context operands as captured or read according to whether they were captured or mutated.
The range information is consulted when checking whether function arguments are immutable and non-mutating. A function expression whose parameter range extends beyond its start is treated as potentially mutating its inputs, so those arguments are not accepted as safely non-mutating.
If this inference is skipped, the compiler loses the range evidence used by that check. The shown validation behavior warns that mismatched inferred dependencies can make a value change more or less frequently than expected, so omitting mutation and aliasing information can undermine the dependency reasoning required for correct memoization.
The pass also supports diagnostics. Mutating a frozen value produces an immutability diagnostic, and mutation errors from nested functions can be appended to the surrounding environment.
Sources: compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts:48-127, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts:2521-2576, compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts:287-289, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts:572-671, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges.ts:559-571
How it connects
This analysis sits inside the compiler's function-analysis pipeline and hands aliasing effects to reactive-scope inference. It is therefore a foundation for Reactive Scopes & Codegen, where co-mutating variables are grouped into reactive scopes.
It operates on HIR values and instruction signatures, connecting to Compiler HIR & Passes. Its inferred effects also support the broader optimization and validation behavior described in Compiler Overview & Pipeline and Compiler Validation & Diagnostics.
Sources: compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts:48-127, compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts:30-45, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts:486-561, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts:572-671
Key takeaways
Aliasrepresents a relationship through which mutation can propagate;Capturerepresents information flow into another value.inferMutationAliasingRangesbuilds ordered alias, capture, and possible-alias relationships before propagating mutations.- A
MaybeAliasedge downgrades propagated mutation to conditional. - Range inference runs before
inferReactiveScopeVariablesand helps identify functions that may mutate their inputs. - Skipping the pass removes evidence used in dependency and memoization reasoning.
Sources: compiler/packages/babel-plugin-react-compiler/src/Inference/AliasingEffects.ts:29-175, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges.ts:75-557, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges.ts:704-842, compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts:48-127, compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts:2521-2576