Commit Graph
1708 Commits
Author SHA1 Message Date
Sathya Gunasekaran 88186db690 [hir] Make hoisting a TODO bailout 2023-09-13 17:58:58 +01:00
Joe Savona d2f46b1437 Delete now-unused mermaid visualization 2023-09-11 16:58:22 -07:00
Joe Savona ade3235ca3 ForInStatement: specialize inference/types
Replaces the use of `NextIterableOf` in for-in with a new `NextPropertyOf` 
instruction. The key distinction is `for-of` invokes an arbitrary iterator, 
which means a) each iteration may mutate the collection being iterated and b) 
the returned value may be mutable. However, `for-in` invokes a language-level 
mechanism to iterate: simply iterating alone _cannot_ modify the collection, and 
the returned value is known to be a primitive.
2023-09-11 16:54:05 -07:00
Joe Savona 51497c8c59 Basic ForInStatement support
First pass of ForInStatement support. This mostly just copies our handling of 
ForOfStatement, but the next PR updates to use a different instruction instead 
of `NextIterableOf`.
2023-09-11 15:56:49 -07:00
Joe Savona 911c988453 Support ObjectPattern within RestElement
Supports ObjectPattern within RestElement, the `...{y}` in `const [x, ...{y}] = 
z`
2023-09-11 13:03:02 -07:00
Joe Savona 1ec445d8f9 Support reordering call expressions
Supports call expressions if the callee and args are themselves reorderable. As 
part of this I realized that we currently allow identifier references to be 
reordered. To be safe, this PR updates the logic to continue consider 
identifiers as reorderable, but considers an arrow function to be not 
reorderable if it contains a local variable reference. We can likely relax that 
rule, but this quickly unblocks the next experiment.
2023-09-11 12:49:47 -07:00
Joe Savona b1f7ebfd47 Reodering of unary and arrow function expressions
Supports reordering of unary and arrow function expressions: 

* Supports a trivially safe subset of unary operators, rejects things like 
`void` just because we don't need it yet. 

* Supports arrow function expressions that are either an empty block statement 
or a single expression which is itself reorderable.
2023-09-11 12:49:47 -07:00
Joe Savona de2b684bc5 TryStatement: handle edge case control flows
Adds handling for some cases where the handler is unreachable (or is provably 
unreachable after analysis & optimization), where the try/catch can be flattened 
away: 

* The try block is empty. Nothing can throw, so the handler is unreachable. 

* The try block will always return. It can't return anything interesting (ie the 
result of a function call or variable load) since those could throw, but a try 
block that always return a primitive means the handler is unreachable. 

* The same, except where we only determine that the try block always returns via 
constant propagation.
2023-09-08 13:54:45 -07:00
Joe Savona c8cebdd946 TryStatement: handle mutation of try values via catch param 2023-09-08 11:03:55 -07:00
Joe Savona edd9c52142 Sprout tests to double-check our destructuring
I wanted to double-check the semantics of when default values are used, so i 
wrote out some fixtures with sprout enabled.
2023-09-07 17:17:03 -07:00
Joe Savona e8d130bd1d Support type casts and conditionals as default values 2023-09-07 17:07:34 -07:00
Joe Savona 0052f2e591 TryStatement: enable sprout on new tests
Enables sprout for all the new try/catch fixtures in this stack. I added new 
helpers and tried to make sure we're testing the most interesting codepath of 
each fixture. This is where property testing would help, since we could test 
multiple paths with a single block of code, but for now this seems like a good 
balance of coverage.
2023-09-07 16:50:24 -07:00
Joe Savona ee77d91ca2 TryStatement: handle catch clause params
It's possible that the value thrown during a `try` block actually is a reference 
to some value defined outside the scope of the try block. If the catch clause 
param is also mutated, that means the mutable range of the variable would have 
to include the entire try/catch. 

We handle this by emitting a DeclareLocal temporary for the catch param prior to 
the try/catch. If it is modified during the catch block, that will extend its 
mutable range to cover the full try/catch. If any values are mutated inside the 
try, their range will also (naturally) extend around the full try/catch block. 
These ranges will overlap and be merged, ensuring that we capture the 
possibility that the value is mutated via the catch param. See unit test.
2023-09-07 16:32:55 -07:00
Joe Savona e3622ee413 TryStatement: disallow throw inside try/catch
Modeling `throw` inside of a try/catch is awkward because it's basically a 
variable reassignment and a goto together. Thankfully that is an antipattern — 
using exceptions instead of control-flow — so it seems pretty reasonable to just 
put a todo here and leave it.
2023-09-07 16:32:54 -07:00
Joe Savona ff0b05848b TryStatement: optimization pass, handle early returns
Adds an optimization pass to prune unnecessary maybe-throw terminals, when the 
block can be proven not to throw. For now we're _very_ conservative about what 
instructions we consider not to throw. There isn't too much of an advantage in 
pruning further, either. 

This PR also updates BuildReactiveFunction to handle the possibility of early 
returns within try or catch blocks, making sure we don't hit the invariant of 
emitting the same block twice.
2023-09-07 16:32:53 -07:00
Joe Savona 936b96b24f TryStatement: partial lowering
Implements the core lowering logic for try/catch: 

* Inside of the `try` block, we use the new HIRBuilder mode to wrap every 
instruction in a separate basic block with a maybe-throw terminal 

* We emit a 'try' terminal for the try/catch itself 

For basic examples this already works correctly. But we don't handle catch 
clause params yet.
2023-09-07 16:32:53 -07:00
Joe Savona 7f5bac171e TryStatement: try terminal scaffolding
This PR adds the other piece, a 'try' terminal which represents try/catch and 
the possibility of fallthrough to the code afterwards. For now `finally` is 
unsupported. We don't yet produce these terminals, see later in the stack.
2023-09-07 14:22:33 -07:00
Joe Savona 0977115440 TryStatement: maybe-throw terminal for per-instruction throw points
Adds a "maybe-throw" terminal which represents the possibility that the block 
may or may not throw, and can either continue forward or exit to an exception 
handler (`catch`). Also updates HIRBuilder to track the current mode, and when 
inside a try block to wrap every instruction inside a basic block that ends in a 
maybe-throw. 

So far this code isn't used yet, so doesn't affect output.
2023-09-07 14:22:28 -07:00
Sathya Gunasekaran 20203cd889 [hir] Remove unnecessary check for place kind 2023-09-07 13:57:02 +01:00
Sathya Gunasekaran 603d2179a8 [hir] Pretty print object property keys 2023-09-07 13:56:59 +01:00
Sathya Gunasekaran f2027b9a4d [hir] Add support for string literal property keys
Mostly reuses existing analysis of an identifier property key. 

Adds a new type field to ObjectProperty to propogate the type of the key. This 
is used in codegen to correctly emit a string literal or an identifier.
2023-09-07 13:56:56 +01:00
Sathya Gunasekaran 59504e1cb4 [hir] Traverse function to capture deps, not just body node
Technically there is a body node created for implicit return expression in a 
arrow function, so the existing logic should've worked fine. But there seems to 
be a Babel bug, so let's work around it by traversing the function. 

Added a test case that captures a dep as a param -- this is currently 
unsupported  and also something that would've been ignored before this PR. Added 
a failing test to make sure we think about this case when we add support for 
default params.
2023-09-06 14:58:04 +01:00
Sathya Gunasekaran a93aa8f322 [hir] Encapsulate pipeline from rest of Forget
Future passes will lead to inconsistent state between passes and will require
passes to run in a specific order, so let's make sure no one will misconfigure
the passes.
2023-09-06 14:12:11 +01:00
Lauren Tan 31f0f426e1 [hoisting] Add failing tests
The current plan is to add support for hoisting function declarations, let/const 
declarations, but not var yet
2023-09-05 17:04:03 -04:00
Joe Savona ef51c92c8a first rendering test
Updates the rendering code and gets a first unit test working with a fully
static component (written by hand rather than compiled).
2023-09-05 14:03:22 -07:00
Lauren Tan 9d6170ab98 Update snap fixture
Missed this in my last PR
2023-09-05 15:07:37 -04:00
Lauren Tan 6a71868d4c [sprout] Convert for-of-mutate to sprout 2023-09-05 12:35:18 -04:00
Lauren Tan 375e2d8f33 [babel] Remove babel/plugin-syntax-jsx
Internal version of babel/core doesn't support the `inherits` property, so let's 
try removing it. The tests still seem to pass, so this might be vestigial from 
the first iteration of the compiler from last year
2023-09-05 12:08:01 -04:00
Lauren Tan fdd6a1b8b1 Add rollup to eslint-plugin-react-forget 2023-09-05 12:08:00 -04:00
Sathya Gunasekaran 2d3507b0e1 Add signal codegen for ReactiveScopes
Instead of emitting a memo block, emit a function expression and pass it as an
argument to derived (which will then create a computed).

The naming of 'derived' needs to be tweaked.
2023-09-05 11:13:31 +01:00
Sathya Gunasekaran 4571979e46 Add pass to lower useState 2023-09-05 11:13:30 +01:00
Sathya Gunasekaran 403881c548 [forest] Turn off jsx memoization 2023-09-01 11:24:19 +01:00
Sathya Gunasekaran 9b037118c5 [test] Add basic test 2023-09-01 11:24:19 +01:00
Sathya Gunasekaran da61d47e87 Add flag 2023-08-31 11:15:41 +01:00
Sathya Gunasekaran c456662318 [tsconfig] Specify module resolution to be nodenext 2023-08-31 11:15:37 +01:00
Joe Savona 41c23e87c5 Disable compilation of "Components" inside class methods
Minimal repro extracted from our internal codebase. Our inference mode sees that 
this arrow function is component-like and attempts to compile it, which then 
fails because the function accesses `this`  which we bailout on.
2023-08-31 23:16:27 +01:00
Mofei Zhang e36c8cb395 [patch] tsconfig: unbreak vercel build on main 2023-08-30 10:36:46 -07:00
Mofei Zhang 7930a213d2 [sprout][QoL] remove isComponent annotation
--- 

quality of life improvement as this seemed to be confusing (oops, and thanks for 
the feedback!) 

We don't *really* need static annotations for whether a function returns jsx 
(e.g. should be rendered as a React element) or not (e.g. should be wrapped in a 
wrapper component. This PR adds check for returned jsx objects at runtime 

--- 

Tested by running diffing the output of `yarn sprout --verbose` between this PR 
and base.
2023-08-29 17:00:55 -07:00
Mofei Zhang c7b5f2fde0 [yarn] Update typescript node16 -> node18 2023-08-29 16:51:32 -07:00
Joe Savona 8790324562 Workaround serious Babel bug
I ran into the same issue that @poteto and @gsathya (and probably @mofeiZ) have 
run into: "Duplicate declaration of '$'" caused by Babel visiting a function 
twice despite our calling `skip()`. This PR keeps a set of nodes that we have 
already visited to avoid visiting them again, as a workaround for skip not 
working. 

# Test Plan 

Synced to www and confirmed that the previous bug no longer reproduces, and the 
compiled output looks sane.
2023-08-30 13:21:51 +01:00
Joe Savona 4f55a66d5c Compile args to forwardRef/memo
Completes a todo (ie fixes a silly mistake) from a PR earlier in the stack, so 
we now correctly recognize and compile arguments to `React.forwardRef()` and 
`React.memo()`.
2023-08-29 22:09:42 +01:00
Joe Savona ddc9f8030e Enable compilation of FunctionExpressions
Thanks to the previous diff we can trivially support components or hooks defined 
as a FunctionExpression.
2023-08-29 22:09:41 +01:00
Joe Savona cee08ba750 [rfc] Dont convert ArrowFunction to FunctionDecl
This PR changes the way we compile ArrowFunctionExpression to allow compiling 
more cases, such as within `React.forwardRef()`. We no longer convert arrow 
functions to function declarations. Instead, CodeGenerator emits a generic 
`CodegenFunction` type, and `Program.ts` is responsible for converting that to 
the appropriate type. The rule is basically: 

* Retain the original node type by default. Function declaration in, function 
declaration out. Arrow function in, arrow function out. 

* When gating is enabled, we emit a ConditionalExpression instead of creating a 
temporary variable. If the original (and hence compiled) functions are function 
declarations, we force them into FunctionExpressions only here, since we need an 
expression for each branch of the conditional. Then the rules are: 

* If this is a `export function Foo` ie a named export, replace it with a 
variable declaration with the conditional expression as the initializer, and the 
function name as the variable name. 

* Else, just replace the original function node with the conditional. This works 
for all other cases. 

I'm open to feedback but this seems like a pretty robust approach and will allow 
us to support a lot of real-world cases that we didn't yet, so i think we need 
_something_ in this direction.
2023-08-29 22:09:41 +01:00
Joe Savona 7d11445d8e Revamp compilation modes
We currently have multiple flags for targeting which functions to compile, but 
they are actually mutually exclusive. This PR consolidates to a single 
`compilationMode: 'annotation' | 'infer' | 'all'` flag: 

* Annotation compiles only functions that explicitly opt-in with "use forget" 

* Infer compiles explicitly opted-in functions (via "use forget") as well as any 
known/inferred components or hooks: 

* Component declarations 

* Component or hook-like functions (same rules as the ESLint plugin but with an 
extra check for whether it uses JSX or calls a hook) 

* All compiles all top-level functions. We should get rid of this in a follow-up 
and make tests use infer mode by default, and add explicit opt-ins where 
necessary. 

In all modes, "use no forget" always takes precedence and can be used to 
opt-out. The default mode is now "infer".
2023-08-29 22:09:40 +01:00
Joe Savona 3653ae2de3 Option to infer React functions to compile
Adds a new option to infer which functions to compile, based on React's ESLint 
rule. The main difference is that in addition to checking the function name we 
also check that it creates JSX or calls a hook. This should cover a significant 
majority of components and reduce the chance of accidentally targeting 
non-components, but it will leave some false negatives. 

Note that some cases that the ESLint plugin infers as React functions don't work 
yet: we don't compile FunctionExpressions, only ArrowFunctionExpressions, and 
the way we handle ArrowFunctionExpression doesn't work with things like 
forwardRef or variable declarations. We'll need more updates to fully handle all 
these cases, which I'll do later in the stack.
2023-08-29 22:09:40 +01:00
Mofei Zhang 6dbc48075b [tests] bug repros 2023-08-26 14:00:43 -07:00
Mofei Zhang bcbcd37eb0 [sprout] Allow forwardRef and memo components 2023-08-25 16:54:21 -07:00
Sathya Gunasekaran 56b0f971d0 [sprout] Port alias-capture-in-method-receiver-and-mutate test 2023-08-23 17:44:26 +01:00
Sathya Gunasekaran aceacce5e2 [sprout] Add a mutate utility function 2023-08-23 17:37:54 +01:00
Lauren Tan ce82a56fa6 [ci] Fix output of parser benchmark
yarn adds some other output, so just run the script directly
2023-08-24 10:14:28 -04:00