Commit Graph

56 Commits

Author SHA1 Message Date
Joe Savona 76aaf32c55 HIR StoreLocal.type uses babel type 2024-02-05 21:51:09 -08:00
Lauren Tan 7bce302421 Make Other mutation validation message more generic
The current error message "This mutates a global or a variable after it 

was passed to React" no longer makes sense since we now have more 

specific error messages for different kinds of Effect.Mutate or 

Effect.Stores. This replaces the fallthrough "Other" case with a 

more generic message. It's not perfect, but it's a little more accurate 

than what is currently emitted 

The proper fix might be to treat functions as mutable objects and allow 

the mutation, or special case `Function.displayName`. For now though 

this PR just updates the message in the meantime so it's less 

confusing.
2024-02-01 11:02:30 -05:00
Joe Savona d55420c430 Allow prefixed hooks for compiling bundled code
We're doing some internal benchmarking using a lightweight bundler that @pieterv 
wrote for experimentation purposes. It's designed to fully preserve Flow type 
annotations so we can experiment with type-driven compilation and test out what 
benefits we might get from "cross-module" compilation more easily (ie by just 
bundling together a few modules so we can see them all as one). 

However, the bundler renames local variables and imports, so that a reference to 
`useMemo()` might end up as `React$useMemo()` or similar. This PR adds a flag to 
tell the compiler that builtin hooks might be prefixed and resolve them 
appropriately.
2024-01-30 22:11:17 -05:00
Joe Savona 57163f0a52 InferReactivePlaces account for immutable aliases of mutably aliased values
I found this by adding logic to reject inputs where reactivity gets newly 
propagated in PruneNonReactiveDependencies. It's possible to create a readonly 
alias to a mutable value such that we don't know the value is reactive yet when 
the alias is created. Thus we need to do a fixpoint iteration even if there are 
no loops in order to be able to revisit such aliases and reflow the reactivity 
forward. Example: 

```javascript 

const x = []; 

const y = x; 

const z = [y]; // y isn't reactive yet when we first visit this, so z is 
initially non-reactive 

y.push(props.value); // then we realize y is reactive. we need a fixpoint to 
propagate this back to z 

const a = [z]; // need an indirection to get past the partial propagation in 
PruneNonReactiveDependencies 

let b = 0; 

if (a[0][0]) { 

b = 1; 

} 

return [b]; 

``` 

Existing fixtures don't change because the basic reactivity propagation in 
PruneNonReactiveDependencies is enough to make common cases work. I confirmed 
that the new fixture does not work on previous PR in the stack.
2024-01-22 15:35:55 -08:00
Joe Savona 8be56418d3 InferReactivePlaces accounts for mutable aliasing
Fixes T175227223. When inferring reactivity, mutation of a value with a reactive 
input marks the mutable value as reactive. However, we also need to account for 
aliases: 

```javascript 

const x = []; 

const y = x; 

y.push(props.value); 

``` 

Previously we would have only considered `y` reactive here, but `x` also becomes 
reactive. 

The implementation extracts out a helper from InferReactiveScopeVariables that 
builds a `DisjointSet<Identifier>` of disjoint sets of mutably aliased values. 
InferReactivePlaces then treats all instances of each mutable alias group as 
equivalent for reactivity purposes.
2024-01-19 16:03:58 -08:00
Joe Savona c8323f3b42 Mutation within a reactively controlled block propagates reactivity
In InferReactivePlaces, we already account for reactively controlled values: 
where a value is never assigned a non-reactive value, but _which_ value is 
assigned is based on a reactive condition (the test conditions of an if, switch, 
loop, etc). 

This PR extends that reactively-controlled inference to mutation that is 
conditioned upon a reactive value. From the test case: 

```javascript 

let x = []; 

if (props.cond) { 

// This mutation has no reactive inputs. 

// *But* the mutation conditionally occurs based on props.cond which is reactive 

x.push(1); 

} 

let y = false; 

if (x[0]) { // therefore the value observed here is reactive 

y = true; 

} 

// so the value of y here is reactive via the reactive control dependency x[0] 

return [y]; 

```
2024-01-19 10:03:51 -08:00
Jan Kassens 8a634bc1c0 Add frozen reason for props and hook arguments
Add frozen reason for props and hook arguments 

Improves the error message when mutating props or hook arguments. 

Previously, this would print a generic error about mutating global variables.
2024-01-12 14:51:59 -05:00
Joe Savona c80f0f022c useContext returns frozen values
This was an oversight in the original definition of useContext (oops my bad). 
Context values are owned by React and should not be modified. I found this 
because some cases of existing useMemo were not preserved (tested via the 
validatePreserveExistingManualMemo flag) due to function calls referencing 
context being assumed to mutate. 

This change will allow more memoization, it's also just more correct for the 
rules of React. Note the new ValueReason variant so that we can provide a 
precise error message about mutating context values.
2024-01-10 16:12:17 -08:00
Joe Savona f504eaa16e Add back transitive freeze functions option
Adds back a mode to transitively freeze function expressions, independently from 
the mode to preserve existing manual memoization. This lets us experiment with a 
few variants: 

* Preserve existing memoization 

* Validate existing memoization with: 

* `enableAssumeHooksFollowRulesOfReact` && 
`enableTransitivelyFreezeFunctionExpressions` 

* `enableAssumeHooksFollowRulesOfReact` only 

* neither of those flags 

Note that `enableTransitivelyFreezeFunctionExpressions` alone probably doesn't 
make sense, it's more aggressive than 

`enableAssumeHooksFollowRulesOfReact` so we might as well try them together.
2023-12-18 15:33:02 -08:00
Joe Savona c77acb3ac6 Separate mode to validate preserving manual memoization
Adds a new mode which validates that existing manual memoization is preserved 
_without_ using information from the manual memoization to affect compilation. 
This gives us a way to try out the more aggressive version of Forget — ignoring 
manual memoization — first and see how much code bails out and what patterns 
cause this. 

We can then proceed to enable the mode to actually _preserve_ existing memo 
guarantees only where necessary.
2023-12-15 17:12:06 -08:00
Joe Savona c6200d1a2b Fix comments, extend fixtures 2023-12-15 15:19:41 -08:00
Joe Savona 6747d4e33c PreserveMemo for useCallback transitively freezes function exprs
Merges `@enableTransitivelyFreezeFunctionExpressions` into the new 
`@enablePreserveExistingMemoizationGuarantees` mode, since they are both 
motivated by the same use case of preserving effect behavior by preserving 
existing memoization behavior. 

The idea is that `useCallback` has an implicit assumption: that the variables 
captured by the callback aren't subsequently modified. Previous PRs treated the 
values directly captured by the callback as frozen. But if those variables were 
themselves another function expression, and that expression captured a mutable 
value, then we wouldn't consider the freeze to be transitive: 

```javascript 

const object = makeObject(); 

useHook(); // oops, hook call inside `object`'s mutable range, can't memoize 
object, log, or onClick! 

const log = () => { console.log(object) }; 

const onClick = useCallback(() => { log() }); 

maybeMutate(object); 

``` 

However, the assumption of such code is that it _doesn't_ modify such 
transitively captured values. So here we merge 
`@enableTransitivelyFreezeFunctionExpressions` mode into the 
memoization-preserving mode. Now, the memoize instructions emitted for 
useCallback (and useMemo) will transitively freeze captured function 
expressions, allowing us to memoize. 

The flip side of this is that some code may be violating these rules. We'll rely 
on runtime validation to detect such cases.
2023-12-15 15:19:40 -08:00
Joe Savona 5bfd70ac6f Preserve memoization guarantees for useCallback
Improves `@enablePreserveExistingMemoizationGuarantees` for the useCallback 
case. Similar to useMemo, we add an explicit `Memoize` instruction for the 
callback function itself _and_ for its dependencies. This means we'll assume the 
callback doesn't mutate any captured variables. 

TODO: check this with cases involving refs (should be allowed, but also not 
accidentally freeze the ref) and reassignment of locals (should be disallowed, 
though that might just be a validation we're missing today)
2023-12-15 13:47:24 -08:00
Joe Savona 86e2edfa87 Prune memoize instructions in codegen
The previous PR introduced `memoize` instructions whose lvalues aren't used, but 
which can't be pruned by DCE due to pipeline ordering. Here we change to make 
memoize an instruction intended for its side effects only, and prune during 
codegen.
2023-12-15 13:47:23 -08:00
Joe Savona 723b616c67 enablePreserveMemo treats memo deps as frozen
See discussion on #2448 for full context. In the new 
`@enablePreserveExistingMemoizationGuarantees` mode, the goal is to preserve the 
existing referential equality guarantees from the original code. #2448 lays the 
groundwork by explicitly marking the _output_ of each useMemo block as memoized, 
hinting to the compiler that the value cannot subsequently change. This ensures 
the mutable range doesn't extend _later_, possibly overlapping a hook call and 
causing memoization to gett pruned. 

This PR fixes the other direction. There are cases where free variables 
referenced in the useMemo block could have been inferred as mutated, which could 
then extend the _start_ of the range earlier past a hook: 

```javascript 

const foo = createObject(); 

useBar(); 

const baz = useMemo(() => { 

const baz = createObject(); 

maybeMutate(foo, baz); 

return baz; 

}, [foo]); 

``` 

Here the compiler would infer that both `baz` and `foo` are mutable at the 
`maybeMutate()` call, grouping them in the same scope. But that scope would span 
the `useBar()` call, and be pruned, meaning that `baz` went unmemoized. 

However, useMemo blocks shouldn't be mutating free variables. Only variables 
newly created within the useMemo block should be mutable. So this PR extends the 
feature to treat all free variables referenced in a useMemo block as frozen as 
of the block itself.
2023-12-15 13:47:22 -08:00
Joe Savona ec27708024 [be] Remove unnecessary check for SetState type in analyzefunctions
This was used as part of the previous ValidateNoSetStateInRender, but i rewrote 
that to not rely on the mutable range of function expressions.
2023-12-15 13:47:21 -08:00
Joe Savona 2abd439b43 Option to preserve existing memoization guarantees
Adds an option to preserve existing memoization guarantees for values produced 
with useMemo and useCallback. We still discard the calls to these hooks, but we 
preserve the information that the value is frozen at that point in the program. 
Because these values are produced solely within the useMemo/useCallback 
callback, their mutation cannot have any interspersed hook calls. This means 
that the values mutable range will never span a hook and end at the point of the 
useMemo, ensuring that they are memoized at the same point. 

The main things that can change (relative to the orignal code) are: 

* Forget will infer a precise set of dependencies, ignoring the user-provided 
values. In practice this should only occur if the original code had a lint 
violation, which Forget would bail out on. So in practice this shouldn't happen 
unless the code doesn't use the React linter. 

* Forget may start the memoization block earlier than the developer did if other 
values are mutated along with the value being produced. This can cause 
memoization to fail, but only in situations where it would have failed 
previously: 

```javascript 

const a = []; 

useFoo(); 

const b = useMemo(() => { 

const c = a; 

c.push(1); 

return c; 

}, [a]); 

``` 

In this example (sans Forget) the useMemo will invalidate on every render 
because `a` will always be a new array and its listed as a dependency of the 
useMemo. Forget would correctly determine that the memoization would have to 
work as follows: 

```javascript 

let c; 

if (...) { 

const a = [] 

useFoo(); // OOPS we made a hook call conditional 

const t0 = a; 

t0.push(1); 

c = t0; 

... 

} else { 

c = $[...] 

} 

``` 

Because this is invalid, Forget would (later in the pipeline) strip out this 
memoization block and (as with the original) leave `c` un-memoized. 

In this same example, removing the hook would cause Forget to be able to memoize 
a value that wasn't memoized before: 

```javascript 

const a = []; 

const b = useMemo(() => { 

const c = a; 

c.push(1); 

return c; 

}, [a]); 

``` 

This invalidates every render without Forget, but would memoize correctly with 
Forget (it would expand the memoization block to include the declaration of 
`a`).
2023-12-15 13:47:20 -08:00
Jan Kassens afbaa8d3ca Add a reason to ValueKind for better error messages (#2447) 2023-12-15 10:20:14 -05:00
Joe Savona 12fbfc4fee Use variable type annotations to drive inference
Builds on the utilities added previously to infer types from type annotations on 
variable declarations. This is a limited form, where currently we only infer for 
local identifiers (not function parameters) and only infer a type for the 
variable initializer and not subsequent reassignments.
2023-12-11 11:34:29 -08:00
Joe Savona fa8f47eb41 InferReactivePlaces understands setState type
I realized this while working on Forest. When computing the dependencies of a 
reactive scope we can omit setState functions in the general case (exception 
described below). Currently that's implemented in PruneNonReactiveDependencies. 
However, this causes us to miss some optimizations — a value isn't reactive if 
its only dependency is a setState, and that may allow further downstreams values 
to become non-reactive. We lose out on that by only filtering out setStates in 
PruneNonReactiveDependencies — this logic really belongs in InferReactivePlaces. 

So this PR moves the check for setState types to that pass. The updated fixtures 
show that this already uncovers some wins. The _new_ fixtures covers the 
exception. It's possible for a value to be typed as being a setState function, 
but to still be reactive: if its a local that is conditionally assigned 
different setState function values. Currently this test happens to work because 
our phi type inference is incomplete (see #2296). I'm adding the test now though 
to prevent regressions when we fix phi type inference.
2023-12-11 11:34:24 -08:00
Joe Savona 7da906d648 Fix Array#at and similar cases to capture if receiver is mutable
The previous PR helped me realize we weren't handling Array#at correctly. If the 
receiver is a mutable value its effect should be Capture and the lvalue effect 
needs to be Store. This PR updates the definition for Array#at to make the 
receiver Capture, and then updates inference to automatically set the lvalue 
effect to Store if _any_ argument (or the receiver) was Capture.
2023-11-29 12:05:25 -08:00
Joe Savona 75c7fdcbd0 MutableIfOperandsAreMutable flag handles mutation via capturing
There was one missing piece to the optimization from the previous PR: Array#map 
can return an alias to the receiver in its output, which means that mutations of 
the result have to be treated as mutations of the receiver. This means we need 
to use a Capture effect on the receiver. If that doesn't get downgraded to a 
Read bc the value was immutable, we then also need to make the lvalue effect a 
Store (so that InferMutableRanges actually looks at it for aliasing).
2023-11-29 10:46:44 -08:00
Joe Savona f7d16db544 [RFC] Refine memoization for Array#map with non-mutating callbacks
Improves memoization for cases such as #2409: 

```javascript 

const x = []; 

useEffect(...); 

return <div>{x.map(item => <span>{item}</span>)}</div>; 

``` 

We previously thought that the `x.map(...)` call mutated `x` since its kind was 
Mutable. However, in this case we can determine that the map call cannot mutate 
`x` (or anything else): the lambda does not mutate any free variables and does 
not mutate its arguments. 

This PR adds a new flag to function signatures, used for method calls only, that 
checks for such cases. The idea is that if the receiver is the only thing that 
is mutable — including that there are no args which are function expressions 
which mutate their parameters — then we can infer the effect as a read. See 
tests which confirm that function expressions which capture or mutate their 
params bypass the optimization.
2023-11-29 10:46:43 -08:00
Joe Savona b952bc3d87 [be] Consistently use known returnValueKind from signatures
We were using `returnValueKind` from function signatures for CallExpression but 
not MethodCall; this PR changes to use this signature information for both 
instruction kinds.
2023-11-29 10:46:42 -08:00
Mofei Zhang 359b9b1589 [ez] Patch unsound array destructuring
--- 

Going to hold off on landing until after codefreeze, it's not urgent as we 
already fixed playground in #2404. All other internal pipelines do error 
handling through Entrypoint, which catches and creates UnexpectedErrors as 
needed.
2023-11-28 17:48:22 -05:00
Sathya Gunasekaran 369c315ac4 [hir] Update error message to say global
This is non ideal but at least it's a step in the right direction. 

Getting the correct error requires us to track every identifier and global, 
which seems a bit excessive for now. 

We can revisit and improve this error if this is starting to confuse folks.
2023-11-15 17:03:55 +00:00
Lauren Tan 480c11bdb1 [hoisting] Make hoisting related errors consolidatable
Noticed from our paste that we weren't correctly rolling up hoisting related 
errors due to specific information being in the error title, so this PR moves 
them into description instead.
2023-11-13 16:54:54 -05:00
Joe Savona 8d41529600 Support computed object keys 2023-11-13 09:55:52 -08:00
Sathya Gunasekaran 42497b60f5 [error] Prefix side-effecting function names with throw
I did a double take when I thought we didn't handle returning the 

error when reading the code and when I edited the code, typescript told 

me that there's no need to return as creating the error will throw. 

This PR makes it clear from the name of the function that we will throw.
2023-11-08 16:09:13 +00:00
Sathya Gunasekaran 6405c980eb Use starred-block for multi line comments 2023-11-08 08:27:41 +00:00
Joe Savona 72c27b6893 Feature flag for transitively freezing values
This PR adds a feature flag to model a potential new-in-practice rule in React: 
that freezing a function expression also freezes its closed-over values, 
transitively. For example, in the following code `data` is frozen when the 
lambda that captures it is is passed to useEffect: 

```javascript 

const data = []; 

// useEffect freezes its argument (the function expr), which transitively 
freezes its captured value data 

useEffect(() => { 

foo(data); 

}, [data]); 

data.push(true); // ERROR: mutating a frozen value 

mutate(data); // we conservatively assume this doesn't mutate but could be wrong 

``` 

Note that this rule has never been written down or enforced. It is theoretically 
equivalent to the rule (already implemented in Forget) that values captured by 
JSX are frozen: 

```javascript 

const style = {...}; 

<div style={style}>...</div> 

style.width = 10; // ERROR: mutating a frozen value 

mutate(style); // we conservatively assume this doesn't mutate but could be 
wrong 

``` 

However, JSX is typically constructed toward the very end of a render function. 
Thus in practice there isn't much subsequent code that could even modify such a 
captured value. But for the useEffect case (and other hooks that take closures 
as arguments), they tend to occur much earlier in a render function. There's 
more code that can run later and still modify the captured values, without 
causing issues in practice. The _practical_ rule today is that you can't modify 
values captured by frozen lambdas _after the component returns_: it's fine in 
practice to modify captured values between calling eg useEffect and returning 
from render. 

Thus this feature flag is fairly likely to break some percent of real product 
code. I'm adding this so that we can experiment and see how unsafe it actually 
is.
2023-11-06 08:33:32 -08:00
Joe Savona 0d3f3884b8 Fix for IIFE return values mutated later
Fixes for the previous PR. What was happening is that our inference was 
inferring the correct mutable ranges and reactive scopes, but the inlining 
process left the instructions from the IIFEs inside a separate block, with a 
'label' terminal preceding it. When we converted to ReactiveFunction this was 
preserved as a ReactiveLabelTerminal, which meant that the first instruction for 
the mutable range could be nested inside one LabelTerminal, while more would be 
in a subsequent LabelTerminal. But we close blocks based on the block scope! 
This meant that we'd have leftover instructions (in the second LabelTerminal) 
that got left out of the block. 

Furthermore, because inlining was happening after EnterSSA we weren't creating 
phis correctly. This PR fixes a bunch of these issues, and a subsequent PR 
handles the remaining cases: 

* We move DropManualMemo and InlineIIFEs before EnterSSA. This means we lose the 
ability to use type information, but we ensure that we create proper SSA ids and 
phis for any reassignments within the IIFE 

* We also update PruneUnusedLabels to not just remove the unused labels, but to 
actually remove LabelTerminals that don't need them.
2023-11-06 08:33:29 -08:00
Lauren Tan d49eb8b580 [ez] Run copyright script 2023-11-02 12:43:35 -04:00
Joe Savona 3e157bbc27 Propagate reactivity to other operands accounting for mutable ranges
Previously if any operand was reactive, we transferred that reactivity to other 
operands that had a mutable effect (capture, conditionally mutate, mutate, or 
store). But a value can be captured without ever being modified again. This PR 
updates the logic to only transfer reactivity among operands that are actually 
mutable at the given instruction, based on the mutable range. This is strictly 
more precise.
2023-11-01 17:13:05 -07:00
Joe Savona b34172c11e Reactivity inference is single-pass when no loops
We can complete in a single-pass if there are no loops, as a performance 
optimization.
2023-11-01 17:13:04 -07:00
Joe Savona 3ab47ac6bd Replace InferReactiveIdentifiers w new inference
This PR adds one remaining feature to InferReactivePlaces: tracking indirections 
like LoadLocal, PropertyLoad, and similar. Consider something like: 

``` 

// INPUT 

x.push(reactiveValue); 

// HIR 

t0 = LoadLocal 'x' 

t1 = PropertyLoad t0, 'push' 

t2 = LoadLocal 'reactiveValue' // reactive 

t3 = CallExpression mutate t0 . read t1 ( read t2 ) 

``` 

Because a reactive value (`t2`) flows into `t0`, we want to record t0 as 
reactive as well. But that's just the temporary for `LoadLocal 'x'` - what's 
really happening is that from this point, `x` is reactive. 
InferReactiveIdentifiers tracked this, and now that logic is ported into 
InferReactivePlaces as well. That lets us remove all the actual inference from 
InferReactiveIdentifiers.
2023-11-01 17:13:04 -07:00
Joe Savona 8610533ed1 Infer reactive control dependencies
Updates `InferReactivePlaces` to infer control dependencies. We build on the 
formal definition of control dependencies, which is that statement S2 is 
control-dependent on statement S1 if S1 is in the post-dominance-frontier of S2. 
Intuitively, if S1 decides whether S2 is reached or not, then S1 is a control 
dependency of S2. The post dominance frontier of a given statement S is the set 
of statements which may or may not reach S, and captures the intuitive notion. 

We take advantage of phis: phis are the point where a variable may have multiple 
values depending on the path we took. If a phi is not already known to be 
reactive from data dependencies we check for control dependencies. Specifically 
we look at each phi operand. We check if the block that the operand came from 
has any reactive control dependencies, and if so we mark the phi itself as 
reactive. 

The post-dominance-frontier (PDF) algorithm requires walking the post-dominator 
tree a bunch, so we cache the PDF of blocks so that we don't have to recalculate 
on subsequent iterations. 

In addition, `InferReactiveIdentifiers` now uses the _union_ of its own 
inference plus the new `InferReactivePlaces` output when deciding what 
identifiers are reactive. This ensures that control dependencies are recorded 
correctly, fixing the previous test cases. The next diff adds the remaining 
features to InferReactivePlaces so that it can fully replace 
InferReactiveIdentifiers.
2023-11-01 17:13:03 -07:00
Joe Savona d5c3fb87e6 HIR-based reactive identifier analysis
See context from #2187 for background about control dependencies. 

Our current `PruneNonReactiveIdentifiers` pass runs on ReactiveFunction, after 
scope construction, and removes scope dependencies that aren't reactive. It 
works by first building up a set of reactive identifiers in 
`InferReactiveIdentifiers`, then walking the ReactiveFunction and pruning any 
scope dependencies that aren't in that set. 

The challenge is control variables, as demonstrated by the test cases in #2184. 
`InferReactiveIdentifiers` runs against ReactiveFunction, and when we initially 
wrote it we didn't consider control variables. To handle control variables we 
really need to use precise control- & data-flow analysis, which is much easier 
with HIR. 

This PR adds the start of `InferReactivePlaces`, which annotates each `Place` 
with whether it is reactive or not. This allows the annotation to survive 
LeaveSSA, which swaps out the identifiers of places but leaves other properties 
as-is. This version does _not_ yet handle control variables, but it's already 
more precise than our existing inference. In our current inference, if `x` is 
ever assigned a reactive value, then all `x`s are marked reactive. In our new 
inference, each instance of `x` (each Place) gets a separate flag based on 
whether x can actually be reactive at that point in the program. 

There are two main next steps (in follow-up PRs): 

* Update the mechanism by which we prune non-reactive dependencies from scopes. 

* Handle control variables. I think we may be able to use dominator trees to 
figure out the set of basic blocks whose reachability is gated by the control 
variables. This should clearly work for if/else and switch, as for loops i'm not 
sure but intuitively it seems right.
2023-11-01 17:13:02 -07:00
Joe Savona 748859a00e Add reactive flag to Place
This is part of a stack for inferring variables which are reactive via *control 
dependencies* as opposed to a data dependency. In compiler engineering, a 
statement S2 is control-dependent on statement S1 if S1 is in the post-dominance 
frontier of S2. Stated more intuitively: if S1 decides whether or not S2 is 
reached, then S1 is a control dependency of S2. 

As a start, we add `Place.reactive: boolean` so that individual places can track 
whether they are reactive or not. This lets us do fine-grained reactivity 
inference on the control-flow graph, even taking into account different SSA 
instances of a variable, so that we can say that a particular SSA version of `x` 
is reactive, while other "versions" of x (due to reassignment) are not.
2023-11-01 17:13:01 -07:00
Joe Savona 529568c7db [be] Rename InlineUseMemo imports 2023-10-09 16:07:09 -07:00
Joe Savona 428c47581e Generalize InlineUseMemo to inline IIFEs
This PR completes the refactor. We now do the following sequence: 

* ValidateUseMemo. This is a new pass that extracts just the validation logic 
from the existing InlineUseMemo. This was always being run before, so this pass 
also always runs. 

* DropManualMemoization. As before, this converts useMemo calls into an IIFE 
(immediately invoked function expression). 

* InlineImmediatelyInvokedFunctionExpressions (prev InlineUseMemo). This pass 
now inlines _all_ IIFEs, including both useMemo calls that were dropped as well 
as IIFEs that the user wrote. 

The motivation for this change is that some codebases use IIFEs as a workaround 
for lack of if expressions, but we're unable to optimize within function 
expressions. This is the reason we originally added inlining for useMemo, but 
given that IIFEs are common it makes sense to generalize the inlining. 

## Test Plan 

* Manually checked changes in output 

* Synced internally and tested on profile page, no issues observed. Also 
spot-checked some of the changes in ouput and it looks as expected.
2023-10-09 15:56:10 -07:00
Joe Savona 444a1ad9c1 Reorder InlineUseMemo after type inference
The goal of this stack is to generalize `InlineUseMemo` into a pass that inlines 
all immediately invoked function expressions (IIFEs). Rather than specialize 
just useMemo calls, we'll rely on DropManualMemoization running first and 
turning useMemo calls into IIFEs. Then the generalized inlining pass can handle 
those IIFEs as well as others present in the source. 

For now, moving the order of the pass makes the output closer to what it will 
eventually be after this stack is complete.
2023-10-09 15:25:18 -07:00
Joe Savona 74c6c992ba [be] s/DropUseMemo/DropManualMemoization/
Renames this pass to clarify that it's dropping all manual memoization (within 
components), including both useMemo and useCallback.
2023-10-09 15:25:17 -07:00
Joe Savona 683a197a89 Manually revert #2127 (allow mutating context in callbacks)
#2127 introduced a special type for the result of `useContext()` that was sort 
of ref-like. The intent was to allow code like this: 

``` 

function Foo() { 

const cx = useContext(...); 

function onEvent() { 

cx.foo = true; 

}; 

return <Bar onEvent={onEvent} />; 

} 

``` 

However, that code actually is allowed by the compiler by default. It's only a 
bailout when `@validateFrozenLambdas` is enabled. The "fix" in #2127 therefore 
wasn't strictly necessary to unblock rollout, and it's also flawed in a few 
ways: 

* First, `useContext(FooContext)` should have equivalent behavior to a custom 
hooks which does the same thing, ie `function useFooContext() { return 
useContext(FooContext) }`. Specializing the type of useContext makes the 
behavior different. 

* Second, it meant that even readonly accesses of the context inside a callback 
marked the function as capturing, which in turn prevented those callbacks from 
being memoized. 

So i'm reverting this and we'll have to think a bit more about this case.
2023-10-09 15:21:12 -07:00
Joe Savona 11d03ffbe0 Promote enableFunctionCallSignatureOptimizations to stable (remove flag)
Per title, this feature flag is enabled everywhere and is clearly stable, let's 
promote to stable and remove the flag to simplify.
2023-10-05 09:20:09 -07:00
Joe Savona 0b0f8994ba Promote enableTreatHooksAsFunctions to stable (remove flag)
Per title, this feature flag is enabled everywhere and is clearly stable, let's 
promote to stable and remove the flag to simplify.
2023-10-05 09:20:05 -07:00
Joe Savona 34890556ec [be] Improve feature flags setup
Updates `Environment` to store all feature flags on a single `config` object. We 
now also define an object with all the default config values, and use this to 
populate defaults for any missing values in the user-provided config.
2023-10-03 13:58:05 -07:00
Sathya Gunasekaran 3aaf8be25c [hir] Use a stable identity for undefined value
InferReferenceEffects uses object identity to merge states, which breaks when we 
create a new object to model `undefined`. 

Two value objects representing `undefined` are not equal due to referential 
equality. 

Instead, let's use a singleton to represent `undefined` value.
2023-10-04 12:11:35 +05:30
Joe Savona a2dd376820 Drop useMemo/useCallback when called via React namespace
Teaches `DropUseMemo` (which also handles useCallback) to also transform the 
methodcall case.
2023-10-03 08:47:07 -07:00
Joe Savona 216696dd88 Allow context values to be mutated in callbacks
Adds a new type for representing context values, which is transitive. So 
`useContext(a).b.c` also gets inferred as a context type. This allows us to 
refine our inference, and allow passing callbacks that modify context where a 
"frozen" lambda is exepcted.
2023-10-03 08:47:04 -07:00