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.
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.
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.
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.
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.
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;
```
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.
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.
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).
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.
Refactors the representation of ObjectExpression properties from a Map to an
`Array<ObjectProperty>` to prepare for the next diff which adds spread element
support.
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.
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.
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.
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.
---
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.
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.
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.
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.
Changes ReactiveWhileTerminal’s test to use the new value block representation.
This means logical and condition expressions will work as while test values now.
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.
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.
Extends ReactiveInstruction's value type to be a regular InstructionValue *or* a
LogicalValue. LogicalValue is operator, left, and right. It's really convenient
that we've already distinguished Instruction/ReactiveInstruction now — while the
_helpers_ here are updated to handle this new value type, the types ensure that
HIR can never encounter a LogicalValue.
The actual conversion of logical terminals into this value is complex and is
later in the stack.