InferReactiveIdentifiers has some extra logic to find identifiers declared in
the same scope, and promote non-reactive identifiers to reactive if they appear
inside a reactive scope (reactive scope == scope with one or more (reactive)
dependencies). Even though the identifier alone might not be technically
reactive (have no reactive inputs), it can get re-recreated if the scope
re-evaluates.
We can now do this during PruneNonReactiveDependencies as we exit out of each
scope.
I removed fixpoint iteration and all tests pass, which matches my intuition that
it's really that we need strictly two passes. Removing to simplify and for
performance (avoid unnecessary extra visits of the ast)
The fact that InferReactiveIdentifiers is integrated directly into
PropagateScopeDependencies has made the latter pretty tricky to debug at times.
If a dependency is missing, we have to introspect and figure out if that's
because it was somehow inferred as non-reactive. This PR creates a new
PruneNonReactiveDependencies pass to separate out these phases.
Optimizes dead code elimination. Currently it keeps iterating the control flow
graph until no new usages have been discovered, which accounts for usages across
loops. However, when there are no loops it's sufficient to iterate the CFG
exactly once.
With the upcoming changes to SSA renaming in #1194, we rewrite phi operand
identifiers to have the same IdentifierId as the declaration the identifier
originated from: so downstream checks need to compare ids instead of the
identifier instance.
This tracks whether a value is a context ref or generated from a context ref.
This lets us track mutations to context refs and treat it separately as we want
this to be more conservative than our existing inference.
ValueKind.Context is exactly like ValueKind.Mutable but is more conservative.
This is a temporary fix for the issue we discovered on our first integration,
where destructuring of a function return value is emitting the function call
multiple times:
```javascript
// Input
const [x, setX] = useState(null);
// Output
const x = useState(null)[0];
const setX = useState(null)[1];
```
The reason this happens is that we lower `useState(null)` to a temporary, and
then generate a ComputedLoad for each of x and setX. Codegen doesn't emit
temporaries eagerly - it assumes they are going to be used exactly once and it
re-emits the value each time the temporary is used. Hence why the
`useState(null)` part gets duplicated in the output.
Right now destructuring is the only place i'm aware of where we reuse
temporaries this way. And we do want to change codegen to preserve destructuring
in the output to correctly handle array patterns. However, that's a more
involved change. For now, this PR is a stopgap. During the pass where we promote
temporaries used in scopes to named variables, we now check to see if those
temporaries are used multiple times and promote them.
The above example would then generate something like
```javascript
const t0 = useState(null);
const x = t0[0];
const setX = t0[1];
```
This is still incorrect (it assumes t0 is an array), but it's more likely to
work in practice. I'll revert this change once we correctly handle
destructuring.
Configures typescript-eslint for the project with an initial configuration that
starts with their recommended rules, and adds/disables a few (generally either
disabling warnings or promoting them to errors). The new `yarn lint` command is
not hooked up to CI yet, so for now this is something we can opt-in to running
locally. If you have some free time, help get us down to zero errors!
My general philosophy for linting, which I propose we follow, is that lints
should be very high-signal:
* Error, don't warn. If it's worth mentioning it's worth fixing.
* Enable rules that consistently identify real problems. If we frequently would
have to disable the rule due to false positives, it isn't high-signal.
* Enable rules that help improve consistent style (to avoid code review about
style rather than substance).
I realized we hadn't updated InferReferenceEffects to match our latest thinking
on hooks. Specifically, we will default to assuming that hooks can mutate their
arguments and return mutable values — this works with our model since we don't
treat hooks specially for reactive scope construction. Ie, first we figure out
what variables construct together, then we create scopes, then we prune scopes
that contain hooks. So changing the reference effects for hooks "just works".
Note that it is helpful for our unit tests to have an example hook that we know
_does_ freeze its input and return a frozen value, so i've temporarily added
`useFreeze()` to the list of defined hooks. That is meant as a stopgap: the
right solution is to allow some way to tell the compiler about specific custom
hooks and their semantics.
Adds a subclass of ReactiveFunctionVisitor, ReactiveFunctionTransform, which
makes it easier to write passes that change the shape of a ReactiveFunction. The
two use-cases converted so far are both flattening away certain categories of
reactive scopes — this will make it easier to add a similar pass to prune scopes
that contain hook calls.
Effect.Capture is very similar to Effect.Read, but the only difference is that
this reference is stored somewhere via a Effect.Store.
Previously, any operand associated with a Effect.Store in the same instruction
would get aliased -- so there was no need to explicitly differentiate between a
"normal read" and "read that gets stored".
This difference is now explicit with FunctionExpression where every dependency
is "read" but only a few are "captured" for store (and mutation). In a follow up
PR, the mutating deps will have an Effect.Capture to differentiate from the
other non mutating deps (Effect.Read).
This commit adds a new Program visitor to our Babel plugin which then calls our
FunctionDeclaration visitor. Babel does some "smart" merging of plugin passes so
so even if plugin A is inserted prior to plugin B, if A does not have a Program
visitor and B does, B will run first.
Note that we also can't use Forget inside of a Babel preset as plugins run
_before_ presets (https://babeljs.io/docs/en/plugins/#plugin-ordering).