Commit Graph
1708 Commits
Author SHA1 Message Date
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 4a826985db More tests for AssignmentPattern 2023-03-27 10:34:16 -07:00
Joe Savona 6d434cc777 Generalize helper for reorderable expressions
Adds a new helper method that we can use when processing expressions whose 
evaluation ordering may not be preserved. This was previously the case only for 
switch test case values, but we can use this for AssignmentPattern 
(destructuring default values) as well.
2023-03-27 10:34:11 -07:00
Joe Savona 80bdab7447 Support AssignmentPattern (default values in destructuring)
"Supports" default values in destructuring (AssignmentPattern) by lowering to a 
ternary, even in the output. Examples: 

```javascript 

// Input: 

const [x = 'default'] = y; 

// Output: 

const [t0] = y; 

const x = t0 === undefined ? 'default' : t0; 

``` 

```javascript 

// Input 2 

const [{x} = makeObject()] = y; 

// Output 2 

const [t0] = y; 

const {x} = t0 === undefined ? makeObject() : t0; 

``` 

Note that this is how Babel lowers AssignmentPattern, so it isn't too bad. This 
should help avoid the need to update product code, even if the output isn't 
perfectly ideal.
2023-03-27 10:34:11 -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
mofeiZ 027f773179 [rhir] Patch for reactive computed loads
We need to check reactivity of both the operand and its resolved source (if 
operand is produced by a LoadLocal / PropertyLoad / ComputedLoad). 

Both the operand and its source can have reactivity. 

e.g. 

```js 

const o = makeObject(); // source has no reactivity 

const x = o[props.x];   // x is reactive 

```
2023-03-27 13:27:58 -04:00
Sathya Gunasekaran bb2325bdce [typer] Track return type in FunctionType
Rather than having a special FunctionCall type that deduces the return type, 
change the FunctionType to include the return type. 

This return type is inferred as part of unification.
2023-03-27 15:58:08 +01:00
Sathya Gunasekaran a0fa5ede54 [typer] Type Array.at returnType as PolyType 2023-03-27 15:58:07 +01:00
Mofei Zhang 67b2a9f314 [rhir] Represent OptionalMemberExpression as a conditional dependency
--- 

Every `OptionalMemberExpression` rvalue has the form 
`<requiredPath>?.<optionalPath>`. 

``` 

// required = [a], optional: [b, c] 

props.a?.b.c; 

props.a?.b?.c; 

``` 

When calculating reactive dependencies, recall that it is always correct to add 
a subpath of a dependency (e.g. we can always take `props.a` instead of 
`props.a.b` as a dependency). See comments in `DeriveMinimalDependencies` for a 
longer explanation. 

There are two ways we can deal with `OptionalMemberExpression`: 

- We can always truncate a OptionalMemberExpression dependency to its 
`requiredPath`, taking only the required path as a dependency. 

- this is the simpler approach, but it potentially loses granularity. 

e.g. 

``` 

// here, since props.a is already unconditionally accessed, 

// we can safely add props.a.b as a dependency and preserve both 

// nullthrows and the correct dependency set. 

scope @0 { 

let x = []; 

x.push(props.a?.b); 

x.push(props.a.b); 

} 

``` 

(See added test case `reduce-reactive-cond-memberexpr-join` + its comment block 
for a more detailed explanation` 

- (the approach taken by this PR) 

We can add the `requiredPath` as a potentially unconditional access (dependent 
on other control flow) and `requiredPath + optionalPath` as a conditional 
dependency.
2023-03-27 10:41:05 -04:00
Mofei Zhang 4aef9dac49 [be] Make ReactiveScopeDependency.path nonnullable
--- 

Previously, both `path=null` and `path=[]` could represent a dependency with no 
property path (i.e. the result of a LoadLocal with no PropertyLoad). 

Make path non-nullable so we don't have to add null checks everywhere.
2023-03-24 17:53:06 -04:00
Mofei Zhang b07bbc36ae [buildhir] Patch: lower nested OptionalMemberExpr 2023-03-24 17:53:04 -04:00
Mofei Zhang 5e95967c7c [be][dependencies] Remove control flow info from PropertyLoad sidemap
--- 

We don't need to store whether a `PropertyLoad` happens within a conditional 
(within its reactive scope). In fact, the PropertyLoad producing a rval often is 
in a different ReactiveScope from where the rval is used. 

We only need to add `#inConditionalWithinScope` when we actually visit a 
reactive dependency.
2023-03-24 17:53:03 -04:00
Joe Savona 9ff82c3c10 Support for (Optional)MemberExpression callee in OptionalCall
Earlier PRs bailed out when the callee of an OptionalCallExpression was a 
MemberExpression or OptionalMemberExpression (ie for optional method calls). 
This PRs expands support for optional method calls, including when the receiver, 
method, or both are optional. Even better, we don't need to add any additional 
terminals or instruction variants for this case - the one new OptionalCall 
terminal from earlier in the stack works for all these cases.
2023-03-24 14:22:16 -07:00
Joe Savona 326ee664dc Test cases for optional call
Tests, focusing on two key behaviors: 

* Dependencies of the args are treated as conditional, since the call may not 
happen 

* Args cannot be memoized independently, even when that would be valid for a 
non-optional call.
2023-03-24 14:22:15 -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 feb8e924e4 BuildHIR for OptionalCallExpression
Implements lowering for a subset of optional calls - specifically, we don't 
(yet) support when the callee is a member expression or an optional member 
expression. So `foo?.()` works but we bailout on `object?.foo()` and 
`object.foo?.()`. 

For `<calleee>?.(<args>)` we lower as roughly: 

``` 

bb0: 

t0 = <callee> 

OptionalCall test=bb1 fallthrough= 

bb1 (value): 

Branch t0 consequent=bb2 alternate=bb3 

bb2 (value): 

...lower <args> here... 

t1 = Call t0, args 

StoreLocal res, t1 

Goto bb4 

bb3 (value): 

t2 = undefined 

StoreLocal res, t2 

Goto bb4 

bb4: 

// result in `res` here 

```
2023-03-24 14:22:13 -07:00
Joe Savona 40bd9b060c Scaffolding for OptionalCall
Adds a new `optional-call` terminal and sets up the appropriate handling in the 
visitors, with lowering/reactivefunction/codegen as todos for now and 
implemented in follow-ups.
2023-03-24 14:22:13 -07:00
mofeiZ 568048af0f [tests] failing tests for dependencies and codegen (#1414) 2023-03-24 15:23:42 -04:00
Joe Savona 0ee5e482a2 Optional computed member expressions are not supported (add validation)
We don't propagate the `optional` flag through to codegen, so let's error on 
this for now
2023-03-23 09:01:21 -07:00
Mofei Zhang ef83c02d3c [hir][typer] infer polymorphic types from PropertyLoad and PropertyCall
--- 

Expand Hindley Milner type inference to infer dependent types. 

Say `t` is a typevar and `t'` is some type (a built-in type, phi node, or 
another typevar). 

Our type equations are as follows (please edit/correct notation 😅) 

- type substitution: `t = t'`, 

- ~~dependent~~ polymorphic property load: `t = t'.prop` 

- polymorphic function call `t = fnCall{returnType}` 

- ~~dependent property call: `t = t'.prop` (only if t'.prop is a function 
type)~~ 

- ~~dependent return type: `t = t'.[[returntype]]`~~
2023-03-23 15:08:19 -04:00
Mofei Zhang 447f401f32 [be][tests] Remove all hir-tests (cleanup for ObjectShape stack)
--- 

+10 −1,698 lines [[insert impacc macro]] 

The ObjectShape stacks (#1350, #1358) used these tests to record changes in 
inferred types (and associated ObjectShapes), reference effects, and mutable 
ranges. 

Now that those PRs have landed, we can delete these tests. They are somewhat 
fragile (changing anytime HIR / printHIR is changed) and easily cause 
rebase/merge conflicts.
2023-03-23 15:08:18 -04:00
Mofei Zhang c3589a9565 [hir] infer reference effects for property call
--- 

This PR does not add inference for normal `CallExpression`s, since built-in 
functions for `Array` and `Object` are usually only valid if called with a 
correctly-typed `this`. If we want codegen to preserve source code semantics, 
Forget should only add inferred types it is confident about. 

This PR also adds `returnEffect` to FunctionSignature. `returnEffect = Store` if 
this function is known to always return a captured value from `receiver` or 
`args`.
2023-03-23 15:08:18 -04:00
Mofei Zhang 76e6eff110 [hir][typer] infer polymorphic types from PropertyLoad and PropertyCall
--- 

Expand Hindley Milner type inference to infer dependent types. 

Say `t` is a typevar and `t'` is some type (a built-in type, phi node, or 
another typevar). 

Our type equations are as follows (please edit/correct notation 😅) 

- type substitution: `t = t'`, 

- ~~dependent~~ polymorphic property load: `t = t'.prop` 

- polymorphic function call `t = fnCall{returnType}` 

- ~~dependent property call: `t = t'.prop` (only if t'.prop is a function 
type)~~ 

- ~~dependent return type: `t = t'.[[returntype]]`~~
2023-03-23 15:08:17 -04:00
Joe Savona 9507493ee9 Allow accessing properties of globals in switch test values
We limit the types of expressions allowed as switch case test values because we 
our HIR doesn't yet preserve order-of-evaluation for switch test values (we 
model them as being evaluated prior to entering the switch, as opposed to 
lazily, when the case is reached). One common pattern internally is test case 
values that are properties of a global, eg you have some bag of enum values and 
are comparing against that: 

```javascript 

// at module scope, or imported from another module: 

const OPTIONS = {FOO: 'foo'}; 

// in a component 

switch (value) { 

case OPTIONS.FOO: { ... } 

} 

``` 

This PR allows this specific case, ie member expressions where the innermost 
object is a global identifier.
2023-03-22 14:36:50 -07:00
Joe Savona 7d43730319 [be] Remove stale global handling code
I forgot to remove this when i made globals explicit.
2023-03-22 14:36:49 -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 8ef22a2a90 [hir] todo tests for lambda capture
--- 

(I'm not sure if these are already known issues. I found them while playing 
around with lambda captures. They are also reproducible on main / stable) 

I have some limited understanding of lambda captures after reading Sathya's 
posts -- please correct if/where this is incorrect 

``` 

function Component() { 

// instr1 

// instr2 

const func3 = function(...) { 

// func3instr1 

} 

} 

``` 

We currently determine effects of captured references in `AnalyzeFunctions`, 
before InferReferenceEffects. 

- i.e. for some function 

1. dependencies of all functions (func3.deps) 

2. prefix traversal of all instructions (e.g. instr1, instr2, func3.deps, 
func3instr1, ...) 

- is this just an implementation decision? i.e. what is stopping us from postfix 
traversal in InferReferenceEffects (e.g. instr1, instr2, func3instr1, 
func3.deps) 

As such, for each captured reference, `AnalyzeFunctions` needs to assign a 
reference effect. We currently check `MutableRange`, which seems to miss a few 
cases 

- We do not model assignments to primitives correctly, since primitives do not 
have a mutable range. 

- We're not able to model captured (but not mutated) values correctly. 

Would it be possible to consolidate `AnalyzeFunctions` into 
InferReferenceEffects, using some post-order traversal (iterating over a 
function's instructions to collect its dependencies + associated capture 
effects)? I definitely don't understand lambdas completely, so please tell me 
what I'm missing
2023-03-22 15:58:07 -04:00
Mofei Zhang 501fbd8ed8 [hir] infer reference effects for property call
--- 

This PR does not add inference for normal `CallExpression`s, since built-in 
functions for `Array` and `Object` are usually only valid if called with a 
correctly-typed `this`. If we want codegen to preserve source code semantics, 
Forget should only add inferred types it is confident about. 

This PR also adds `returnEffect` to FunctionSignature. `returnEffect = Store` if 
this function is known to always return a captured value from `receiver` or 
`args`.
2023-03-22 15:58:06 -04:00
Mofei Zhang 241101c08a [builtins] Fix effects: capture -> read, store -> mutate
--- 

I didn't properly understand Capture and Store effects previously, just 
correcting those mistakes! 

These functions are all synchronously mutative, so they should use Read / 
Mutate, not Capture + Store
2023-03-22 15:50:40 -04:00
Mofei Zhang a7191d4e3b [hir][typer] infer polymorphic types from PropertyLoad and PropertyCall
--- 

Expand Hindley Milner type inference to infer dependent types. 

Say `t` is a typevar and `t'` is some type (a built-in type, phi node, or 
another typevar). 

Our type equations are as follows (please edit/correct notation 😅) 

- type substitution: `t = t'`, 

- ~~dependent~~ polymorphic property load: `t = t'.prop` 

- polymorphic function call `t = fnCall{returnType}` 

- ~~dependent property call: `t = t'.prop` (only if t'.prop is a function 
type)~~ 

- ~~dependent return type: `t = t'.[[returntype]]`~~
2023-03-22 15:50:40 -04: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
Mofei Zhang 6ce4cd0898 [rhir] Revert logic that dedupes inlined temporaries
Reverts #1199, which was added before we properly supported destructuring 
assignment. 

Next PR (changes to PropertyCall in #1384) will lower two references to the same 
named identifier (the property call receiver)
2023-03-21 14:57:12 -04:00
Mofei Zhang ab1d11a446 [hir] remove constant propagation for ComputedCall 2023-03-21 14:57:11 -04:00
Mofei Zhang ec345ca538 [wip][typer] Infer Array instance properties
--- 

TODO: add inference for TypedFunction to `InferReferenceEffects`
2023-03-21 14:57:11 -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 f4c4dcb1b3 Instruction-specific propagation to account for assignment-as-value
Updates ConstantPropagation so that each instruction is responsible for whether 
to replace its `.value` with the resolved constant value (if found). 
Specifically, for `StoreLocal` we don't want to replace the value — we want to 
keep the assignment — but we do want to propagate the _result_ of the assignment 
downstream. This more accurately models the semantics of assignment expressions, 
and helps with subsequent PRs.
2023-03-21 10:01:07 -07:00
Sathya Gunasekaran 63450d5768 [test] Update test snapshot 2023-03-21 14:15:01 +00:00
Sathya Gunasekaran 9b5ff25b3b [Babel] Desugar ArrowFunctionExpression
Rewrite ArrowFunctionExpression to FunctionDeclaration and compile it. This lets 
us reuse all the export gating logic, rather than writing separate, specific 
logic for ArrowFunctionExpression.
2023-03-20 17:42:58 +00:00
Sathya Gunasekaran ff571d8d85 [Babel] Refactor compilation checks into a separate function 2023-03-20 17:42:57 +00:00
Sathya Gunasekaran 93b79e4aa0 [Babel] Refactor visitor to separate function
This lets us extend to ArrowFunctionExpression
2023-03-20 17:42:56 +00:00
Sathya Gunasekaran 3605199646 [Babel] Refactor gating test
Move to separate function
2023-03-20 17:42:56 +00: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 ff8868e8d1 Fix self-closing jsx element codegen
We were never emitting self-closing jsx elements because children were never 
null, we now set children to null if they were empty in the AST.
2023-03-17 16:18:11 -07:00
Joe Savona a7ac20973f Support JsxMemberExpression 2023-03-17 16:18:07 -07:00
mofeiZ 0c8f11e13f [test][ssa] Add todo test for reassigning in rval
From my understanding, `LeaveSSA` is not currently handling reassignment in 
rvals. 

Source code 

```js 

let x = foo(); 

x(x = bar()); 

``` 

Simplified HIR (from EnterSSA) 

``` 

[1]  $1 = StoreLocal Let x$0 = [[ foo() ]]       // x$1 = foo() 

[2] $2 = LoadLocal x$1                           // t0 = x$1 

[3] $4 = StoreLocal Reassign x$3 = [[ bar() ]]   // t1 = x$3 = bar() 

[4] $5 = CallExpression $2 ($4)                   // t0(t1) 

``` 

codegen: 

```js 

let x; 

x = foo(); 

x = bar(); 

x(x); 

```
2023-03-17 20:08:20 -04:00
Joe Savona f838ebb34e Simpler fix for missed reactivity bug
Fixes the issue @mofeiZ identified, where mutations of an object via a 
propertyload were not tracking reactivity.
2023-03-17 15:07:14 -07:00
mofeiZ 2148b4e8ca [test][hir-lower] todo: lower property calls in evaluation order
We currently are not lowering property calls in evaluation order. 

I wonder if the following lowering for a PropertyCall (with static or computed 
property) is semantically equivalent to source: 

1. eval + resolve receiver (store in t0) 

2. eval computed property (if present) 

3. resolve t0.property (binding the call to receiver and storing in t1) 

4. eval args 

5. eval t1(args) 

Although codegen might then generate something like this (if args are named 
temporaries) 

```js 

const tmp = receiver.property.bind(receiver); 

// lower args to temporaries 

tmp(arg1, arg2); 

```
2023-03-16 15:48:54 -04:00