Commit Graph
987 Commits
Author SHA1 Message Date
Joe Savona 2ac69bf7d9 [rust] Passes from HIRBuilder
This is a start to porting HIRBuilder, with a largely complete implementation of 
`build()`. Notably this includes all the passes which build() calls, and the 
helper functions those passes call in turn: 

```rust 

reverse_postorder_blocks(&mut hir); 

remove_unreachable_for_updates(&mut hir); 

remove_unreachable_fallthroughs(&mut hir); 

remove_unreachable_do_while_statements(&mut hir); 

mark_instruction_ids(&mut hir)?; 

mark_predecessors(&mut hir); 

``` 

This was pretty straightforward. I ran into one borrow checker issue with (iirc) 
`remove_unreachable_for_updates` where i needed to simultaneously hold a mutable 
reference into the HIR (to write the ForTerminal) and also get an immutable 
reference to check if the update block is reachable. The challenge is this: 

```rust 

for block in hir.blocks.values_mut() { 

^^^^^^^^^^^^^^^^^^^^^^^ hir borrowed mutably here 

if let TerminalValue::ForTerminal(terminal) = &mut block.terminal.value { 

if let Some(update) = terminal.update { 

if !hir.blocks.contains(&update) { 

^^^^^^^^^^ borrowed immutably here 

terminal.update = None; 

^^^^^^^^ mutable borrow still active here (and also bc of the loop) 

} 

} 

} 

} 

``` 

I quickly worked around this as we do in the other passes here by first building 
a set of the block ids contained in the function (so that the 
`hir.blocks.contains()` call becomes a call to the copied set of block ids). 

An alternative would be to add a helper function for mutable iteration which 
takes the desired value out of the data structure so that you can safely mutate 
it and reference the rest of the HIR. Usage might look like this: 

```rust 

hir.blocks.each_mut(|mut block, hir| { 

if let TerminalValue::ForTerminal(terminal) = &mut block.terminal.value { 

if let Some(update) = terminal.update { 

if !hir.blocks.contains(&update) { 

terminal.update = None; 

} 

} 

} 

}) 

``` 

The lambda would receive the current `block` as a mutable reference, and for the 
duration of the call `hir.blocks[block.id]` would be set to a sentinel value 
that would crash if accessed. Meanwhile, `hir` would be a readonly reference to 
the HIR, allowing the lambda to otherwise lookup information on the HIR but not 
mutate it. This seems...kinda fine? But also not immediately necessary as 
there's an easy and efficient-enough-workaround for the cases i've encountered 
so far.
2023-07-06 09:24:43 +09:00
Joe Savona 6c3792e8d5 [rust] Initial translation of HIR and related types
This is an initial translation of HIR (minus the ReactiveFunction bits). It's 
mostly a straightforward translation. A few differences: 

* Instead of Effect having an Unknown variant, we type `Place.effect: 
Option<Effect>`. Maybe i'll revert that but it seems right. 

* Terminal is divided into `Terminal { id: InstructionId, value: TerminalValue 
}` and `enum TerminalValue { ... }`, so that we can reference the terminal's id 
without caring about which kind of terminal it is. 

* Each instruction value variant gets a named struct, whereas in JS we had lots 
of anonymous InstructionValue variants. 

* Rust generators are still an unstable nightly feature and likely to change 
syntax enough that it seems safer not to rely on them. So 
`eachTerminalSuccessor()` returns a `Vec<BlockId>`. If the perf of this is bad 
enough we can switch to something like SmallVec (a popular crate) which stores a 
few elements inline on the stack to optimize for small vecs (which our case 
should be). 

Perhaps more importantly, what's _not different_: I kept the existing approach 
of BasicBlocks having a Vec of owned instruction instances, with 
InstructionValue variant operands as Places rather than indices into a shared 
instruction array. If this works out it will make porting straightforward. 

However there's one aspect of this that is incorrect: right now each `Place` 
owns its `Identifier`, whereas in JS the Identifier instances are shared mutable 
references. I'll definitely need to refactor the data structures to allow 
sharing Identifiers in some form, at which point we may want to change other 
things too.
2023-07-06 09:24:40 +09:00
Joe Savona 4c0122f4cf [rust] Small improvements plus snapshot testing for estree 2023-07-06 09:24:39 +09:00
Joe Savona 2b168ad673 [rust] SWC->ESTree conversion
Start of converting swc's representation into our estree format.
2023-07-06 09:24:38 +09:00
Joe Savona 4230eb63d8 [rust] ESTree representation with basic serialization support
Start of an `estree` crate for representing ESTree with serialization to/from 
ESTree JSON. To get the serialization to work quickly I took some shortcuts and 
uses a slightly less precise modeling. Longer-term we can write some custom 
serialization code and get more precise types (eg avoid the `ExpressionLike` 
enum in favor of proper Expression and ExpressionOrFoo types).
2023-07-06 09:24:37 +09:00
Joe Savona 292f07e19f [rust] SWC usage example
Basic example of parsing some code with SWC and then running name resolution.
2023-07-06 09:24:36 +09:00
Sathya Gunasekaran d10885ae0e [snap] Blow up if testfilter is not found in filter mode
No more failing silently when testfilter file is not found.
2023-07-05 18:53:18 +05:30
Sathya Gunasekaran 4a8b36b8b1 [hir] Check for globals while lowering UpdateExpression
lowerIdentifierForAssignment does extra checks such as checking if globals are 
lvalues. 

UpdateExpression lowers into an assignment and previously we missed out on such 
checks.
2023-07-05 20:21:09 +05:30
Sathya Gunasekaran 8eca031508 [test] Add failing test for mutating global 2023-07-05 20:21:05 +05:30
Lauren Tan 6e6ecfc4d9 [eslint-plugin] Remove babel/traverse and babel/parser
These are unused since we switched over to hermes-parser
2023-07-03 12:25:37 -04:00
Lauren Tan 2a882d798b [eslint-plugin][ez] Remove unused scripts 2023-07-03 12:25:35 -04:00
Lauren Tan c6b437b402 [eslint-plugin] Remove fbt dependencies
I incorrectly included these as deps, we were only using these to verify 
codegen. It seems fine to leave in but when I imported it internally vscode 
would error, so just remove it
2023-07-03 12:25:33 -04:00
Sathya Gunasekaran cff435ca3d [plugin] Remove duplicate export statement 2023-07-03 15:51:05 +05:30
Sathya Gunasekaran 64637ac84c [playground] Refactor pragma parsing into separate function 2023-06-23 14:56:00 +05:30
Sathya Gunasekaran 3883eb9507 [playground] Make all options configurable 2023-06-23 14:55:59 +05:30
Lauren Tan 0892e305bc [eslint-plugin] Clean up error message
Omits some unncessary details from the error message surfaced in eslint
2023-06-28 11:43:56 -04:00
Lauren Tan f850d3b265 Improve remaining InvalidReact error messages 2023-06-28 11:43:56 -04:00
Lauren Tan dda59d093a Add InvalidJS error severity 2023-06-28 11:43:55 -04:00
Lauren Tan 3fb8937391 Add first example of error suggestion 2023-06-27 14:01:40 -04:00
Lauren Tan 448ed2b67b Refactor CompilerErrorDetails
- Made most static methods on CompilerError take a single options object   as an 
argument. With the exception of invariant which takes a condition and an options 
object. - Added a new `suggestions` field on CompilerErrorDetail, which we'll   
use to provide eslint suggestions - Updated eslint-plugin-react-forget to handle 
suggestions
2023-06-27 14:01:39 -04:00
Lauren Tan c3d6789857 Reclassify existing errors
Most of these errors were incorrectly using InvalidInput as a catchall for 
rejecting code. I went through each one and manually updated them to be more 
accurate
2023-06-27 14:01:39 -04:00
Lauren Tan 6c4318e5c2 Add InvalidReact and InvalidConfig error severities 2023-06-27 14:01:38 -04:00
Sathya Gunasekaran e762d5730b [test] Update stale test expectations 2023-06-23 14:51:38 +05:30
Sathya Gunasekaran 0897cc8a0f Add failing test for DropMemoCalls failing to DCE dep array
Found this while running Forget on the React tests. 

This isn't a high priority because the ESLint plugin would've caught this. But 
it'd be nice if either our validation rules caught this or if our compiler did 
correctly eliminate the dep array.
2023-06-20 14:58:36 +01:00
Sathya Gunasekaran 2ca614cb4f [playground] Sync playground flags to Store experiment flags 2023-06-23 14:36:56 +05:30
Sathya Gunasekaran 7caa18c4b9 [playground] Parse pragma in playground 2023-06-23 14:36:55 +05:30
Lauren Tan dfb9e09984 [eslint-plugin][ez] Plugin options are const 2023-06-22 10:18:43 -04:00
Lauren Tan 9c033a481d [eslint-plugin] Only report on InvalidInput errors
Invariants aren't actionable by users, so we can omit reporting them in eslint 
as errors (while continuing to throw in the compiler itself)
2023-06-22 10:18:43 -04:00
Lauren Tan e7e67f335a Use CompilerError.invariant consistently
This PR updates all current usages of invariant to CompilerError.invariant along 
with any available locs
2023-06-22 10:18:42 -04:00
Lauren Tan e9faa5b90d SourceLocation can be null 2023-06-22 10:18:42 -04:00
Lauren Tan 3f2b346ba0 Make CompilerError.invariant assert the condition
This allows us to mimic the `invariant` api, which means you can just assert 
that something holds true after execution proceeds to the next line without 
throwing
2023-06-22 10:18:41 -04:00
Lauren Tan 6c425f6b36 Remove codeframe/nodepath options from CompilerError
These were effectively unused since we almost always passed in null when 
creating errors, so remove them and instead pass in a `loc` explicitly. The 
downside is that we no longer will see Babel codeframes in our test fixtures, 
which doesn't seem like a huge loss
2023-06-22 10:18:41 -04:00
Joe Savona ba75c96077 Fix mutable range validation in PrintHIR
Fixes the mutable range validation in PrintHIR: when we checked the identifier 
scope's range we failed to allow end=start=0 cases which are also valid. 

While I was here, I created a separate assertion pass to check all ranges, that 
way we aren't relying on the printer to validate them. The pass is on for 
playground and tests, but disabled by default so it can't break internal apps.
2023-06-21 16:08:44 -07:00
Joe Savona fa913b423e Distinguish internal invariant passes from user code validation
Distinguishes between two types of "validation" passes: 

- Passes which assert the validity of the HIR. These remain in HIR/ but are 
renamed "assertFoo". 

- Passes which validate that the user code is correct. These move to 
Validation/.
2023-06-20 15:16:57 -07:00
Joe Savona bbb5840e73 Handle phi nodes for ValidateFrozenLambdas
Extends ValidateFrozenLambdas to find cases where a mutable lambda flows into a 
phi node and the phi is later frozen.
2023-06-20 15:16:56 -07:00
Joe Savona 6efd1f99d7 Make function name a string not an identifier
Fixes #1751. Function ids can be plain strings, and we can refer to them as 
globals rather than via a local identifier. In addition to the bug from $1751 
this also cleans up an existing todo.
2023-06-20 15:16:55 -07:00
Joe Savona 4a36b83796 Ref validation allows passing refs as props
Updates ValidateNoRefAccessInRender to allow passing refs to JSX, but continue 
disallowing passing ref values (so `ref` is okay but not `ref.current`)
2023-06-20 10:11:56 -07:00
Joe Savona a9868cc2b1 Fix memoization of lambdas in @enableOptimizeFunctionExpr feature
This is a fix specifically for the `enableOptimizeFunctionExpression` feature 
(disabled by default). I tried running all of our fixtures with that flag on 
everywhere, and this was the only issue. It's actually extracted from another 
fixture which is more complicated, this is a distilled version. There were two 
bugs: 

* DCE was running after LeaveSSA when it needs to run before. Fixing the order 
means code inside the function expression stops getting removed. 

* In the feature flag, context variables share Identifier instances with the 
surrounding code, whereas they are distinct instances without the feature. This 
meant that mutable ranges from inside the function propagated outside the 
function, throwing off our inference. The fix was to reset the ranges of context 
variables after inferring the function expression's effects.
2023-06-16 14:42:19 -07:00
Joe Savona 04f4b50997 Integrate mutable context identifier inference, improve frozen function
inference 

Integrates the new inference to detect definitive mutations of context 
variables. This allows us to more precisely infer mutable function expressions 
and validate that they aren't passed where frozen values are expected.
2023-06-16 14:42:19 -07:00
Joe Savona a2af8776a4 Add InferMutableContextVariables (unused)
New pass to infer context variables which are definitively mutated, 
distinguishing from context variables which _may_  have been mutated. See the 
code comment on the new pass for more details.
2023-06-16 14:42:18 -07:00
Joe Savona 59a1f2a5b0 AnalyzeFunction: ref values take priority over other mutations
Small tweak necessary for the subsequent PRs, refs and ref values take 
precedence over other mutations when computing the effect type of context 
variables. We always want to record ref access within a function as capture 
(since we have later validation) rather than a mutation. For now this has no 
impact, either order records a Capture. But it allows later diffs to make 
non-ref cases have other effects.
2023-06-16 14:42:17 -07:00
Joe Savona 278c7f56ba ValidateFrozenLambdas: visit terminals
The original version of the code wasn't checking return values. I missed this 
since my examples were passing functions _into_ the return value, as opposed to 
return the functions directly. This revealed some existing test fixtures that 
were technically invalid, but easy to fix by changing the return value.
2023-06-16 14:42:16 -07:00
Joe Savona 1db2619807 InferReferenceEffects: distinguish conditional mutations in HIR
In InferReferenceEffects, locations that are ConditionallyMutate are either 
recorded as a Mutate or Read, which means we lose the distinction btw 
conditional/unconditional mutation in later passes. This PR changes to remember 
that these places were conditionally mutable, used in later analysis.
2023-06-16 14:42:16 -07:00
Joe Savona 19976d5f83 [ez] Real location for function context values 2023-06-16 14:42:15 -07:00
Lauren Tan f467774487 [babel-plugin][ez] Rename shouldCompile to shouldVisitNode 2023-06-20 12:30:07 -04:00
Lauren Tan 46fa83d854 [eslint-plugin] Disambiguate compiler errors without instanceof
Babel re-emits errors from plugins so the `instanceof` check was no longer 
working
2023-06-20 12:30:07 -04:00
Lauren Tan 2f0ab0ec9d [eslint-plugin] Temporarily disable validateFrozenLambdas 2023-06-20 12:30:06 -04:00
Lauren Tan 98633d10a6 Add a noEmit compiler option
Defaults to false, ie it runs the codegen pass. When enabled it will simply run 
all passes up to codegen and then skip over it. Naming of this option is copied 
from [TypeScript](https://www.typescriptlang.org/tsconfig#noEmit) which has the 
same named option that makes the compiler only perform typechecking. 

I'm adding this option primarily to get around some issues running the eslint 
plugin on Meta code. The plugin would error because Forget would report 
duplicate Babel AST nodes, which I presume would only occur during codegen. It 
should also make it a tiny bit faster to not run codegen, which is a nice plus.
2023-06-20 12:30:06 -04:00
Lauren Tan 79dbf6fc19 [eslint-plugin] Try using HermesParser 2023-06-20 12:30:05 -04:00
Lauren Tan 059365d726 [eslint-plugin] Temporarily only lint on 'use forget'
Temporarily enabling 'use forget' only mode for the linter to address #1751 so 
we can land the stack internally
2023-06-20 12:30:05 -04:00