Commit Graph

1118 Commits

Author SHA1 Message Date
Mofei Zhang
ce015a164b [sprout] Codemod: add isComponent: false annotations to current fixtures
To keep the type signature simple, let's always have the same set of keys in 
`FIXTURE_ENTRYPOINT`
2023-08-16 15:32:13 -04:00
Lauren Tan
6b242c12a9 [ci] Don't run yarn build in ci
Can't recall why we added this, but it shouldn't be necessary and should shave 
off ~2 mins from ci
2023-08-16 12:16:51 -04:00
Lauren Tan
a59e15d40f [ci] Try to cache node_modules to speed up ci 2023-08-16 11:04:53 -04:00
Sathya Gunasekaran
b968d4da48 [hir] Validate globals are equal before propagating
Make sure the value of the globals in phi operands are the same before constant 
propagating them.
2023-08-16 15:39:01 +01:00
Sathya Gunasekaran
b686b5dd9d [hir] Refactor constant propagation of phis
This PR moves the phi evaluation to a separate function. 

Most importantly, it inverts the default case to _not_ constant propagate unless 
we have explicit validation of the phi operands.
2023-08-16 15:39:00 +01:00
Sathya Gunasekaran
c7210bac93 [test] Add failing test for constant propogation of globals as phi operands 2023-08-16 15:39:00 +01:00
Mofei Zhang
a418e35bf9 [sprout] QoL: use cli args, report pretty results
--- 

- Added sprout to Github Actions by updating `yarn test` command 

- Added cli args (`filter` and `sync`) 

- use chalk to make results nicer  

Tested locally: 

<img width="900" alt="Screenshot 2023-08-14 at 6 04 28 PM" 
src="https://github.com/facebook/react-forget/assets/34200447/b5481bbc-6a50-40c6-a85f-d2443130dd2a">
2023-08-14 19:26:05 -04:00
Mofei Zhang
d2983888f4 [sprout] Initial draft of sprout 🌱 test runner
--- 

## Sprout 🌱 Overview 

**(Overview copied from 
[sprout/README.md](0468ddf8bb/forget/packages/sprout/README.md))** 

React Forget test framework that executes compiler fixtures. 

Currently, Sprout runs each fixture with a known set of inputs and annotations. 
We hope to add fuzzing capabilities to Sprout, synthesizing sets of program 
inputs based on type and/or effect annotations. 

Sprout is currently WIP and only executes files listed in 
`src/SproutOnlyFilterTodoRemove.ts`. 

### Milestones: 

- [] Render fixtures with React runtime / `testing-library/react`. 

- [ ] Make Sprout CLI -runnable and report results in process exit code. 

After this point: 

- Sprout can be enabled by default and added to the Github Actions pipeline. 

- `SproutOnlyFilterTodoRemove` can be renamed to `SproutSkipFilter`. 

- All new tests should provide a `FIXTURE_ENTRYPOINT`. 

- [ ] Annotate `FIXTURE_ENTRYPOINT` (fn entrypoint and params) for rest of 
fixtures. 

- [ ] Edit rest of fixtures to use shared functions or define their own helpers. 

- [ ] *(optional)* Store Sprout output as snapshot files. i.e. each fixture 
could have a `fixture.js`, `fixture.snap.md`, and `fixture.sprout.md`. 

### Constraints 

Each fixture test executed by Sprout needs to export a `FIXTURE_ENTRYPOINT`, a 
single function and parameter set with the following type signature. 

```js 

type FIXTURE_ENTRYPOINT<T> = { 

// function to be invoked 

fn: ((...params: Array<T>) => any), 

// params to pass to fn 

params: Array<T>, 

// true if fn should be rendered as a React Component 

//  i.e. returns jsx or primitives 

isComponent?: boolean, 

} 

``` 

Example: 

```js 

// test.js 

function MyComponent(props) { 

return <div>{props.a + props.b}</div>; 

} 

export const FIXTURE_ENTRYPOINT = { 

fn: MyComponent, 

params: [{a: "hello ", b: "world"}], 

isComponent: true, 

}; 

``` 

--- 

## Implementation Details 

- jest-worker test orchestrator (similar to Snap 🫰). 

I chose to write a test runner instead of directly using Jest for flexibility 
and speed. 

- Sprout 🌱 currently runs much more code per fixture (2 babel transform 
pipelines + 2 `exec(...)` than snap, so all scaling concerns apply. 

- Sprout may need more customization in the future (e.g. fuzzing component 
inputs, caching artifacts) 

- We probably want to add snapshot files for Sprout, which is much easier with a 
custom runner. 

This is also one of the main reasons we wrote Snap. Jest consolidates all 
external snapshots (i.e. non-inline snapshots) from a test into [a single 
file](d0a006ffa9/forget/src/__tests__/__snapshots__/compiler-test.ts.snap). 
This was painful mainly for rebasing changes, but also for small papercuts (e.g. 
needing to run `yarn test -u` twice when deleting a fixture) 

- Currently does not save output to snapshot file, but we can easily add this 
later (i.e. each test would have a `.snap.md` and `.sprout.md` file) 

- Supports filter mode (same testfilter.txt file as snap) 

- Currently does not support watch mode. I expect that sprout's primary use will 
be catching bugs in the PR / Github Actions phase. Can be changed if we need to 
iterate on sprout output while developing. 

- All tests are run with `react-test-renderer` to access the React Runtime, 
which is needed for calls to `useMemoCache` (and other potential hooks). 

- react-test-renderer required a mocked DOM, so I ~~used js-dom~~ did a terrible 
js-dom hack to add document, window, and other browser globals to the 
jest-worker globals, then exec the test code in the jest worker global. 

I can clean this up later by bundling library code (e.g. react, 
react-test-renderer) to not use `require(...)`, then calling `exec` on all "test 
client code" in the js-dom mock global (instead of the real jest worker one) 

- Tests marked `isComponent` need to return valid jsx or primitives. Values 
returned by all other tests are converted to a string primitive via 
JSON.stringify. 

```sh 

# install new dependencies to node_modules 

$ yarn 

$ cd forget/packages/babel-plugin-react-forget 

$ yarn sprout:build && yarn sprout 

PASS  alias-nested-member-path 

PASS  assignment-variations-complex-lvalue 

PASS  assignment-variations 

PASS  chained-assignment-expressions 

PASS  computed-call-evaluation-order 

PASS  const-propagation-into-function-expression-primitive 

PASS  constant-propagation-for 

PASS  constant-propagation-while 

PASS  constant-propagation 

... 

  Done in 3.91s. 

```
2023-08-14 19:26:02 -04:00
Mofei Zhang
904ea6c9e4 [sprout] codemod for tests that can be run out of the box 2023-08-14 19:26:02 -04:00
Mofei Zhang
5669fc73c0 [snap] Refactor fixture utils to separate package
--- 

Move shared functions to `fixture-test-utils` package.
2023-08-14 19:26:01 -04:00
Mofei Zhang
02e8cf9790 [snap][easy] typecheck forget babel plugin options 2023-08-14 19:26:01 -04:00
Joe Savona
45d884bb6b Fix LeaveSSA missing param declarations
Nice find from @mofeiz, we weren't adding declarations for function params in 
LeaveSSA. This caused us to hit an invariant for update expressions that updated 
params which assumed that all named variables had a declaration. This is why 
it's helpful to add invariants, it helps you find places where you violate them 
:-)
2023-08-14 15:48:05 -07:00
Joe Savona
cb2ba7118f [rust][sema] Support hoisting semantics
Adds basic support for hoisting semantics: * Resolution of variable references 
is _always_ deferred in case the correct binding   hasn't been seet yet due to 
hoisting. * Var and function declarations bubble to the appropriate scope 

There are lots of subtleties that aren't implemented yet but these rules cover a 
lot.
2023-08-14 12:24:05 -07:00
Joe Savona
011570a078 [rust][sema] Bubble unresolved references (for hoisting)
This is a precursor to adding support for hoisting in semantic analysis. 
Previously when we encountered an unknown reference we immediately reported an 
error. But hoisted variables may be referenced before they're defined, so we 
don't know for sure when we see an unknown variable if its actually unbound or 
not. 

This PR adds the first part of hoistingn support: rather than immediately report 
an error when encountering an unbound variable we store it in a list of 
unresolved references on the current scope. As we close each scope we recheck 
and see if the variable can now be resolved. If yes we record that, otherwise we 
bubble up the unresolved reference to the parent scope (and try again there). 

The next PR(s) will handle hoisting of `var` and other syntax to the apropriate 
nearest scope boundary (function/module).
2023-08-14 10:08:02 -07:00
Joe Savona
57f648009c [rust] Cleanup visitors 2023-08-14 09:54:29 -07:00
Joe Savona
e248534184 [rust] SSA fixes after operand refactor
When updating the data model for operands from instruction indices to 
identifiers, I forgot to rewrite terminal operands.Doing so required a refactor 
to use the new BlockRewriter helper.
2023-08-14 09:54:26 -07:00
Joe Savona
a49335f72a [rust] Support destructuring
Adds a `Destructure` instruction closely following the design of the TS-based 
compiler. The main change is fairly small: the TS compiler doesn't support rest 
spreads that are not identifiers, such as the `...y[]` in `const [x, ...[y]] = 
z`. I added support for this in the Rust compiler.
2023-08-11 15:13:21 -04:00
Lauren Tan
298bf55f25 Address feedback 2023-08-14 12:05:52 -04:00
Lauren Tan
3ec95c531b [be] More babel plugin Program cleanup
Give a more descriptive name
2023-08-11 16:39:03 -04:00
Lauren Tan
25e3575c38 [be] Move more logic into Imports module 2023-08-11 16:39:01 -04:00
Lauren Tan
ac8d874711 [eslint-plugin] Silence babel warnings in test
be quiet Babel!
2023-08-11 16:38:59 -04:00
Lauren Tan
d2990f1592 [be] Extract import logic to own module
Extracting logic around inserting import statements into its own module, with 
more descriptive function names
2023-08-11 16:38:57 -04:00
Lauren Tan
ebd8ed5c71 [be] Extract gating logic to its own module
Just moving code around to another module.
2023-08-11 16:38:55 -04:00
Lauren Tan
dd8b743f6a [be] Refactor gating logic in babel plugin
`compileProgram` was getting complex, so this extracts some of the logic into 
smaller functions. Additionally, the `try` block now only wraps the `compileFn` 
generator from Pipeline, which means not accidentally catching other non-Forget 
errors
2023-08-11 16:38:52 -04:00
Lauren Tan
6c719f62a0 [ez] ComponentDeclaration transforms only into FunctionDeclaration
ComponentDeclarations can only be transformed into FunctionDeclarations, so the 
types were wrong
2023-08-11 13:31:06 -04:00
Lauren Tan
c3ec002d69 Invert shouldVisitNode logic 2023-08-10 17:24:22 -04:00
Lauren Tan
ec44c4789b Add ComponentDeclaration util 2023-08-10 17:24:21 -04:00
Sathya Gunasekaran
e70dfbb5a4 [babel] Lookup magic prop directly 2023-08-10 13:31:46 -04:00
Sathya Gunasekaran
c1c35407ba [test] Test with non component declaration 2023-08-10 12:02:08 -04:00
Joe Savona
c3ad8a86b1 [rust] Semantic analysis resolves break/continue 2023-08-10 10:59:51 -04:00
Joe Savona
ee13c30d10 [rust] Sketch of ReactiveIR (prev ReactiveFunction)
Initial data types for `forget_reactive_ir`, which is the Rust analogue of 
`ReactiveFunction` in the TS compiler. I'm renaming here for clarity, though 
naming suggestions are very welcome!
2023-08-10 10:59:51 -04:00
Joe Savona
7c38b15078 [rust] Align instruction data model closer to JS
Aligns the data model for `Instruction` closer to JS: * Adds an `lvalue: 
IdentifierOperand` property (Identifier + Effect) * Changes rvalues from being a 
reference to the definining instruction by   instruction offset (InstrIx) to be 
a variable reference (IdentifierOperand) 

There are pros and cons to the previous offset-based approach. It's definitely 
convenient to be able to jump directly to the instruction that defined an 
operand value. However: * Many passes need to track things like basic 
reassignments, which mean they need to build   up mappings of IdentifierId to 
some data. When all operands are IdentifierOperands, this   can be a single 
mapping. * It aligns closer to JS, which makes porting a bit easier. 

But most of all: porting the `ReactiveFunction` data type — which is in tree 
form - is non-trivial with the offset-based approach. As we map HIR to the tree 
form, we'd have to remap every operand offset. Using identifiers for operands 
simplifies this. 

We can always revisit this design choice later.
2023-08-10 10:59:50 -04:00
Joe Savona
cbb815e907 [rust] Port EliminateRedundantPhi fix 2023-08-10 10:59:50 -04:00
Joe Savona
74a062ba9d [rust] Autofix lints 2023-08-10 10:59:50 -04:00
Joe Savona
b8f1d37b6a [rust] Remove SWC
After the previous PR we no longer depend on SWC for the critical path of 
development/testing. Notably this unblocks adding support for the rest of the 
language: the new `forget_hermes_parser` crate automatically converts Hermes 
Parser's AST into our `forget_estree` format via codegen. Achieving full 
language support with SWC would have required manually defining the remaining 
conversions for the rest of the language. 

Long-term we'll need to revisit how to integrate into SWC, and more generally 
into setups build atop SWC such as Next.js's Turbopack-based build 
configuration. But i'll delete for now since we don't depend on it for 
iteration, it's slow to build, and requires us to opt-in to nightly Rust.
2023-08-10 10:59:49 -04:00
Joe Savona
7feaabfe68 [rust] Use hermes parser for Forget fixture tests
Replaces the use of SWC parser in the Forget fixture tests with Hermes Parser. 
This includes aligning on a single type to represent JS Values and numbers 
(combining semi-duplicated code from forget_hir and forget_estree) and adding 
support for lowering babel-style 
NumericLiteral/BooleanLiteral/StringLiteral/NullLiteral node types (since Hermes 
Parser produces a mix of estree/babel node types)
2023-08-10 10:59:49 -04:00
Joe Savona
6bca9fb7a3 [rust] Update HIR builder to use new semantic analysis
Updates HIR builder to rely on the new semantic analysis instead of assuming 
that the ast nodes will already have binding info attached. That was a stopgap 
until we had our own name resolution :-) 

Once this lands we can remove SWC and switch everything to forget_hermes_parser, 
and also remove the non-spec Identifier.binding field (which stored the 
temporary name resolution data).
2023-08-10 10:59:48 -04:00
Sathya Gunasekaran
5d8587b81c Add option to compile ReactScript 2023-08-09 14:49:29 -04:00
Sathya Gunasekaran
27f38ec321 Add hermes-parser 2023-08-09 14:47:56 -04:00
Sathya Gunasekaran
6075d7ea34 Upgrade babel 2023-08-09 14:47:56 -04:00
Joe Savona
43677da7e4 Add instructions for UpdateExpression variants
Adds new instructions to accurately model UpdateExpression semantics, since 
`x++` is un-intuitively not the same as `x = x + 1`. There are a few different 
ways to model the combination of prefix/postfix and increment/decrement: 

* One instruction for all combinations of prefix/postfix and 
increment/decrement, eg 'UpdateExpression' 

* Instructions for Increment/Decrement, each with a property to distinguish 
prefix/postfix 

* Instructions for Prefix/Postfix, each with aproperty to distinguish 
increment/decrement. 

I chose the latter, `PrefixUpdate` and `PostfixUpdate`, because it keeps the 
number of new instructions minimal while keeping separate instructions for the 
most important distinction: whether the result of the instruction is the value 
before applying the operation or after. I'm open to suggestions about this 
though. 

A few quick notes: 

* Constant propagation is supported but only for numbers (we don't support 
bigint yet anyway) 

* LeaveSSA needs to know about these instructions since their presence requires 
making the original variable declaration Let, not Const. 

* EnterSSA mapped lvalues before rvalues, which is out of order but didn't 
previously matter. I just had to flip the order and everything worked.
2023-08-07 13:50:32 -07:00
Mofei Zhang
e33c9c43cc [ssa] Patch: propagate every rewrite to function expressions
--- 

#1899 only propagated eliminated phi nodes to function expressions on the first 
iteration through blocks. However, this was buggy as later iterations could 
introduce rewrites that need to be propagated. 

[playground 
repro](https://0xeac7-forget.vercel.app/#eyJzb3VyY2UiOiJmdW5jdGlvbiBDb21wb25lbnQoKSB7XG4gIGNvbnN0IHggPSA0O1xuXG4gIGNvbnN0IGdldDQgPSAoKSA9PiB7XG4gICAgd2hpbGUgKGJhcigpKSB7XG4gICAgICBpZiAoYmF6KSB7XG4gICAgICAgIGJhcigpO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gKCkgPT4geDtcbiAgfTtcblxuICByZXR1cm4gZ2V0NDtcbn0ifQ==). 

I manually synced #1907 to check that this fix works for the VR Store codebase.
2023-08-07 17:41:35 -04:00
Mofei Zhang
ff2f2e0685 [patch] Gate EliminatePhi fix behind feature flag 2023-08-07 17:41:34 -04:00
Sathya Gunasekaran
adfae63f20 [hir] Remove unnecessary rewrite of instr.lvalue
The for loop over eachInstructionLValue already rewrites instr.lvalue: ```       
  for (const place of eachInstructionLValue(instr)) {           
rewritePlace(place, rewrites);         } ```
2023-08-07 13:41:59 -04:00
Joe Savona
461083d7bd [rust] Deprecate old manual ast visitor 2023-08-04 14:44:54 -07:00
Joe Savona
ab5213e8f4 [rust][sema] Comments 2023-08-04 12:16:08 -07:00
Joe Savona
3df04725bd [rust][sema] For statements and JSX
Adds semantic analysis support for normal `for` statements and for JSX. The main 
catch with JSX is that there are a bunch of identifiers that we have to ignore 
since they aren't variable references: jsx attribute names, namespace names, jsx 
member expression properties, and closing elements.
2023-08-04 12:02:21 -07:00
Joe Savona
4756c5ab26 [rust][sema] Shared logic for for-in/for-of
Shares the code for for..in and for..of, along with a few other internal 
refactorings.
2023-08-04 12:02:18 -07:00
Joe Savona
e6e2d9437e [rust] Improved name resolution
This is the start of an improved semantic analysis pass, reusing the 
ScopeManager added earlier in the stack but with new analysis built using the 
new visitor trait. The logic is a rough port of 


https://github.com/facebook/hermes/blob/main/tools/hermes-parser/js/hermes-eslint/src/scope-manager/referencer/Referencer.js 

Lots of bits are still missing, i'm starting with the parts that Forget needs.
2023-08-04 09:45:56 -07:00
Joe Savona
c6ae535e94 [rust] Codegen AST visitor
Updates `estree` codegen to emit a Visitor trait (temporarily named `Visitor2` 
since there is a hand-rolled one that some code is using). We use knowledge of 
the grammar to only visit fields whose type is a Node or Enum, or an "object" 
type that opts into being visitable. The latter is used for Function and Class. 

This will make it much easier to write the semantic analyzer.
2023-08-03 17:00:02 -07:00