Commit Graph

683 Commits

Author SHA1 Message Date
Joe Savona 4e2ef92779 Ensure <fbt> children are not independently memod
This is a Meta-ism, but adding it for now to unblock. We special-case the 
`<fbt>` element for translation purposes, and have a transform that requires the 
children of this element to be a limited subset of nodes. Notably, any dynamic 
translation values must appear as `<fbt:param>` children — we disallow 
identifiers as children of `<fbt>` nodes. 

This PR adds a new pass which finds `<fbt>` nodes and ensures their immediate 
operands are not independently memoized. Note that this still allows the values 
of `<fbt:param>` to be independently memoized, as demonstrated in the unit test.
2023-03-31 12:32:45 -07:00
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
Mofei Zhang c00e7a2af2 [test] Test case for optional chaining in codegen
```js 

// here, `a?.b.c` is a single optional chain 

// (evaluates to undefined if a is nullish) 

a?.b.c; 

// here, 'a?.b` is an optional chain, and `.c` is an unconditional load 

// (nullthrows if a is nullish) 

(a?.b).c; 

``` 

--- 

Next PR in stack will add a bailout for `(a?.b).c`. 

(If we want to properly handle `(a?.b).c`, we might want to model optional 
chains explicitly in the HIR. We currently assume that any `PropertyLoad` whose 
lhs is an optional property load is read conditionally.)
2023-03-30 18:43:55 -04:00
Sathya Gunasekaran 0eeedee95e [hir] Rewrite identifiers to be consistent
If we resolve identifiers to be different from the binding, update the binding 
to the new name.
2023-03-31 12:35:17 +01:00
Sathya Gunasekaran 3b3d15eaeb [hir] Check if callee before pruning member path 2023-03-31 16:36:04 +01:00
Lauren Tan 46a9d40914 [babel] Invoke gating module as a call expression
This is incredibly obvious in hindsight, but for exposure logging to work 
correctly we need to *call* the underlying `MobileConfig.getBool` function at 
the callsite – otherwise the bool is evaluated once (and only once) when the 
module is loaded. 

Tested internally and verified that in dogfooding the exposure logging was 
working correctly
2023-03-31 11:15:51 -04:00
mofeiZ 5beecef28f [patch] Remove invalid invariant in deriveMinimalDeps
Confirmed that this fixes invariant violation on -
2023-03-30 17:33:37 -04:00
Lauren Tan d67b3cbdc0 [printer] Add missing terminal printer for do-while 2023-03-30 15:50:08 -04:00
Lauren Tan 89ea2d5f7c [be] Fix missing break and update no-fallthrough eslint rule
- Fixes a missing break in InferTypes - I disabled no-fallthrough previously 
because it would erroneously report that certain cases with non-builtin throws 
(eg `invariant`) would fall through. This brings the rule back but allows 
disabling it with a `// break omitted` comment, since it's still helpful in 
catching some actual missing breaks.
2023-03-30 15:50:07 -04:00
Mofei Zhang e43f91edc3 [env] Type inference for global object
- Add `DEFAULT_SHAPES` ShapeRegistry, which holds builtins and all 
`ObjectShapes` used in `DEFAULT_GLOBALS`. 

- Add a few typed objects / functions into `DEFAULT_GLOBALS` (used for tests) 

- Add type inference and `infer-global-object` test
2023-03-30 15:03:11 -04:00
Mofei Zhang 25388d3fda [be] Clean up null -> PolyType in ObjectShape
--- 

Question - should the unifier ignore `PolyType` for now? 

```js 

class Unifier { 

// ... 

bindVariableTo(v: TypeVar, type: Type) { 

if (type.kind === "Poly") { 

return; 

} 

// ... 

```
2023-03-30 15:03:11 -04:00
Mofei Zhang ffc1b5b700 [be] More comments for ObjectShapes; rename BuiltIn shapes 2023-03-30 15:03:10 -04:00
Mofei Zhang 44b2f504ea [rfc][env] Hook, object, and function types for Environment globals
Adds `GlobalRegistry`, which holds the names and types of known global objects, 
i.e. 

```js 

type GlobalRegistry = Map<string, PrimitiveType | ObjectType | FunctionType | 
HookType | PolyType>; 

// ... 

globalRegistry.get("NaN"); // {kind: "Primitive"} 

globalRegistry.get("parseInt"); // {kind: "Function", shapeId: "..."} 

globalRegistry.get("Math"); // {kind: "Object", shapeId: "..."} 

``` 

Since we currently do not track module imports and module-level declarations, 
builtin and custom hooks currently also live in GlobalRegistry. 

```js 

globalRegistry.get("useState"); // {kind: "Hook", definition: {...}} 

globalRegistry.get("useFreeze"); // {kind: Hook, definition: {...}} 

``` 

This PR does not allow Forget users to define their own globals. When we add 
this as a configuration, we should not expose `ShapeRegistry` to the user, as a 
user-provided ShapeRegistry may accidentally be not well formed. (i.e. missing 
(1) required shapes (BuiltInArray for [] and BuiltInObject for {}) or (2) some 
recursive shapeIds) 

```js 

export type UserType = UserObject | UserFunction | "Primitive" | "BuiltinObject" 
| ...; 

export type UserObject = { 

kind: "Object", 

properties: Map<string, UserType> 

} 

export type UserFunction = { 

kind: "Function", 

properties: Map<string, UserType>, 

signature: ... 

} 

export type UserGlobals = Map<string, UserType>; 

class Environment { 

constructor(globals: Map<string, UserType>, ...) { 

// ... 

addUserDefinedGlobals(this.#globals, this.#shapes); 

```
2023-03-30 15:03:10 -04:00
Mofei Zhang 45331ef21b [be][env] Move EnvironmentOptions -> EnvironmentConfig
--- 

Simplify Environment options by: 

- EnvironmentOptions -> EnvironmentConfig 

config is now directly passed around instead of being eagerly merged. 

- Moving merging / initialization logic into `Environment` constructor. From my 
understanding, there is no need to decouple merged options from an environment. 

This prepares Environment for the next PR, which adds non-stateful properties to 
Environment (i.e. a `GlobalRegistry`) that should be converted from config 
values (i.e. not directly exposed to the user due to potentially inconsistent 
inputs)
2023-03-30 15:03:09 -04:00
Mofei Zhang 8e37df6dab [be] Hook type inference should only happen at LoadGlobal
--- 

#1254 added inference for hooks loaded from globals. This is the only time we 
need to generate a type equation assigning `lval` to a resolved`Hook` type. 

@gsathya Would love to get your feedback here on the change. From my 
understanding, this change is technically incorrect, since the type equation we 
generate should be dependent on the `callee` type (i.e. `Hook` if callee is a 
hook, `Function` if callee is a function). 

Would the next step be to consolidate `Hook` and `Function` types? 

```js 

type Function { 

... 

isHook: boolean, // set by inference 

} 

type FunctionSignature { 

isHook: boolean, // set when adding to ShapeRegistry 

} 

```
2023-03-30 15:03:09 -04:00
Sathya Gunasekaran e81063e5ef [rfc] Desugar FunctionDeclaration to a FunctionExpression 2023-03-30 15:48:54 +01:00
Sathya Gunasekaran 05f48e2aac [hir] Refactor FunctionExpression lowering into separate function
This will allow us to reuse all the lowering for FunctionDeclaration.
2023-03-30 15:48:53 +01:00
mofeiZ c2154b2785 [playground] Only collect first level of function declarations
#1433 means that Forget now will compile nested function declarations, so we no 
longer need to compile these separately
2023-03-29 17:14:58 -04:00
Joe Savona 57bc5fd56e Support AssignmentPattern in params (param default values)
I already taught `lowerAssignment()` to handle assignment patterns for 
destructuring, we just have to call this helper for assignment pattern params 
too.
2023-03-28 09:52:55 -07:00
Sathya Gunasekaran 0999b6b815 [hir] Treat captured refs in throw or return terminals as mutations
In a lambda, a return/throw terminal could return a captured context ref needs 
to be treated as a mutation to correctly alias the returned  context ref and the 
lvalue.
2023-03-29 17:15:00 +01:00
Sathya Gunasekaran dd0476a1b5 [hir] Infer mutable ranges for terminals
Terminal operands are generally not mutating so this hasn't mattered so far. But 
in a lambda, a return terminal could return a captured context ref which needs 
to be treated as a mutation to correctly alias the returned context ref and the 
lvalue.
2023-03-29 15:48:55 +01:00
Sathya Gunasekaran e0180ce86a [lambdas] Track ReactiveScopeDepenency of ComputedLoad
Fixes an issue where we did not track mutations to captured computed loads in 
lambdas.
2023-03-28 16:29:31 +01:00
mofeiZ ec0abcd643 [hir] Strip JSXEmptyExpression syntax
`JSXEmptyExpression` is never added to a React element's children [in 
`react.buildChildren`](https://github.com/babel/babel/blob/main/packages/babel-types/src/builders/react/buildChildren.ts), 
which is [used 
by](https://github.com/babel/babel/blob/main/packages/babel-plugin-transform-react-jsx/src/create-plugin.ts#L649) 
`plugin-transform-react-jsx`]. 

An alternative would be to represent JSX expressions differently in HIR, then 
codegen `JSXEmptyExpression`s back when we encounter an `EmptyExpression` 

```js 

-   children: Array<Place>, 

-   children: Array<Place | "EmptyExpression">, 

``` 

(We could also retain `JSXEmptyExpression` as an `InstructionValue` that 
produces a Primitive. However, this would make babel types in Codegen a bit more 
messy, as `JSXEmptyExpression` does not extend `Expression` (which currently is 
the result of every `InstructionValue`).)
2023-03-27 18:12:18 -04:00
Joe Savona 43176b129e [be] Simplify BuildHIR w lowerValueToTemporary helper
Creates a new helper, `const temp: Place = lowerValueToTemporary(builder, 
value)` which creates a new temporary and an instruction to write that value to 
the temporary. We have this pattern all over BuildHIR, and the new helper makes 
this all a bit tidier.
2023-03-27 11:35:34 -07: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 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