Commit Graph
65 Commits
Author SHA1 Message Date
Mofei Zhang 8dfe06ddba [hir] Allow reorderable exprs in optional computed load 2023-03-31 10:58:28 -04:00
Mofei Zhang c77c1b1644 [hir] Disallow unconditional load from optional memberexpr 2023-03-30 19:23:23 -04:00
Joe Savona 445e550e00 Support await expressions
Adds support for `await` expressions. We have primarily seen await used inside 
callbacks, not directly within component render logic, but because we construct 
HIR for lambdas it is helpful to be able to model await rather than require 
everyone to rewrite to use the Promise API. Note a subtlety: awaiting a promise 
is a mutative operation, so we a) model it as a Mutate effect and b) avoid DCE 
of await expressions since they may cause side effects. See the test cases for 
examples.
2023-03-27 10:41:49 -07:00
Joe Savona 61e97dc278 Support RegExp literals
New InstructionValue variant since RegExp literals are valid expressions.
2023-03-27 10:34:10 -07:00
Joe Savona 179b24b56e JSXNamespacedName support
This is kind of a hack, but i think it's worth it given that JSXNamespacedName 
is relatively uncommon. Adding a new InstructionValue variant to represent a 
namespaced name is one option, but then that isn't a valid expression and can't 
appear as an operand anywhere else. Instead, we lower namespaced names as a 
primitive (string) as `${namespace}:${name}` — exploiting the fact the namespace 
and name can't have a colon, and non-namespaced tagnames also can't have colons. 

It's a bit of a hack but it's contained to the JSX processing code. If folks 
have strong opinions on this i'm happy to change but this felt reasonable as a 
quick and reliable way to unblock support. 

NOTE: there is a larger question of what to do about compiling `fbt` tags. 
Before we can do anything with them, though, we need to parse them.
2023-03-27 10:34:09 -07:00
Joe Savona 77bb9ff765 ReactiveFunction and codegen for optional calls
Implements HIR->ReactiveFunction conversion and Codegen for optional calls. We 
add a new OptionalCall variant of ReactiveValue, which is a SequenceExpression 
that describes the evaluation of the args and the call itself. This is then 
straightforward to codgen.
2023-03-24 14:22:14 -07:00
Joe Savona 14c03b897f Consolidate {Property,Computed}Call into MethodCall
Now that we model the method resolution via a PropertyLoad or ComputedLoad, we 
don't need to distinguish between PropertyCall and ComputedCall. These two call 
variants are now combined into a single MethodCall variant.
2023-03-22 14:36:48 -07:00
Joe Savona 5f1bebbeae ComputedCall modeled as ComputedLoad + Call for order-of-evaluation semantics
This is the version of @mofeiZ's change for PropertyLoad, but made to work on 
ComputedCall. We force the method to be evaluated in the same scope as the call 
in InferReactiveScopeVariables.
2023-03-22 14:36:48 -07:00
Mofei Zhang 01a6502baa [hir] represent PropertyCall as receiver + PropertyLoad
How Forget currently lowers PropertyCall: 

```js 

// source: [[ calleeExpr ]].propertyName( [[ argExpr0 ]]) 

$0 = [[ calleeExpr ]] 

$1 = [[ argExpr0 ]] 

$2 = PropertyCall callee=$0 property="propertyName" args=[$1] 

``` 

This PR changes the lowering: 

```js 

// source: [[ calleeExpr ]].propertyName( [[ argExpr0 ]]) 

$0 = [[ calleeExpr ]] 

$1 = PropertyLoad $0 "propertyName" 

$2 = [[ argExpr0 ]] 

$3 = PropertyCall callee=$0 fn=$1 args=[$2] 

``` 

From my understanding, `PropertyCall` needs the receiver to properly model JS 
semantics which is something like `resolvedFn.apply(resolvedCallee, arg0, arg1, 
...)`. This is additionally useful for: 

- Fine-grained mutability / alias analysis. The property call is technically a 
read of the resolved function, and a mutate of the callee. 

- Dependency tracking. While we could special case PropertyCall, this 
representation would correctly add both callee and callee.propertyName as 
dependencies for PropertyCall. 

e.g. 

```js 

let x = []; 

mutate(x); 

useFreeze(x); 

let y = {}; 

y.a = x.bar(); 

return y; 

```
2023-03-21 14:57:12 -04:00
Joe Savona ef34ca6cb0 Model other assignment variants as values
The previous PR only updated simple assignment expressions (where the lvalue is 
an identifier), this PR extends the same idea to all assignment variants. Note 
that there is one case that doesn't work yet, which is complex destructuring 
assignment as a value: 

```javascript 

let x = makeObject(); 

x.foo(([[x]] = makeObject())); 

``` 

What happens here is that we lower the destructuring to a series of steps: 

``` 

tmp1: Destructure Const [ tmp0 ]  = makeObject(); 

tmp2: Destructure Reassign [ x ] = tmp0; 

PropertyCall x, 'foo', [ tmp1 ] 

``` 

Thankfully we can detect this case: if we have a const/let declaration with an 
lvalue, that's invalid. See the new error test case which shows we correctly 
detect & reject this case for now.
2023-03-21 10:01:09 -07:00
Joe Savona bf1db812a8 Model assignment as value
This PR subtly changes how we represent assignment expressions in order to 
accurately model them _as expressions_. Specifically, the result of lowering an 
assignment is now the temporary created for the assignment's lvalue. This allows 
us to restore the assignment as a value (expression) during codegen. Note how 
this fixes a bug and cleans up some output.
2023-03-21 10:01:08 -07:00
Joe Savona 3e84c870f6 Helper for lowering args
Cleans up duplicated code for processing call/constructor arguments. As a side 
benefit, we now support spread elements for constructor arguments (and if we 
want to change how we represent that, we can do it in one place).
2023-03-20 12:55:14 -07:00
Joe Savona a7ac20973f Support JsxMemberExpression 2023-03-17 16:18:07 -07:00
Joe Savona f54f653277 DeclareLocal instruction
Adds a `DeclareLocal` instruction which represents declaring a named variable 
without initializing it. Currently declarations without an initializer (`let x`) 
are transformed into a declaration to undefined (`let x = undefined`) which 
changes the semantics due to hoisting and TDZ (temporary dead zone). The correct 
thing is to represent declaration without initialization.
2023-03-15 13:12:07 -07:00
Joe Savona f88713bba5 Support spread elements in CallExpression args 2023-03-15 17:13:50 -07:00
Lauren Tan faa8eef0a8 [be] Fix remaining lints and enable lint in CI 2023-03-10 16:21:01 -05:00
Joe Savona f985d6cdba PropertyDelete/ComputedDelete instructions 2023-03-08 12:04:27 -08:00
Joe Savona 1c023263a3 Use shorthand where possible for ObjectExpression properties
During codegen, emit object properties as shorthand where possible (`{x}` 
instead of `{x: x}`)
2023-03-06 16:35:52 -08:00
Joe Savona e381aa042f Support spread elements in ArrayExpression
Similar to the previous, but for array expression: `const x = [...y]`
2023-03-06 15:32:43 -08:00
Joe Savona 93cca54aba Support spread patterns in object literals
Support ObjectExpression with spread items, eg `const x = {...y}`.
2023-03-06 15:24:43 -08:00
Joe Savona 7893a6c403 Represent ObjectExpression properties as Array<ObjectProperty>
Refactors the representation of ObjectExpression properties from a Map to an 
`Array<ObjectProperty>` to prepare for the next diff which adds spread element 
support.
2023-03-06 15:19:34 -08:00
Joe Savona ca9d49090a New destructuring representation modeled on StoreLocal
Changes to explicitly model destructuring (array and object patterns), expanding 
support to include rest elements and preserving destructuring through the 
output. The new "Destructure" instruction is similar to "StoreLocal" but has a 
pattern instead of a place. For now each level of nested array/object patterns 
creates a separate destructure instruction, which ensures we have a temporary 
Place to talk about the intermediate array/object and its type/effects etc. 
Example: 

``` 

// INPUT 

const [x, {y}, ...z] = a; // yay rest elements work now! 

// HIR 

[1] <unknown> $2 = LoadLocal a$1 

[2] <unknown> $6 = Destructure Const [ <unknown> x$3, <unknown> $4, ...<unknown> 
z$5 ] = <unknown> $2 

[3] <unknown> $8 = Destructure Const { y: <unknown> y$8 } = <unknown> $4 

// OUTPUT 

const [x, t0, ...z] = a; 

const {y} = t0; 

``` 

Note that we can still collapse to a single destructure statement during 
codegen, independently of whether we have separate instructions internally. For 
now i'm going w the simple approach of emitting multiple statements in codegen 
(the code will very likely get further rewritten by downstream babel passes 
anyway). 

Also, I don't love the "if StoreLocal/Destructure else ..." pattern that the 
StoreLocal created and that this PR entrenches. As discussed w @gsathya offline, 
the long-term direction will be to add a separate visitor, roughly 
`eachLValue()` and `eachOperand()` so that we can treat all instructions the 
same. Existing Instruction.lvalue will go away and become a property of the 
other types of instructions.
2023-03-03 17:09:25 -08:00
Joe Savona 36d1fa2569 [storelocal] Instruction lvalue is just a Place 2023-03-02 14:19:18 -08:00
Joe Savona f4abf0a9ee [storelocal] lvalues are always const 2023-03-02 14:19:17 -08:00
Joe Savona 487786f7d4 StoreLocal instruction
Adds a new `StoreLocal <kind> <place> = <value>` instruction which stores 
<value> into <place>. With this change, Instruction.lvalue is _always_ a `const` 
temporary, and never a named identifier (there's a new validation pass to assert 
this). StoreLocal is the only way to declare or update a named identifier: the 
instructionKind property says whether it's a const/let declaration or a 
reassignment. Naturally a _lot_ of passes had to be updated to make this work, 
but the existing Effect.Store variant that @gsathya added made this overall 
straightforward. 

Note that as of this PR several passes still have code to handle the possibility 
of an instruction lvalue being something other than a temporary. When we clean 
that up in a follow-up, there will be a lot less of the duplication that appears 
here. For example, CodegenReactiveFunction has two places to handle variable 
declarations in this PR. However, one of them is to handle lvalues, which should 
now _always_ be temporaries and never emit a regular variable declaration. 
Similarly, several passes have to build up a table of identifier -> identifier 
(because of LoadLocal). Longer-term, we should update the Place abstraction so 
that it directly specifies the instruction which created that temporary, so we 
can look it up on demand instead of needing an extra mapping.
2023-03-02 14:19:17 -08:00
Lauren Tan 440fd1f24a Add support for DoWhile statements
Adds support for DoWhileStatements. It's pretty similar to how we handle While, 
except in the case where a test block is unreachable (for example, an early 
unconditional `break` within the loop body). In this scenario we eliminate the 
terminal altogether and replace it with a goto to the loop block.
2023-03-01 19:45:02 -05:00
Joe Savona 71db40c6ff LoadLocal instruction
Changes InstructionValue::Place to InstructionValue::LoadLocal for clarity, this 
is intended as the only instruction where a variable can appear as an operand. 
All other instructions operands will be temporaries.
2023-03-01 16:27:34 -08:00
Lauren Tan 7a6a0e5f72 [be] Fix various eslints 2023-02-28 19:19:38 -05:00
Mofei Zhang 9ca41f8a2e [rhir] small: ReactiveDependency uses Identifier instead of Place
--- 

We never use the `Place` of a ReactiveScopeDependency, except for when we want 
to access its identifier. Later PRs in this stack will convert 
`ReactiveScopeDependency` to property access trees (and traverse over the tree). 
This usually involves merging multiple Dependencies into trees (where each root 
is a unique identifier). We then traverse over each tree to extract its 
dependencies (e.g. unconditional leaves). 

``` 

{place: {loc: 1, identifier: 'props'}, path: ['a', 'b']} 

{place: {loc: 2, identifier: 'props'}, path: ['a']} 

// merges into a single tree root, which should represent a single identifier 

``` 

The `place` of each individual `ReactiveScopeDependency` will be lost during the 
tree traversal, and it doesn't really make sense to recreate them using the 
`Place` attached to the tree root.
2023-02-27 13:38:02 -05:00
Joe Savona 326e8c13f7 Scaffolding for LoadGlobal instruction
Adds a new `LoadGlobal` InstructionValue variant which will be used to represent 
identifiers that refer to globals. We don't construct this value type yet.
2023-02-16 15:17:30 -08:00
Joe Savona 849198da9d Pass cache size to useMemoCache() 2023-02-16 08:57:42 -08:00
Lauren Tan b5a0739e8c Scopes with reassignments should still emit memo block 2023-02-13 16:52:07 -05:00
Lauren Tan a76627c972 Use IdentifierIds to when comparing Identifier
With the upcoming changes to SSA renaming in #1194, we rewrite phi operand 
identifiers to have the same IdentifierId as the declaration the identifier 
originated from: so downstream checks need to compare ids instead of the 
identifier instance.
2023-02-13 16:52:05 -05:00
Lauren Tan 985a289a4c [BE] Various linter fixes 2023-02-13 16:52:04 -05:00
Lauren Tan d47f608c61 Record reassignments
Record a variable that is declared in some other scope and that is being 
reassigned in the current one as a reassignment
2023-02-08 10:26:33 -05:00
Lauren Tan d43a5014f4 Rename ReactiveScope.outputs to ReactiveScope.declarations 2023-02-08 10:26:27 -05:00
Lauren Tan 195f473cb4 s/useMemoCache/unstable_useMemoCache
uMC is still prefixed with unstable in React 
(https://github.com/facebook/react/blob/653dd2348ccfd7bfa4e11d814e247ed3ff7c5fa7/packages/react/src/React.js#L143)
2023-02-07 11:40:06 -05:00
Sathya Gunasekaran 500cfddc3a [λ] Remove unused params in FunctionExpression 2023-02-06 14:03:13 +00:00
Mofei Zhang e30a9e9258 [hir syntax] Handle TemplateLiteral syntax 2023-02-03 15:44:37 -05:00
mofeiZ ecb084c4b8 [ReactiveHIR] Infer reactive identifiers and promote temporaries
Messed up ghstack, this is a duplicate of #1093
2023-02-03 14:52:46 -05:00
Lauren Tan 8a0d3169fd Record todo bailouts in CodegenReactiveFunction
Went over this pass and converted any todos to bailouts, otherwise we continue 
to throw an invariant if there's an internal error
2023-02-01 14:49:47 -05:00
Joe Savona 2069269903 Support TypeCastExpression
Support TypeCastExpressions — `(x: TypeAnnotation)`. This is pretty 
straightforward, it's semantically identical to a raw identifier. 

One catch is that our prettier config is hard-coded to use the babel-ts parser, 
i wasn't sure how to make that dynamic based on the file extension so for now i 
just ignored .flow.js files in our pretter config.
2023-02-01 08:42:33 -08:00
Joe Savona 47074a2def Support JsxSpreadAttribute
Changes the representation of JsxElement props to be an array of attributes, 
each of which can be a named attribute or spread attribute.
2023-01-31 16:43:46 -08:00
Joe Savona 0c5176c618 [valueblocks] Cleanup
Removes dead code related the now-unused old representation for value blocks.
2023-01-31 13:39:41 -08:00
Joe Savona fa525e0b6f [valueblocks] For.init is a value block 2023-01-31 13:39:40 -08:00
Joe Savona 894eadfa67 [valueblocks] For.update is a proper value block 2023-01-31 13:39:39 -08:00
Joe Savona 6930b5f77e [valueblocks] For.test is a proper value block 2023-01-31 13:39:39 -08:00
Joe Savona 90e1265442 [valueblocks] While.test is a proper value block
Changes ReactiveWhileTerminal’s test to use the new value block representation. 
This means logical and condition expressions will work as while test values now.
2023-01-31 13:39:38 -08:00
Joe Savona acd227440e [valueblocks] Support conditional expressions (ternary)
Support conditional expressions from AST -> HIR -> ReactiveFunction -> AST. This 
also helps make the patterns for value block handling more clear, so i was able 
to extract some reusable logic in the HIR -> ReactiveFunction conversion phase.
2023-01-31 13:39:35 -08:00
Joe Savona d4acc7efa6 [valueblocks] Convert logical terminal to ReactiveValue
Implements the conversion from LogicalTerminal into a ReactiveLogicalValue (and 
ReactiveSequenveValue if necessary). The implementation is a bit rough, i clean 
it up in subsequent PRs which revealed parts of the logic that could be shared w 
ternaries.
2023-01-31 13:39:32 -08:00