Commit Graph
1708 Commits
Author SHA1 Message Date
Joe Savona ca1e92ad91 Compile partial reassignments into a single derived scope 2024-02-23 14:41:25 -08:00
Joe Savona d6406d8360 Validate hook calls in object methods
Adds some test cases for hook calls in object methods. Initially we didn't catch 
these because InferTypes doesn't actually visit ObjectMethod bodies. Once we fix 
that we correctly reject these examples.
2024-02-16 11:27:52 -08:00
Joe Savona 9c419dfd80 Disallow calling hooks in functions
> Don’t call Hooks inside loops, conditions, or nested functions 

Per https://react.dev/warnings/invalid-hook-call-warning#breaking-rules-of-hooks 
it is invalid to call hooks inside function expressions. We now validate this by 
default, i'll verify internally before landing. 

Note the validation is somewhat more conservative and we only disallow known 
hook calls here, this seems like a reasonable tradeoff but i'm open to 
suggestions. We could reuse the same known/potential hook mechanism here but it 
would take some more refactoring.
2024-02-16 11:00:56 -08:00
Joe Savona a4234bcfbf infer mode only compiles component/hook decls
Forks the logic from Program.ts, letting us customize what
gets compiled.
2024-02-17 17:37:00 -08:00
Joe Savona ca3d16c5ef Enable hook syntax support
Updates the compiler to understand Flow hook syntax. Like component syntax, in 
infer mode hooks are compiled by default unless opted out. 

Looking ahead, i can imagine splitting up our compilation modes as follows: 

* Annotations: opt-in explicitly 

* Declarations: annotations + component/hook declarations 

* Infer: annotations, component/hook declarations, + component/hook-like 
functions 

This also suggest an alternative annotation strategy: "use react" (or "use 
component" / "use hook") as a general way to tell the compiler that a function 
is intended for React. Then opting out of memoization could do "use 
react(nomemo)".
2024-02-17 17:31:47 -08:00
Joe Savona 1e3f9130f3 Rewrite reactive scopes to computed with lambda
Updates LowerReactiveScopes to rewrite to a ReactiveFunctionValue
(ReactiveFunction-based) instead of a FunctionExpression (HIR-based). This lets
us include terminals and even nested reactive scopes in the result.
2024-02-16 14:59:01 -08:00
Joe Savona 191064b55c Add ReactiveFunctionValue variant
Per the previous PR, we don't have a way to rewrite an arbitrary subset of a
ReactiveFunction into a function expression, since FunctionExpression's contents
is still in HIR.

While long-term our plan is to move to HIR everywhere, this PR adds a stopgap of
adding a ReactiveFunctionValue variant of ReactiveValue. As a reminder,
ReactiveValue is a union of (HIR) InstructionValue | SequenceExpression |
LogicalExpression | ConditionalExpression.

For now i did a first stab at the visitors and transforms with the idea that:

* By default, visitors/transforms _don't_ look into these function expressions,
since we didn't previously traverse into (HIR-based) FunctionExpression either

* But there is a visitor/transform method that you can override if you need to.
2024-02-16 14:59:00 -08:00
Joe Savona 03874a29b3 Repro for needing function body rewriting in ReactiveFunction phase
Adds an example demonstrating why we need the ability to rewrite parts of a
ReactiveFunction into a function expression. Here, the reactive scope needs to
contain an `if` terminal, but we can't put a ReactiveIfTerminal inside a
function expression, since that expects HIR.

There are two main paths forward:

* Use HIR everywhere. I wrote this up and we're all agreed, it's just a bunch of
work.

* Add an alternative FunctionExpression variant to ReactiveFunction

For now i'm going to take the second route.
2024-02-16 14:58:59 -08:00
Sathya Gunasekaran 770ca4ab3e [Babel] Add support for "use memo"
We want to start moving away from "Forget", so this PR adds support "use memo" 
and "use no memo" 

I've left "use forget" and "use no forget" directives unchanged for now, as we 
need to migrate existing users first and then come back and delete support for 
these directives.
2024-02-19 17:48:49 +00:00
Jan Kassens 530f2c293d Add test fixture showing invalid location
Add test fixture showing invalid location 

The error should be on the node mutating the output, not in the read location.
2024-02-15 19:00:21 -05:00
Jan Kassens ac89712750 Add code frame to snap errors
Add code frame to snap errors 

This should make it easier (possible) to see if errors point at the right lines. 

No idea why I had to add 1 to the column, you'd think it's all babel-standard 
(whatever it is) and there wouldn't be off by one errors, but I'm not quite in 
the mood to debug babel issues more then necessary right now...
2024-02-15 18:42:21 -05:00
Lauren Tan d3bca420c0 Opt out react-forget-runtime from being Forgotten
This caused a build error when Forget was used in an Expo app as the 
react-forget-runtime package was itself being compiled with Forget. This broke 
Metro as metro serializes modules to iifes, but the import syntax that was 
injected by the useMemoCachePolyfill flag was left behind 

In practice I don't think the runtime package needs to ever be compiled by 
Forget, so this PR opts out the whole file. This would also prevent builds from 
breaking if someone decided to use the "all" compilation mode. 

Test plan: Ran the expo app and verified that it now builds with no errors
2024-02-15 17:44:44 -05:00
Lauren Tan 5142affc26 Allow module level "use no forget"
Currently we only allow adding the directive to function bodies, but there may 
be cases where we want to always opt out an entire module from being compiled by 
Forget
2024-02-15 17:44:44 -05:00
Joe Savona 21c5631570 [be] Prune implicit breaks when flattening unused labels
A labeled block will generally end with an implicit break out of the label. 
However, if there are no _explicit_ breaks to the label, we'll end up with a 
ReactiveFunction along the lines of: 

``` 

bb1: { 

...instructions with no explicit `break bb1`... 

(implicit) break; 

} 

``` 

The `PruneUnusedLabels` pass removes such unused labels, inlining the content of 
label terminal into the surrounding block. However, we weren't pruning the 
`break`! This wasn't a problem in practice since codegen, and future passes, 
would just ignore this. But it's more correct to go and find these unnecessary 
implicit breaks and prune them, which this PR does. 

Again, this shouldn't have any impact other than producing cleaner 
ReactiveFunction data during debugging.
2024-02-14 15:36:40 -08:00
Joe Savona ad5a272393 Skip functions using same rules as regular pipeline 2024-02-14 15:23:37 -08:00
Joe Savona 09d122a20e [be] Remove @enableMergeConsecutiveScopes flag, feature is stable
Continuing on my quest to clean up our feature flags, the logic for merging 
consecutive feature flags is stable. Let's remove 
`@enableMergeConsecutiveScopes` since this is enabled everywhere.
2024-02-14 15:13:00 -08:00
Jordan Brown a927040b7a Add an optional validation to bail out on capitalized function calls
Some components stop being components over time and are used as regular 
functions instead, but they may have lingering hook calls. Those hook calls make 
it so the capitalized function calling them do not error (they appear to be a 
function to existing eslint rules), but they are nonetheless unsafe to memoize. 
This diff adds a conservative option to bail out on all capitalized function 
calls. 

There are a handful of known-non-component capitalized functions, like 
`Boolean`, `String`, and `Number`. This diff also adds the ability to supply 
capitalized function names that should not be considered in this analysis. 

I added three tests: 

1. Ensure an error occurs in the obvious case 

2. Ensure an error occurs when the value is aliased simply 

3. Ensure the allowlist works 

This is my first commit so please go hard on me. I was unsure about where this 
code should live, so please nitpick.
2024-02-14 13:23:32 -05:00
Mike Vitousek 8a3def923b Fixed sprout test failure 2024-02-09 16:48:13 -08:00
Mike Vitousek 11a5254b9c add output 2024-02-09 15:24:51 -08:00
Mike Vitousek 966932c525 fix again :( 2024-02-09 15:17:55 -08:00
Mike Vitousek c10d711309 Further test fix 2024-02-09 15:09:26 -08:00
Mike Vitousek 8c7685ed40 Fix breakages and add fixture entrypoint 2024-02-09 15:00:05 -08:00
Mike Vitousek 48024b76bb Fix lints 2024-02-09 14:50:06 -08:00
Mike Vitousek 19476aa5f6 Option to bail on Flow react-rule suppressions 2024-02-09 14:21:07 -08:00
Joe Savona 24ed13e1f0 Update error message and comments 2024-02-13 16:45:18 -08:00
Joe Savona fea7b5ac0d Move useMemoCache outside of hook guards
The hook guards are incompatible with using a forget-runtime. Specifically, 
forget-runtime needs to make a call to `useState()` or some other hook to attach 
data to the fiber, but all the builtin hooks are overridden to disallow calling 
them outside of explicit boundaries. We'd either have to wrap the useMemoCache 
call in a push/pop to allow it to call other hooks, or as in this PR, just move 
it outside the enforcement.
2024-02-13 16:45:18 -08:00
Joe Savona 6924786973 [be] Change validate functions to not return unnecessary Result
These validations needs to be able to transitively check for violations within 
function expressions, without immediately erroring. So the inner "-Impl" helpers 
return a Result. But the outer, exported validate functions don't need to return 
a Result, especially since TS has no Rust-style enforcement that return values 
are actually used. Unwrapping within the validation means the caller can't 
forget to do so and inadvertently silence the errors.
2024-02-13 16:45:17 -08:00
Joe Savona 19b4abed69 [be] Remove unused validateRefAccessDuringRenderFunctionExpressions flag
I had split this up from the main validation since function validation was less 
precise; now that previous PRs fix the false positives we can remove this extra 
flag.
2024-02-13 16:45:17 -08:00
Joe Savona bde30f0284 [be] Remove ValidateFrozenLambdas
This pass doesn't really make sense in light of 
`@enableTransitivelyFreezeFunctionExpressions`. The original idea of 
ValidateFrozenLambdas was that trying to pass a "mutable" lambda to a frozen 
value was invalid. But since then we've realized that the better heuristic is 
that freezing a lambda is transitive.
2024-02-13 16:45:16 -08:00
Joe Savona 5f1b8fd57f Improve validateNoRefAccessInRender
Rewrites the validation to not rely on the mutable range of functions to 
determine whether they are called or not, since the range can be extended for 
other reasons (they happen to reference a mutable value that is mutated later, 
even though the function isn't called during render). 

Instead we use the same approach as validateNoSetStateInRender, explicitly 
tracking references to function expressions that access refs, and checking if 
those function expressions appear to be called. This can have false negatives, 
as with the setState validation, but catches lots of obviously incorrect code 
without false positives.
2024-02-13 16:45:15 -08:00
Joe Savona 6dae958eab [be] Extract computeUnconditionalBlocks() helper 2024-02-13 16:45:15 -08:00
Joe Savona f7f05501e0 Support destructuring assignment of context variables
Fixes T178003134. Previously we did not check whether values reassigned during a 
destructuring assignment were context variables. This would either miscompile, 
or as of my fix earlier in #2579, would fail validation. Specifically, this 
happened on AssignmentEpression with an object/array pattern lvalue, where the 
pattern contained an identifier that is a context variable. 

This is now fixed: we track whether the outermost assignment is a normal 
assignment or destructuring, and force destructuring to a temporary whenever the 
identifier is a context variable. We apply the same logic to variable 
declarations that are destructuring to a context variable.
2024-02-13 16:45:14 -08:00
Mofei Zhang a107ba81ed [BE] Remove navigator; edit sprout fixtures
--- 

I recall adding the navigator override because some React library file had done 
an unconditional access, but this doesn't seem to be the case anymore. 
Regardless, newer versions of nodejs comes with a global `navigator` [see 
thread](https://github.com/nodejs/node/issues/39540) that error on writes
2024-02-12 19:12:01 -05:00
Mofei Zhang 5dd55e881b [lower][patch] Recognize UpdateExpression as lval assignment
--- 

Oops, I broke this in #2552. We never handled UpdateExpressions to context 
variables previously, so this PR also adds a todo bailout
2024-02-09 16:18:37 -05:00
Joe Savona d4cdaf2523 Fix assignment expression with context variables
Fixes the one case discovered in the previous PR; for AssignmentExpression we 
correctly lowered the store instruction to a local/context, but then always used 
a `LoadLocal` to read the result back. 

The load instruction appears like it might be dangling - i think what was 
happening is that DCE cleaned up the unused LoadLocal whereas it leaves the 
LoadContext alone. But this works for now, we can always clean up the extra 
instruction later since this case isn't too common.
2024-02-09 14:25:33 -08:00
Joe Savona 6da1912eed Validate that all variable references are consistently local/context
Validates that all references to a variable (pre-SSA) are consistently "local" 
references or "context" references. Ie, if a variable is declared as 
DeclareContext, any accesses must be eg LoadContext or StoreContext, not 
LoadLocal/StoreLocal. This will help with the issue from #2577 (assuming that we 
know a variable _is_ a context variable) but also provides a more precise 
bailout for an existing case with destructuring assignment to a context 
variable.
2024-02-09 14:09:55 -08:00
Sathya Gunasekaran da8ca6e954 [types] Check propType when checking if two types are equal 2024-02-08 16:19:55 +00:00
Sathya Gunasekaran 3c431f9afe [types] Check the shapeId as part of the object type equals check 2024-02-08 16:19:55 +00:00
Joe Savona f0cffef0a2 Fix hook pattern matching for custom hooks 2024-02-07 17:34:10 -08:00
Joe Savona af6e837b60 Fix destructuring with mixed local/scope declarations
Fixes T176436488. The logic for rewriting Destructure instructions was correct, 
but the visitor implementation was accidentally dropping subsequent Destructure 
instructions within a block after encountering one that needed a rewrite. 
Switching to use the transform infra (added after this pass was written) fixes 
it.
2024-02-05 22:29:53 -08:00
Joe Savona 0277bc4d54 Separate snap flags for watch/update/filter
This is not that big a deal but a constant papercut, i often want to jump 
directly to watch mode with a filter applied. I know @poteto likes to (or at 
least used to) run watch with update enabled. Now instead of passing a mode, you 
can pass `--watch`, `--filter`, and `--update` independently.
2024-02-05 15:55:08 -08:00
Mofei Zhang 707d9643dd [logger] Log todo when encountering "use no forget"
--- 

This change simply logs on every function we encounter with a `use no forget` 
directive. A few nuances -- `compilationMode: "infer"` only compiles functions 
we infer to be 'react functions'. 

```js 

// `add` would not be compiled, as it has no jsx, no hook calls, 

// and is not named as a component or hook 

function add(a, b) { 

return a + b; 

} 

``` 

With this PR, we would report todos for functions that Forget wouldn't 
ordinarily try to compile. 

```js 

// Todo: Skipped due to "use no forget" directive. 

function add(a, b) { 

"use no forget"; 

return a + b; 

} 

``` 

This seems fine to me as (1) it's a bit nonsensical to have a `use no forget` 
direction on a non-react function, and (2) we're goalling on getting `use no 
forget`s down to 0.
2024-02-05 16:05:09 -05:00
Joe Savona 195b5e5a24 Fix sprout 2024-02-05 21:51:18 -08:00
Joe Savona b79a5289fc Resolve type aliases
The goal of this PR is to move towards a uniform representation for all type
declarations, whether they are named type aliases, function declarations, or
inline annotations. We now assign every non-primitive type declaration (named or
anonymous) a unique DeclarationId. In the next PR, we'll also re-map inline
annotations back to this declaration id when encountering them.

This PR is extremely gross and my intent is to refactor a bunch of things in the
HIR to allow this to be less gross. Challenges:

* Babel name resolution requires using scopes but i really want to just work
with plain nodes, since NodePath and TypeScript do _not_ get along. So here, i
find all identifiers and store a mapping of identifier -> scope, so that i can
later look them up if necessary.

* HIR doesn't have a notion of a declaration id, and in general we don't want
to extend HIR. So i end up with a whole bunch of side table information and
indirection. For example, a function doesn't know it's own declaration id.
So we have to look it up. Function params don't track their Forest type, so
we have to look them up on the function declaration. Etc.
2024-02-05 21:51:13 -08:00
Joe Savona 292a247cc4 Add type to DeclareLocal 2024-02-05 21:51:11 -08:00
Joe Savona 76aaf32c55 HIR StoreLocal.type uses babel type 2024-02-05 21:51:09 -08:00
Joe Savona 9ec9331def Emit function param and return type annotations 2024-02-05 21:51:09 -08:00
Sathya Gunasekaran 29cb62ad6c Add type information for jsx/runtime 2024-02-02 10:56:44 -05: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
Lauren Tan a0aa66ca8f Add test for function property mutation
There were no previous test paths that tested it, so I'm adding the example from 
https://github.com/facebookexternal/forget-feedback/issues/32 as a fixture
2024-02-01 11:02:30 -05:00