Commit Graph
708 Commits
Author SHA1 Message Date
Joe Savona d243e748d0 Build -> Codegen for LabelTerminal
NOTE: See background in #1476. 

Updates BuildHIR to use the new LabelTerminal for LabeledStatements, and adds 
support for HIR->ReactiveFunction transformation and codegen. Note that we 
sometimes produce an extraneous block wrapper if it turns out the label wasn't 
necessary, that seems...fine?
2023-04-05 16:26:40 -07:00
Joe Savona b087635e5d LabelTerminal scaffolding
Adds a new `LabelTerminal` which will be used to represent LabeledStatements 
that contain a statement other than a loop. What we do for these cases is 
basically break the containing block in two, with a goto after the inner 
statement to the fallthrough. This allows us to model the label, and any `break` 
to it, in the HIR. However this fails in codegen because we can't find the 
fallthrough branch — we need a high level terminal that knows about this 
structure. 

Hence LabelTerminal. Now, instead of just a continuation block and a goto, we 
have a structured terminal. The LabelTerminal expresses the block for the 
labeled statement and the continuation, and we can use this to put it back 
together when constructing a ReactiveFunction. Note that this PR is just the 
scaffolding for LabelTerminal, the next PR is the interesting bits.
2023-04-05 16:26:36 -07:00
Joe Savona 592b911e7f fix main 2023-04-05 15:47:40 -07:00
Joe Savona 1650d8fef2 Ensure blank line after copyright header 2023-04-05 13:42:27 -07:00
Joe Savona 801e4258cf Add missing copyright headers 2023-04-05 12:21:51 -07:00
Joe Savona fd91191c94 Script to add/fix copyright
Adds a script to automate adding/updating the copyright header to all 
appropriate files. For now i've excluded fixture inputs, just because it would 
impact fixture outputs too, but we can add them in a later PR if we want.
2023-04-05 12:21:47 -07:00
mofeiZ 9d97015236 [globals] Remove global shape for Array.from
Type inference currently assumes that a `FunctionSignature`'s effects have no 
false positives. If a `mutate` effect is observed on a read-only place, Forget 
currently assumes this is an user error and 
[throws](https://github.com/facebook/react-forget/blob/207595e04e2be08b8f62bf21dac9d846b9651e43/forget/src/Inference/InferReferenceEffects.ts#L275-L281). 

Array.from is polymorphic -- its effects are dependent on the type of its 
parameters
2023-04-05 12:53:39 -04:00
Joe Savona 4505218911 Ensure unique BlockId for nested functions
This PR ensures that we use a single id space for the `BlockId`s in both 
top-level functions as well as any nested FunctionExpressions (note, we already 
do this for `IdentifierId`). This will make it easier for follow-ups to merge 
the CFG of nested functions (ie useMemo bodies) with the parent without block id 
collisions.
2023-04-05 09:01:08 -07:00
Joe Savona a3dfc7e3a2 Use CompilerError.invariant in BabelPlugin
This means we get more information when we hit these invariants (ie the source 
location)
2023-04-04 17:56:11 -07:00
Joe Savona 1e2dbf7fdb Fix JSX form of fbt
Fixes `<fbt>`. This required a bunk of yak shaving to work through several 
issues: 

* First, there was a bug in codegen for JsxNamedspacedName. I added handling for 
it for identifiers, but JsxNamespacedName gets converted to a Primitive. The 
output looked correct because Babel happily creates invalid Jsx identifiers! 

* Next, I needed to add locations to JSX nodes. It took me a while to pinpoint 
which specific node needed the location, so I ended up just adding locations to 
all the parts of a Jsx element. 

* That uncovered the fact that FBT was expecting the `<fbt:param>`'s `name` 
attribute value to be a StringLiteral, not a StringLiteral wrapped in a 
JsxExpressionContainer. So now we special-case JsxAttribute and emit raw 
StringLiteral (either is allowed per the spec) 

And with that, voila, `<fbt>` works.
2023-04-04 14:37:30 -07:00
Joe Savona 3c46984e09 Repro of fbt issues
Adds the two FBT (https://facebook.github.io/fbt/) plugins to our test setup so 
that we can verify Forget plays well with FBT. Unfortunately FBT's plugins are a 
bit finicky, and things that are technically allowed per the JSX spec (such as 
wrapping string attribute values in a JsxExpressionContainer) aren't supported 
by FBT's plugin. This PR is just to add the fbt plugins and highlight some cases 
that fail; these are fixed in later PRs in the stack. 

For example, the `fbt-params.js` fixture fails on this PR: 

Input 

```error.fbt-params.js 

import fbt from "fbt"; 

function Component(props) { 

return ( 

<fbt desc={"Dialog to show to user"}> 

Hello <fbt:param name="user name">{props.name}</fbt:param> 

</fbt> 

); 

} 

``` 

Output 

``` 

React Forget › __tests__/fixtures/compiler › fixtures › fbt-params 

Expected fixture 'fbt-params' to succeed but it failed with error: 

/Users/joesavona/github/react-forget/forget/fbt-params: fbt: unsupported babel 
node: MemberExpression 

--- 

props.name 

--- 

``` 

See fixes later in the stack.
2023-04-04 14:32:21 -07:00
Joe Savona a97c55dc5d Support for with empty update expression
Adds support for `for` statements with an empty or unreachable update 
expression. In both cases, reversePostorderBlocks() will remove the 
empty/unreachable update block, leaving the ForTerminal.update pointing to a 
non-existent block. We explicitly rewrite this (much like we null out 
unreachable fallthroughs after shrink). When transforming to ReactiveFunction, 
we emit the update block as null if it was the same as the test block.
2023-04-04 13:29:04 -07:00
Joe Savona 1d77016026 Make ReactiveForTerminal.update nullable
Makes ReactiveForTerminal.update nullable, allowing the update clause to be 
omitted. Follow-up diffs null out the update clause in some circumstances.
2023-04-04 13:29:03 -07:00
Joe Savona d660f37bf2 Validator pass that terminal successors all exist 2023-04-04 13:29:02 -07:00
Joe Savona 6d62d9f505 Infer closures as frozen if they dont capture mutable values
Fix for the previous issue, suggested by @gsathya: when we run 
InferReferenceEffects on the outer function we check each closure to see if it 
actually captured any mutable values. If it didn't, we can mark the closure as 
readonly and memoize it independently.
2023-04-04 12:30:12 -07:00
Joe Savona 939582dae0 Repro for unmemoized readonly callback
Repro of a closure that we currently treat as readonly because it captures a 
possibly-mutable value, but which we later realize is not mutable. Specifically, 
when we check `exit()` we think `dispatch()` is mutable and therefore consider 
it captured, which means we can't independently memoize `exit`.
2023-04-04 12:30:11 -07:00
Joe Savona 2e3aa3954c Memoize hook args (treat as escaping)
Updates `PruneNonEscapingScopes` to consider hook arguments as potentially 
escaping. This is because hook inputs are "owned" by React — for example, 
closures passed to `useEffect`, or a value that is passed to a custom hook and 
which then becomes a memoized input.
2023-04-04 12:30:10 -07:00
Joe Savona e32ea49a0e Special-case fbt() function call form
Similar to what we did for `<fbt>` jsx elements, this PR ensures that `fbt()` 
calls have their operands memoized in the same scope to honor the limited 
contract for what's allowed as an argument of an fbt() call expression.
2023-04-03 11:47:45 -07:00
Sathya Gunasekaran 0cb10446b3 [test] Failing test for returning from a for-loop 2023-04-01 09:22:34 +01:00
Lauren Tan b19555573f [babel] Make gating option a pair of module and project
There are some internal restrictions in Metro that only allow us to specify one 
gating module as an injected dependency. To allow multiple projects, this PR 
updates the Babel plugin to take a gating options config specifiying a project 
name. The project name is used as a suffix for the generated import; for 
example: 

```js 

const options = { 

// ... 

gating: { 

module: "ReactForgetFeatureFlag", 

importSpecifierName: "isForgetEnabled_Secret", 

}; 

// generates 

import {isForgetEnabled_Secret} from "ReactForgetFeatureFlag"; // a module that 
exports multiple flags 

// ... 

```
2023-04-03 12:33:23 -04:00
Sathya Gunasekaran 1aec5ef523 [hir] Skip computed part of the captured ref 2023-03-31 17:37:06 +01:00
Lauren Tan 55793bd96f Also visit phis in fallthroughs for DoWhile in LeaveSSA
Oops, forgot about this previously
2023-03-31 17:55:07 -04:00
Lauren Tan 61462a43bc Add support for ForOf statements
Teaches Forget to compile simple ForOf statements, where the init comprises of a 
variable declaration with an identifier or destructure.
2023-03-31 17:55:04 -04:00
Joe Savona 1e2df5bdbb Remove console.log from testing sync script changes
oooooops
2023-03-31 14:43:31 -07:00
Joe Savona 28e6a974fa JSX: children which are jsx elements dont need expr container 2023-03-31 12:32:46 -07:00
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