Compare commits

..

2217 Commits

Author SHA1 Message Date
Josh Story
6230622a1a [Float][Fiber] Assume stylesheets in document are already loaded (cherrypick #29811) (#29835)
Cherrypick #29811

When we made stylesheets suspend even during high priority updates we
exposed a bug in the loading tracking of stylesheets that are loaded as
part of the preamble. This allowed these stylesheets to put suspense
boundaries into fallback mode more often than expected because cases
where a stylesheet was server rendered could now cause a fallback to
trigger which was never intended to happen.

This fix updates resource construction to evaluate whether the instance
exists in the DOM prior to construction and if so marks the resource as
loaded and inserted.

One ambiguity that needed to be solved still is how to tell whether a
stylesheet rendered as part of a late Suspense boundary reveal is
already loaded. I updated the instruction to clear out the loading
promise after successfully loading. This is useful because later if we
encounter this same resource again we can avoid the microtask if it is
already loaded. It also means that we can concretely understand that if
a stylesheet is in the DOM without this marker then it must have loaded
(or errored) already.
2024-06-10 11:41:15 -07:00
Sebastian Markbåge
1df34bdf62 [Flight] Override prepareStackTrace when reading stacks (#29740)
This lets us ensure that we use the original V8 format and it lets us
skip source mapping. Source mapping every call can be expensive since we
do it eagerly for server components even if an error doesn't happen.

In the case of an error being thrown we don't actually always do this in
practice because if a try/catch before us touches it or if something in
onError touches it (which the default console.error does), it has
already been initialized. So we have to be resilient to thrown errors
having other formats.

These are not as perf sensitive since something actually threw but if
you want better perf in these cases, you can simply do something like
`onError(error) { console.error(error.message) }` instead.

The server has to be aware whether it's looking up original or compiled
output. I currently use the file:// check to determine if it's referring
to a source mapped file or compiled file in the fixture. A bundled app
can more easily check if it's a bundle or not.
2024-06-05 09:41:37 +02:00
Sebastian Markbåge
d2767c96e8 [Flight] Encode fragments properly in DEV (#29762)
Normally we take the renderClientElement path but this is an internal
fast path.

No tests because we don't run tests with console.createTask (which is
not easy since we test component stacks).

Ideally this would be covered by types but since the types don't
consider flags and DEV it doesn't really help.
2024-06-04 18:10:06 -04:00
Ricky
eabb681535 Add xplat test variants (#29734)
## Overview

We didn't have any tests that ran in persistent mode with the xplat
feature flags (for either variant).

As a result, invalid test gating like in
https://github.com/facebook/react/pull/29664 were not caught.

This PR adds test flavors for `ReactFeatureFlag-native-fb.js` in both
variants.
2024-06-04 13:07:29 -04:00
Jiachi Liu
9185b9b1e4 Remove startTransition and useActionState from react-server condition of react (#29753)
<!--
  Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.

Before submitting a pull request, please make sure the following is
done:

1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
  2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
  9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
  10. If you haven't already, complete the CLA.

Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->

## Summary

Remove `startTransition` and `useActionState` from `react-server`
condition of react, as they should only stay in client bundle.
This will reduce the server bundle of react itself. 

Found this while tracing where the `process.emit` was called.

<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->

## How did you test this change?

<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
  If you leave this empty, your PR will very likely be closed.
-->
2024-06-04 12:23:36 -04:00
Jan Kassens
a26e90c29c www: set enableRefAsProp to true (#29756)
www: set enableRefAsProp to true
2024-06-04 11:17:19 -04:00
XiaoPi
408258268e fix<compiler>: only call readTestFilter if the filter option is enabled (#29720)
Following the instructions in the compiler/docs/DEVELOPMENT_GUIDE.md, we are stuck on the command `yarn snap --watch` because it calls readTestFilter even though the filter option is not enabled.
2024-06-03 16:09:58 -07:00
Joe Savona
8b677b1e6e compiler: Allow opting out of installed library check
ghstack-source-id: eedd024d36
Pull Request resolved: https://github.com/facebook/react/pull/29742
2024-06-03 15:30:55 -07:00
Ricky
8c3697a849 Fix xplat sync to ignore @generated header (#29738)
Use some clever git diffing to ignore lines that only change the
`@generated` header. We can't do this for the version string because the
version string can be embedded in lines with other changes, but this
header is always on one line.
2024-06-03 16:39:38 -04:00
Sebastian Markbåge
4dcdf21325 [Fiber] Prefix owner stacks with the current stack at the console call (#29697)
This information is available in the regular stack but since that's
hidden behind an expando and our appended stack to logs is not hidden,
it hides the most important frames like the name of the current
component.

This is closer to what happens to the native stack.

We only include stacks if they're within a ReactFiberCallUserSpace call
frame. This should be most that have a current fiber but this is
critical to filtering out most React frames if the regular node_modules
filter doesn't work.

Most React warnings fire during the rendering phase and not inside a
user space function but some do like hooks warnings and setState in
render. This feature is more important if we port this to React DevTools
appending stacks to all logs where it's likely to originate from inside
a component and you want the line within that component to immediately
part of the visible stack.

One thing that kind sucks is that we don't have a reliable way to
exclude React internal stack frames. We filter node_modules but it might
not match. For other cases I try hard to only track the stack frame at
the root of React (e.g. immediately inside createElement) until the
ReactFiberCallUserSpace so we don't need the filtering to work. In this
case it's hard to achieve the same thing though. This is easier in RDT
because we have the start/end line and parsing of stack traces so we can
use that to exclude internals but that's a lot of code/complexity for
shipping within the library.

For example in Safari:

<img width="590" alt="Screenshot 2024-05-31 at 6 15 27 PM"
src="https://github.com/facebook/react/assets/63648/2820c8c0-8a03-42e9-8678-8348f66b051a">

Ideally warnOnUseFormStateInDev and useFormState wouldn't be included
since they're React internals. Before this change, the Counter.js line
also wasn't included though which points to exactly where the error is
within the user code.

(Note Server Components have V8 formatted lines and Client Components
have JSC formatted lines.)
2024-06-03 12:26:38 -04:00
Andrew Clark
bf3a29d097 Update build script to automatically generate RCs (#29736)
RC releases are a special kind of prerelease build because unlike
canaries we shouldn't publish new RCs from any commit on `main`, only
when we intentionally bump the RC number. But they are still prerelases
— like canary and experimental releases, they should use exact version
numbers in their dependencies (no ^).

We only need to generate these builds during the RC phase, i.e. when the
canary channel label is set to "rc".

Example of resulting package.json output:

```json
{
  "name": "react-dom",
  "version": "19.0.0-rc.0",
  "dependencies": {
    "scheduler": "0.25.0-rc.0"
  },
  "peerDependencies": {
    "react": "19.0.0-rc.0"
  }
}
```


https://react-builds.vercel.app/prs/29736/files/oss-stable-rc/react-dom/package.json
2024-06-03 12:21:21 -04:00
Andrew Clark
9598c41a20 useActionState: On error, cancel remaining actions (#29695)
Based on

- #29694 

---

If an action in the useActionState queue errors, we shouldn't run any
subsequent actions. The contract of useActionState is that the actions
run in sequence, and that one action can assume that all previous
actions have completed successfully.

For example, in a shopping cart UI, you might dispatch an "Add to cart"
action followed by a "Checkout" action. If the "Add to cart" action
errors, the "Checkout" action should not run.

An implication of this change is that once useActionState falls into an
error state, the only way to recover is to reset the component tree,
i.e. by unmounting and remounting. The way to customize the error
handling behavior is to wrap the action body in a try/catch.
2024-06-03 11:25:43 -04:00
Andrew Clark
67b05be0d2 useActionState: Transfer transition context (#29694)
Mini-refactor of useActionState to only wrap the action in a transition
context if the dispatch is called during a transition. Conceptually, the
action starts as soon as the dispatch is called, even if the action is
queued until earlier ones finish.

We will also warn if an async action is dispatched outside of a
transition, since that is almost certainly a mistake. Ideally we would
automatically upgrade these to a transition, but we don't have a great
way to tell if the action is async until after it's already run.
2024-06-03 11:20:27 -04:00
Josh Story
def67b9b32 Fix stylesheet typo in 29693 (#29732)
stylehsheet -> stylesheet
2024-06-03 07:51:21 -07:00
Josh Story
47d0c30246 [Fiber][Float] Error when a host fiber changes "flavor" (#29693)
Host Components can exist as four semantic types

1. regular Components (Vanilla obv)
2. singleton Components
2. hoistable components
3. resources

Each of these component types have their own rules related to mounting
and reconciliation however they are not direclty modeled as their own
unique fiber type. This is partly for code size but also because
reconciling the inner type of these components would be in a very hot
path in fiber creation and reconciliation and it's just not practical to
do this logic check here.

Right now we have three Fiber types used to implement these 4 concepts
but we probably need to reconsider the model and think of Host
Components as a single fiber type with an inner implementation. Once we
do this we can regularize things like transitioning between a resource
and a regular component or a singleton and a hoistable instance. The
cases where these transitions happen today aren't particularly common
but they can be observed and currently the handling of these transitions
is incomplete at best and buggy at worst. The most egregious case is the
link type. This can be a regular component (stylesheet without
precedence) a hoistable component (non stylesheet link tags) or a
resource (stylesheet with a precedence) and if you have a single jsx
slot that tries to reconcile transitions between these types it just
doesn't work well.

This commit adds an error for when a Hoistable goes from Instance to
Resource. Currently this is only possible for `<link>` elements going to
and from stylesheets with precedence. Hopefully we'll be able to remove
this error and implement as an inner type before we encounter new
categories for the Hoistable types

detecting type shifting to and from regular components is harder to do
efficiently because we don't want to reevaluate the type on every update
for host components which is currently not required and would add
overhead to a very hot path

singletons can't really type shift in their one practical implementation
(DOM) so they are only a problem in theroy not practice
2024-06-03 07:47:45 -07:00
Rick Hanlon
b421783110 Don't skip www commit 2024-06-03 10:21:28 -04:00
Ricky
5ad2c37273 Skip empty sync commits (both repos) (#29707)
Requires https://github.com/facebook/react/pull/29706

The strategy here is to:
- Checkout the builds/facebook-www branch
- Read the current sync'd VERSION
- Checkout out main and sync new build
- sed/{new version string}/{old version string}
- Run git status, skip sync if clean
- Otherwise, sed/{old version string}/{new version string} and push
commit

This means that:
- We're using the real version strings from the builds
- We are checking the last commit on the branch for the real last
version
- We're skipping any commits that won't result in changes
- ???
- Profit!
2024-06-03 10:09:23 -04:00
Sebastian Markbåge
ba099e442b [Flight] Add findSourceMapURL option to get a URL to load Server source maps from (#29708)
This lets you click a stack frame on the client and see the Server
source code inline.

<img width="871" alt="Screenshot 2024-06-01 at 11 44 24 PM"
src="https://github.com/facebook/react/assets/63648/581281ce-0dce-40c0-a084-4a6d53ba1682">

<img width="840" alt="Screenshot 2024-06-01 at 11 43 37 PM"
src="https://github.com/facebook/react/assets/63648/00dc77af-07c1-4389-9ae0-cf1f45199efb">

We could do some logic on the server that sends a source map url for
every stack frame in the RSC payload. That would make the client
potentially config free. However regardless we need the config to
describe what url scheme to use since that’s not built in to the bundler
config. In practice you likely have a common pattern for your source
maps so no need to send data over and over when we can just have a
simple function configured on the client.

The server must return a source map, even if the file is not actually
compiled since the fake file is still compiled.

The source mapping strategy can be one of two models depending on if the
server’s stack traces (`new Error().stack`) are source mapped back to
the original (`—enable-source-maps`) or represents the location in
compiled code (like in the browser).

If it represents the location in compiled code it’s actually easier. You
just serve the source map generated for that file by the tooling.

If it is already source mapped it has to generate a source map where
everything points to the same location (as if not compiled) ideally with
a segment per logical ast node.
2024-06-02 22:58:24 -04:00
Lauren Tan
d77dd31a32 Bump version to 0.0.0-experimental-7054a14-20240601 2024-06-01 08:15:27 +09:00
Lauren Tan
c6b651bee0 Bump version to 0.0.0-experimental-51a85ea-20240601 2024-06-01 08:15:27 +09:00
Lauren Tan
b17016c869 Bump version to 0.0.0-experimental-938cd9a-20240601 2024-06-01 08:15:27 +09:00
Lauren Tan
113c8e7f72 [compiler:eslint] Don't crash if hermes parser fails to parse
Eslint rules should never throw, so if we fail to parse with Babel or
Hermes, we should just ignore the error. This should fix issues such as
trying to run the eslint rule on non tsx|ts|jsx|js files, Hermes parser
not supporting certain JS syntax, etc.

I didn't add a test for this as our eslint-rule-tester config uses
hermes-eslint parser, so it wasn't possible to add a top level await as
it would crash hermes-eslint before our rule was triggered. Similarly I
couldn't add a test for non-JS files as it would not be parseable by
hermes-eslint.

Fixes #29107

ghstack-source-id: 60afcdb89ab4a8d2e4697cc50c5490803e7cbeac
Pull Request resolved: https://github.com/facebook/react/pull/29631
2024-06-01 08:04:41 +09:00
Andrew Clark
adbec0c25a Fix: useTransition after use gets stuck in pending state (#29670)
When a component suspends with `use`, we switch to the "re-render"
dispatcher during the subsequent render attempt, so that we can reuse
the work from the initial attempt. However, once we run out of hooks
from the previous attempt, we should switch back to the regular "update"
dispatcher.

This is conceptually the same fix as the one introduced in
https://github.com/facebook/react/pull/26232. That fix only accounted
for initial mount, but the useTransition regression test added in
f82973302b3f490ec120c3b102e8c3792452dfc9 illustrates that we need to
handle updates, too.

The issue affects more than just useTransition but because most of the
behavior between the "re-render" and "update" dispatchers is the same
it's hard to contrive other scenarios in a test, which is probably why
it took so long for someone to notice.

Closes #28923 and #29209

---------

Co-authored-by: eps1lon <sebastian.silbermann@vercel.com>
2024-05-31 17:52:47 -04:00
Mike Vitousek
ec6fe57a50 [compiler] rfc: Include location information in identifiers and reactive scopes for debugging
Summary: Using the change detection code to debug codebases that violate the rules of react is a lot easier when we have a source location corresponding to the value that has changed inappropriately. I didn't see an easy way to track that information in the existing data structures at the point of codegen, so this PR adds locations to identifiers and reactive scopes (the location of a reactive scope is the range of the locations of its included identifiers).

I'm interested if there's a better way to do this that I missed!

ghstack-source-id: aed5f7edda
Pull Request resolved: https://github.com/facebook/react/pull/29658
2024-05-31 14:06:04 -07:00
Mike Vitousek
522d22f299 [compiler] Recompute values every time
Summary: This PR expands the analysis from the previous in the stack in order to also capture when a value can incorrectly change within a single render, rather than just changing between two renders. In the case where dependencies have changed and so a new value is being computed, we now compute the value twice and compare the results. This would, for example, catch when we call Math.random() in render.

The generated code is a little convoluted, because we don't want to have to traverse the generated code and substitute variable names with new ones. Instead, we save the initial value to the cache as normal, then run the computation block again and compare the resulting values to the cached ones. Then, to make sure that the cached values are identical to the computed ones, we reassign the cached values into the output variables.

ghstack-source-id: d0f11a4cb2
Pull Request resolved: https://github.com/facebook/react/pull/29657
2024-05-31 14:06:02 -07:00
Mike Vitousek
c69211a9df [compiler] Prune dependencies that are only used by useRef or useState
Summary: jmbrown215 recently had an observation that the arguments to useState/useRef are only used when a component renders for the first time, and never afterwards. We can skip more computation that we previously could, with reactive blocks that previously recomputed values when inputs changed now only ever computing them on the first render.

ghstack-source-id: 5d044ef787
Pull Request resolved: https://github.com/facebook/react/pull/29653
2024-05-31 14:06:00 -07:00
Mike Vitousek
5c420e3824 [compiler] Debug tool to emit change detection code rather than memoization
Summary: The essential assumption of the compiler is that if the inputs to a computation have not changed, then the output should not change either--computation that the compiler optimizes is idempotent.

This is, of course, known to be false in practice, because this property rests on requirements (the Rules of React) that are loosely enforced at best. When rolling out the compiler to a codebase that might have rules of react violations, how should developers debug any issues that arise?

This diff attempts one approach to that: when the option is set, rather than simply skipping computation when dependencies haven't changed, we will *still perform the computation*, but will then use a runtime function to compare the original value and the resultant value. The runtime function can be customized, but the idea is that it will perform a structural equality check on the values, and if the values aren't structurally equal, we can report an error, including information about what file and what variable was to blame.

This assists in debugging by narrowing down what specific computation is responsible for a difference in behavior between the uncompiled code and the program after compilation.

ghstack-source-id: 50dad3dacf
Pull Request resolved: https://github.com/facebook/react/pull/29656
2024-05-31 14:05:58 -07:00
Mike Vitousek
8b01a2e0bf [compiler] Option to always take the non-memo branch
Summary: This adds a debugging mode to the compiler that simply adds a `|| true` to the guard on all memoization blocks, which results in the generated code never using memoized values and always recomputing them. This is designed as a validation tool for the compiler's correctness--every program *should* behave exactly the same with this option enabled as it would with it disabled, and so any difference in behavior should be investigated as either a compiler bug or a pipeline issue.

(We add `|| true` rather than dropping the conditional block entirely because we still want to exercise the guard tests, in case the guards themselves are the source of an error, like reading a property from undefined in a guard.)

ghstack-source-id: 955a47ec16
Pull Request resolved: https://github.com/facebook/react/pull/29655
2024-05-31 14:04:54 -07:00
Mike Vitousek
28fe581bac [compiler] Option for preserving calls to useMemo/useCallback
Summary: This adds a compiler option to not drop existing manual memoization and leaving useMemo/useCallback in the generated source. Why do we need this, given that we also have options to validate or ensure that existing memoization is preserved? It's because later diffs on this stack are designed to alter the behavior of the memoization that the compiler emits, in order to detect rules of react violations and debug issues. We don't want to change the behavior of user-level memoization, however, since doing so would be altering the semantics of the user's program in an unacceptable way.

ghstack-source-id: 89dccdec9ccb4306b16e849e9fa2170bb5dd021f
Pull Request resolved: https://github.com/facebook/react/pull/29654
2024-05-31 14:02:13 -07:00
Sebastian Markbåge
8bc81ca90f Create a root task for every Flight response (#29673)
This lets any element created from the server, to bottom out with a
client "owner" which is the creator of the Flight request. This could be
a Server Action being invoked or a router.

This is similar to how a client element bottoms out in the creator of
the root element without an owner. E.g. where the root app element was
created.

Without this, we inherit the task of whatever is currently executing
when we're parsing which can be misleading.

Before:
<img width="507" alt="Screenshot 2024-05-30 at 12 06 57 PM"
src="https://github.com/facebook/react/assets/63648/e234db7e-67f7-404c-958a-5c5500ffdf1f">

After:
<img width="555" alt="Screenshot 2024-05-30 at 4 59 04 PM"
src="https://github.com/facebook/react/assets/63648/8ba6acb4-2ffd-49d4-bd44-08228ad4200e">

The before/after doesn't show much of a difference here but that's just
because our Flight parsing loop is an async, which maybe it shouldn't be
because it can be unnecessarily deep, and it creates a hidden line for
every loop. That's what the `Promise.then` is. If the element is lazily
initialized it's worse because we can end up in an unrelated render task
as the owner - although that's its own problem.
2024-05-31 13:54:10 -04:00
Ricky
6d3110b4d9 Don't allow blank issues (#29691)
We're getting a ton of issues filed using the blank template, for
example these airline support tickets:
https://github.com/facebook/react/issues/29678


I think someone somewhere is linking to our issues with pre-filled
content. This fixes it by forcing a template to be used.
2024-05-31 12:05:32 -04:00
Sebastian Markbåge
63d673c676 Use both displayName and name in forwardRef/memo (#29625)
When defining a displayName on forwardRef/memo we forward that name to
the inner function.

We used to use displayName for this but in #29206 I switched this to use
`"name"`. That's because V8 doesn't use displayName, it only uses the
overridden name in stack traces. This is the only thing covered by our
tests for component stacks.

However, I realized that Safari only uses displayName and not the name.
So this sets both.
2024-05-31 00:22:22 -04:00
Timothy Yung
8fd963a1e5 Fix Missing key Validation in React.Children (#29675)
## Summary

In https://github.com/facebook/react/pull/29088, the validation logic
for `React.Children` inspected whether `mappedChild` — the return value
of the map callback — has a valid `key`. However, this deviates from
existing behavior which only warns if the original `child` is missing a
required `key`.

This fixes false positive `key` validation warnings when using
`React.Children`, by validating the original `child` instead of
`mappedChild`.

This is a more general fix that expands upon my previous fix in
https://github.com/facebook/react/pull/29662.

## How did you test this change?

```
$ yarn test ReactChildren-test.js
```
2024-05-30 18:02:47 -07:00
Josh Wilson
aa3d6c0840 Add react-easy-state to list of known incompatible libraries. (#29661)
Like mobx, this library depends on mutating a Proxied store and breaks reference equality checks.
2024-05-30 16:37:09 -07:00
Sebastian Markbåge
9710853baf [Flight] Try/Catch Eval (#29671)
Follow up to https://github.com/facebook/react/pull/29632.

It's possible for `eval` to throw such as if we're in a CSP environment.
This is non-essential debug information. We can still proceed to create
a fake stack entry. It'll still have the right name. It just won't have
the right line/col number nor source url/source map. It might also be
ignored listed since it's inside Flight.
2024-05-30 15:00:55 -04:00
Sebastian Markbåge
9d4fba0788 [Flight] Eval Fake Server Component Functions to Recreate Native Stacks (#29632)
We have three kinds of stacks that we send in the RSC protocol:

- The stack trace where a replayed `console.log` was called on the
server.
- The JSX callsite that created a Server Component which then later
called another component.
- The JSX callsite that created a Host or Client Component.

These stack frames disappear in native stacks on the client since
they're executed on the server. This evals a fake file which only has
one call in it on the same line/column as the server. Then we call
through these fake modules to "replay" the callstack. We then replay the
`console.log` within this stack, or call `console.createTask` in this
stack to recreate the stack.

The main concern with this approach is the performance. It adds
significant cost to create all these eval:ed functions but it should
eventually balance out.

This doesn't yet apply source maps to these. With source maps it'll be
able to show the server source code when clicking the links.

I don't love how these appear.

- Because we haven't yet initialized the client module we don't have the
name of the client component we're about to render yet which leads to
the `<...>` task name.
- The `(async)` suffix Chrome adds is still a problem.
- The VMxxxx prefix is used to disambiguate which is noisy. Might be
helped by source maps.
- The continuation of the async stacks end up rooted somewhere in the
bootstrapping of the app. This might be ok when the bootstrapping ends
up ignore listed but it's kind of a problem that you can't clear the
async stack.

<img width="927" alt="Screenshot 2024-05-28 at 11 58 56 PM"
src="https://github.com/facebook/react/assets/63648/1c9d32ce-e671-47c8-9d18-9fab3bffabd0">

<img width="431" alt="Screenshot 2024-05-28 at 11 58 07 PM"
src="https://github.com/facebook/react/assets/63648/52f57518-bbed-400e-952d-6650835ac6b6">
<img width="327" alt="Screenshot 2024-05-28 at 11 58 31 PM"
src="https://github.com/facebook/react/assets/63648/d311a639-79a1-457f-9a46-4f3298d07e65">

<img width="817" alt="Screenshot 2024-05-28 at 11 59 12 PM"
src="https://github.com/facebook/react/assets/63648/3aefd356-acf4-4daa-bdbf-b8c8345f6d4b">
2024-05-30 12:00:46 -04:00
Ruslan Lesiutin
fb61a1b515 fix[ReactDebugHooks/find-primitive-index]: remove some assumptions (#29652)
Partially reverts https://github.com/facebook/react/pull/28593.

While rolling out RDT 5.2.0, I've observed some issues on React Native
side: hooks inspection for some complex hook trees, like in
AnimatedView, were broken. After some debugging, I've noticed a
difference between what is in frame's source.

The difference is in the top-most frame, where with V8 it will correctly
pick up the `Type` as `Proxy` in `hookStack`, but for Hermes it will be
`Object`. This means that for React Native this top most frame is
skipped, since sources are identical.

Here I am reverting back to the previous logic, where we check each
frame if its a part of the wrapper, but also updated `isReactWrapper`
function to have an explicit case for `useFormStatus` support.
2024-05-30 16:11:56 +01:00
Timothy Yung
5bd4031226 Revert Build Versions from Content Hash to Commit Hash (#29663)
https://github.com/facebook/react/pull/29236 caused issues for internal
syncs at Meta, because we were computing version numbers using file
hashes (to eliminate "no-op" internal sync commits). The problem is that
since version numbers may not be consistent across synced files (e.g. if
some files have not changed in recent commits), the newly introduced
version mismatch check fails.

There's some more work that needs to be done here to restore the
benefits of file-specific hashing, but for now this simply reverts the
content hash changes from the following PRs:

- https://github.com/facebook/react/pull/28633
(95319ab5af)
- https://github.com/facebook/react/pull/28590
(37676aba76)
- https://github.com/facebook/react/pull/28582
(cb076b593c)
- https://github.com/facebook/react/pull/26734
(5dd90c5623)
- https://github.com/facebook/react/pull/26331
(3cad3a54ed)
2024-05-30 07:26:12 -07:00
Timothy Yung
72644ef2f2 Fix key Warning for Flattened Positional Children (#29662)
## Summary

https://github.com/facebook/react/pull/29088 introduced a regression
triggering this warning when rendering flattened positional children:

> Each child in a list should have a unique "key" prop.

The specific scenario that triggers this is when rendering multiple
positional children (which do not require unique `key` props) after
flattening them with one of the `React.Children` utilities (e.g.
`React.Children.toArray`).

The refactored logic in `React.Children` incorrectly drops the
`element._store.validated` property in `__DEV__`. This diff fixes the
bug and introduces a unit test to prevent future regressions.

## How did you test this change?

```
$ yarn test ReactChildren-test.js
```
2024-05-30 07:25:48 -07:00
Dmytro Rykun
51dd09631a Add tests for ReactNativeAttributePayloadFabric.js (#29608)
## Summary

This PR add tests for `ReactNativeAttributePayloadFabric.js`.

It introduces `ReactNativeAttributePayloadFabric-test.internal.js`,
which is a copy-paste of `ReactNativeAttributePayload-test.internal.js`.

On top of that, there is a bunch of new test cases for the
`ReactNativeAttributePayloadFabric.create` function.

## How did you test this change?

```
yarn test packages/react-native-renderer
```
2024-05-30 10:24:00 +01:00
Niklas Mollenhauer
c2b45ef0dd feat(compiler): Implement constant folding for more binary expressions (#29650)
## Summary

There are already most arithmetic operators in constant propagation:
`+`, `-`, `*`, `/`.
We could add more, namely: `|`, `&`, `^`, `<<`, `>>`, `>>>` and `%`:

Input:
```js
function f() {
  return [
    123.45 | 0,
    123.45 & 0,
    123.45 ^ 0,
    123 << 0,
    123 >> 0,
    123 >>> 0,
    123.45 | 1,
    123.45 & 1,
    123.45 ^ 1,
    123 << 1,
    123 >> 1,
    123 >>> 1,
    3 ** 2,
    3 ** 2.5,
    3.5 ** 2,
    2 ** 3 ** 0.5,
    4 % 2,
    4 % 2.5,
    4 % 3,
    4.5 % 2,
  ];
}
```
Output:
```js
function f() {
  return [
    123, 0, 123, 123, 123, 123, 123, 1, 122, 246, 61, 61, 9,
    15.588457268119896, 12.25, 3.3219970854839125, 0, 1.5, 1, 0.5,
  ];
}
```

Resolves #29649

## How did you test this change?
See tests.

Note:
This PR was done without waiting for approval in #29649, so feel free to
just close it without any comment.
2024-05-29 10:35:19 -07:00
Joe Savona
867edc6576 compiler: ValidateNoRefInRender detects writes of refs
Improves ValidateNoRefAccessInRender, detecting modifications of refs during render.

Fixes #29161

ghstack-source-id: 99078b3cea5b2d9019dbf77ede9c2e4cd9fbfd27
Pull Request resolved: https://github.com/facebook/react/pull/29170
2024-05-29 09:27:44 -07:00
Niklas Mollenhauer
320da67570 feat(compiler): Compiler Logical Negation Constant Propagation (#29623)
## Summary

Resolves #29622

## How did you test this change?
I verified the implementation using the test.

Note:
This PR was done without waiting for approval in #29622, so feel free to
just close it.
2024-05-29 09:17:43 -07:00
Sophie Alpert
38e3b23483 Tweak error message for "Should have a queue" (#29626) 2024-05-29 08:41:10 -07:00
Joe Savona
afb2c39ec3 compiler: fixtures for fast-refresh mode (w todos)
ghstack-source-id: 65dd14fe9b37328bd60fe791b23dde54da10b285
Pull Request resolved: https://github.com/facebook/react/pull/29175
2024-05-29 07:47:05 -07:00
Joe Savona
c272789ce5 compiler: Add todo for getter/setter syntax
We were missing a check that ObjectMethods are not getters or setters. In our experience this is pretty rare within React components and hooks themselves, so let's start with a todo.

Closes #29586

ghstack-source-id: 03c6cce9a9
Pull Request resolved: https://github.com/facebook/react/pull/29592
2024-05-29 07:45:53 -07:00
Joe Savona
49ed6f0740 compiler: Allow global mutation in jsx props
Fixes https://x.com/raibima/status/1794395807216738792

The issue is that if you pass a global-modifying function as prop to JSX, we currently report that it's invalid to modify a global during rendering. The problem is that we don't really know when/if the child component will actually call that function prop. It would be against the rules to call the function during render, but it's totally fine to call it during an event handler or from a useEffect.

Since we don't know at the call-site how the child will use the function, we should allow such calls. In the future we could improve this in a few ways:
* For all functions that modify globals, codegen an assertion or warning into the function that fires if it's called "during render". We'd have to precisely define what "during render" is, but this would at least help developers catch this dynamically.
* Use the type system to distinguish "event/effect" and "render" functions to help developers avoid accidentally mutating globals during render.

ghstack-source-id: 4aba4e6d21
Pull Request resolved: https://github.com/facebook/react/pull/29591
2024-05-29 07:45:53 -07:00
Lauren Tan
e2e12f3351 Update .git-blame-ignore-revs
- Moves the file as it needs to be in root git directory
- Removes now unreachable commits due to repo merge
- Add run prettier commit c998bb1ed4 to ignored revs

ghstack-source-id: d9dfa7099fbc7782fbce600af4caafd405c196cb
Pull Request resolved: https://github.com/facebook/react/pull/29630
2024-05-29 18:12:16 +09:00
Timothy Yung
3b29ed1638 Fix "findNodeHandle inside its render()" False Positive Warning (#29627)
This was missed in https://github.com/facebook/react/pull/29038 when
unifying the "owner" abstractions, causing `findNodeHandle` to warn even
outside of `render()` invocations.
2024-05-28 20:36:41 -07:00
Lauren Tan
b44263addb Bump version to 0.0.0-experimental-31393f7-20240529 2024-05-29 12:01:36 +09:00
Lauren Tan
bd30dc3ae2 Bump version to 0.0.0-experimental-a97cca1-20240529 2024-05-29 12:01:35 +09:00
Lauren Tan
84c47b3d52 Bump version to 0.0.0-experimental-487cb0e-20240529 2024-05-29 12:01:35 +09:00
Lauren Tan
81c3775816 [compiler] Ignore run prettier commit in git blame 2024-05-29 11:47:42 +09:00
Lauren Tan
9d530e94c4 [compiler:babel] Don't read config files when not running as part of
user's pipeline

When the user app has a babel.config file that is missing the compiler,
strange things happen as babel does some strange merging of options from
the user's config and in various callsites like in our eslint rule and
healthcheck script. To minimize odd behavior, we default to not reading
the user's babel.config

Fixes #29135

ghstack-source-id: d6fdc43c5c
Pull Request resolved: https://github.com/facebook/react/pull/29211
2024-05-29 11:46:27 +09:00
Lauren Tan
c998bb1ed4 [compiler] Run prettier, fix snap
After this is merged, I'll add it to .git-blame-ignore-revs. I can't do
it now as the hash will change after ghstack lands this stack.

ghstack-source-id: 054ca869b7
Pull Request resolved: https://github.com/facebook/react/pull/29214
2024-05-29 11:46:27 +09:00
Lauren Tan
61aa159086 [compiler] Fix up prettier
Our prettier setup is all messed up after the merge, so this PR should
fix things

ghstack-source-id: f825460ea6
Pull Request resolved: https://github.com/facebook/react/pull/29213
2024-05-29 11:46:27 +09:00
Niklas Mollenhauer
a9a0106808 feat(compiler): Implement constant string concat propagation (#29621)
## Summary

Resolves #29617

## How did you test this change?
I verified the implementation using the test.
2024-05-28 16:15:46 -07:00
Sebastian Markbåge
18164761b1 [Flight] Check if a return value is a client reference before introspecting (#29611)
This didn't actually fail before but I'm just adding an extra check.

Currently Client References are always "function" proxies so they never
fall into this branch. However, we do in theory support objects as
client references too depending on environment. We have checks
elsewhere. So this just makes that consistent.
2024-05-28 19:07:30 -04:00
Joe Savona
bd4bb32fe7 compiler: fix for calls on builtin jsx/function types
When I added new builtin types for jsx and functions, i forget to add a shape definition. This meant that attempting to accesss a property or method on these types would cause an internal error with an unresolved shape. That wasn't obvious because we rarely call methods on these types.

I confirmed that the new fixtures here fail without the fix.

ghstack-source-id: aa8f8d75a3
Pull Request resolved: https://github.com/facebook/react/pull/29624
2024-05-28 15:17:49 -07:00
Joe Savona
46339720d7 compiler: error on reassigning to const
We currently don't report an error if the code attempts to reassign a const. Our thinking has been that we're not trying to catch all possible mistakes you could make in JavaScript — that's what ESLint, TypeScript, and Flow are for — and that we want to focus on React errors. However, accidentally reassigning a const is easy to catch and doesn't get in the way of other analysis so let's implement it.

Note that React Compiler's ESLint plugin won't report these errors by default, but they will show up in playground.

Fixes #29598

ghstack-source-id: a0af8b9a486d74a8991413322efddc3e3028c755
Pull Request resolved: https://github.com/facebook/react/pull/29619
2024-05-28 13:09:17 -07:00
Hendrik Liebau
97722ca3d4 Export version from react-dom entry with react-server condition (#29596) 2024-05-28 21:47:47 +02:00
Andrew Clark
163122766b Fix: Use action implementation at time of dispatch (#29618)
Fixes the behavior of actions that are queued by useActionState to use
the action function that was current at the time it was dispatched, not
at the time it eventually executes.

The conceptual model is that the action is immediately dispatched, as if
it were sent to a remote server/worker. It's the remote worker that
maintains the queue, not the client.

This is another property of actions makes them more like event handlers
than like reducers.
2024-05-28 15:08:59 -04:00
Jack Pope
2787eebe52 Clean up disableDOMTestUtils (#29610)
`disableDOMTestUtils` and the FB build `ReactTestUtilsFB` allowed us to
finish migrating internal callsites off of ReactTestUtils. Now that
usage is cleaned up, we can remove the flag, build artifact, and test
coverage for the deprecated utility methods.
2024-05-28 14:55:14 -04:00
Andrew Clark
681a4aa810 Throw if React and React DOM versions don't match (#29236)
Throw an error during module initialization if the version of the
"react-dom" package does not match the version of "react".

We used to be more relaxed about this, because the "react" package
changed so infrequently. However, we now have many more features that
rely on an internal protocol between the two packages, including Hooks,
Float, and the compiler runtime. So it's important that both packages
are versioned in lockstep.

Before this change, a version mismatch would often result in a cryptic
internal error with no indication of the root cause.

Instead, we will now compare the versions during module initialization
and immediately throw an error to catch mistakes as early as possible
and provide a clear error message.
2024-05-28 14:06:30 -04:00
Joseph Savona
4ec6a6f714 Repro function expr hoisting (#29615)
Modified version of @mofeiZ's #29232 with CI passing (had to run
prettier)

---------

Co-authored-by: Mofei Zhang <feifei0@meta.com>
2024-05-28 10:06:05 -07:00
Ruslan Lesiutin
6f23540c7d cleanup[react-devtools]: remove unused supportsProfiling flag from store config (#29193)
Looks like this is unused
2024-05-28 11:07:31 +01:00
Sebastian Markbåge
ea6e05912a [Fiber] Enable Native console.createTask Stacks When Available (#29223)
Stacked on #29206 and #29221.

This disables appending owner stacks to console when
`console.createTask` is available in the environment. Instead we rely on
native "async" stacks that end up looking like this with source maps and
ignore list enabled.

<img width="673" alt="Screenshot 2024-05-22 at 4 00 27 PM"
src="https://github.com/facebook/react/assets/63648/5313ed53-b298-4386-8f76-8eb85bdfbbc7">

Unfortunately Chrome requires a string name for each async stack and,
worse, a suffix of `(async)` is automatically added which is very
confusing since it seems like it might be an async component or
something which it is not.

In this case it's not so bad because it's nice to refer to the host
component which otherwise doesn't have a stack frame since it's
internal. However, if there were more owners here there would also be a
`<Counter> (async)` which ends up being kind of duplicative.

If the Chrome DevTools is not open from the start of the app, then
`console.createTask` is disabled and so you lose the stack for those
errors (or those parents if the devtools is opened later). Unlike our
appended ones that are always added. That's unfortunate and likely to be
a bit of a DX issue but it's also nice that it saves on perf in DEV mode
for those cases. Framework dialogs can still surface the stack since we
also track it in user space in parallel.

This currently doesn't track Server Components yet. We need a more
clever hack for that part in a follow up.

I think I probably need to also add something to React DevTools to
disable its stacks for this case too. Since it looks for stacks in the
console.error and adds a stack otherwise. Since we don't add them
anymore from the runtime, the DevTools adds them instead.
2024-05-26 17:55:57 -04:00
Sebastian Markbåge
b078c810c7 [Fiber] Replace setCurrentDebugFiberInDEV with runWithFiberInDEV (#29221)
Stacked on #29044.

To work with `console.createTask(...).run(...)` we need to be able to
run a function in the scope of the task.

The main concern with this, other than general performance, is that it
might add more stack frames on very deep stacks that hit the stack
limit. Such as with the commit phase where we recursively go down the
tree. These callbacks aren't really necessary in the recursive part but
only in the shallow invocation of the commit phase for each tag. So we
could refactor the commit phase so that only the shallow part at each
level is covered this way.
2024-05-25 11:58:40 -04:00
Sebastian Markbåge
d6cfa0f295 [Fiber] Use Owner/JSX Stack When Appending Stacks to Console (#29206)
This one should be fully behind the `enableOwnerStacks` flag.

Instead of printing the parent Component stack all the way to the root,
this now prints the owner stack of every JSX callsite. It also includes
intermediate callsites between the Component and the JSX call so it has
potentially more frames. Mainly it provides the line number of the JSX
callsite. In terms of the number of components is a subset of the parent
component stack so it's less information in that regard. This is usually
better since it's more focused on components that might affect the
output but if it's contextual based on rendering it's still good to have
parent stack. Therefore, I still use the parent stack when printing DOM
nesting warnings but I plan on switching that format to a diff view
format instead (Next.js already reformats the parent stack like this).

__Follow ups__

- Server Components show up in the owner stack for client logs but logs
done by Server Components don't yet get their owner stack printed as
they're replayed. They're also not yet printed in the server logs of the
RSC server.

- Server Component stack frames are formatted as the server and added to
the end but this might be a different format than the browser. E.g. if
server is running V8 and browser is running JSC or vice versa. Ideally
we can reformat them in terms of the client formatting.

- This doesn't yet update Fizz or DevTools. Those will be follow ups.
Fizz still prints parent stacks in the server side logs. The stacks
added to user space `console.error` calls by DevTools still get the
parent stacks instead.

- It also doesn't yet expose these to user space so there's no way to
get them inside `onCaughtError` for example or inside a custom
`console.error` override.

- In another follow up I'll use `console.createTask` instead and
completely remove these stacks if it's available.
2024-05-25 11:58:17 -04:00
Joe Savona
935180c7e0 compiler: only resolve globals and react imports
Updates Environment#getGlobalDeclaration() to only resolve "globals" if they are a true global or an import from react/react-dom. We still keep the logic to resolve hook-like names as custom hooks. Notably, this means that a local `Array` reference won't get confused with our Array global declaration, a local `useState` (or import from something other than React) won't get confused as `React.useState()`, etc.

I tried to write a proper fixture test to test that we react to changes to a custom setState setter function, but I think there may be an issue with snap and how it handles re-renders from effects. I think the tests are good here but open to feedback if we want to go down the rabbit hole of figuring out a proper snap test for this.

ghstack-source-id: 5e9a8f6e0d23659c72a9d041e8d394b83d6e526d
Pull Request resolved: https://github.com/facebook/react/pull/29190
2024-05-24 10:20:26 +01:00
Joe Savona
788ed90b18 compiler: getGlobalDeclaration() takes a NonLocalBinding
No-op refactor to make Environment#getGlobalDeclaration() take a NonLocalBinding instead of just a name. The idea is that in subsequent PRs we can use information about the binding to resolve a type more accurately. For example, we can resolve `Array` differently if its an import or local and not the global Array. Similar for resolving local `useState` differently than the one from React.

ghstack-source-id: c8063e6fb8acdd347a56477d6b06238dd54979b1
Pull Request resolved: https://github.com/facebook/react/pull/29189
2024-05-24 10:20:26 +01:00
Joe Savona
5061f31f4b compiler: distinguish globals/imports/module-locals
We currently use `LoadGlobal` and `StoreGlobal` to represent any read (or write) of a variable defined outside the component or hook that is being compiled. This is mostly fine, but for a lot of things we want to do going forward (resolving types across modules, for example) it helps to understand the actual source of a variable.

This PR is an incremental step in that direction. We continue to use LoadGlobal/StoreGlobal, but LoadGlobal now has a `binding:NonLocalBinding` instead of just the name of the global. The NonLocalBinding type tells us whether it was an import (and which kind, the source module name etc), a module-local binding, or a true global. By keeping the LoadGlobal/StoreGlobal instructions, most code that deals with "anything not declared locally" doesn't have to care about the difference. However, code that _does_ want to know the source of the value can figure it out.

ghstack-source-id: e701d4ebc0fb5681a0197198ac2c2a03b3e8aae9
Pull Request resolved: https://github.com/facebook/react/pull/29188
2024-05-24 10:20:26 +01:00
Andrew Clark
ee5c194930 Fix async batching in React.startTransition (#29226)
Note: Despite the similar-sounding description, this fix is unrelated to
the issue where updates that occur after an `await` in an async action
must also be wrapped in their own `startTransition`, due to the absence
of an AsyncContext mechanism in browsers today.

---

Discovered a flaw in the current implementation of the isomorphic
startTransition implementation (React.startTransition), related to async
actions. It only creates an async scope if something calls setState
within the synchronous part of the action (i.e. before the first
`await`). I had thought this was fine because if there's no update
during this part, then there's nothing that needs to be entangled. I
didn't think this through, though — if there are multiple async updates
interleaved throughout the rest of the action, we need the async scope
to have already been created so that _those_ are batched together.

An even easier way to observe this is to schedule an optimistic update
after an `await` — the optimistic update should not be reverted until
the async action is complete.

To implement, during the reconciler's module initialization, we compose
its startTransition implementation with any previous reconciler's
startTransition that was already initialized. Then, the isomorphic
startTransition is the composition of every
reconciler's startTransition.

```js
function startTransition(fn) {
  return startTransitionDOM(() => {
    return startTransitionART(() => {
      return startTransitionThreeFiber(() => {
        // and so on...
        return fn();
      });
    });
  });
}
```

This is basically how flushSync is implemented, too.
2024-05-23 17:19:09 -04:00
Josh Story
f55d172bcf [Fiber] clarify entry condition for suspensey commit recursion (#29222)
Previously Suspensey recursion would only trigger if the
ShouldSuspendCommit flag was true. However there is a dependence on the
Visibility flag embedded in this logic because these flags share a bit.
To make it clear that the semantics of Suspensey resources require
considering both flags I've added it to the condition even though this
extra or-ing is a noop when the bit is shared
2024-05-23 13:54:51 -07:00
Sebastian Markbåge
84239da896 Move createElement/JSX Warnings into the Renderer (#29088)
This is necessary to simplify the component stack handling to make way
for owner stacks. It also solves some hacks that we used to have but
don't quite make sense. It also solves the problem where things like key
warnings get silenced in RSC because they get deduped. It also surfaces
areas where we were missing key warnings to begin with.

Almost every type of warning is issued from the renderer. React Elements
are really not anything special themselves. They're just lazily invoked
functions and its really the renderer that determines there semantics.

We have three types of warnings that previously fired in
JSX/createElement:

- Fragment props validation.
- Type validation.
- Key warning.

It's nice to be able to do some validation in the JSX/createElement
because it has a more specific stack frame at the callsite. However,
that's the case for every type of component and validation. That's the
whole point of enableOwnerStacks. It's also not sufficient to do it in
JSX/createElement so we also have validation in the renderers too. So
this validation is really just an eager validation but also happens
again later.

The problem with these is that we don't really know what types are valid
until we get to the renderer. Additionally, by placing it in the
isomorphic code it becomes harder to do deduping of warnings in a way
that makes sense for that renderer. It also means we can't reuse logic
for managing stacks etc.

Fragment props validation really should just be part of the renderer
like any other component type. This also matters once we add Fragment
refs and other fragment features. So I moved this into Fiber. However,
since some Fragments don't have Fibers, I do the validation in
ChildFiber instead of beginWork where it would normally happen.

For `type` validation we already do validation when rendering. By
leaving it to the renderer we don't have to hard code an extra list.
This list also varies by context. E.g. class components aren't allowed
in RSC but client references are but we don't have an isomorphic way to
identify client references because they're defined by the host config so
the current logic is flawed anyway. I kept the early validation for now
without the `enableOwnerStacks` since it does provide a nicer stack
frame but with that flag on it'll be handled with nice stacks anyway. I
normalized some of the errors to ensure tests pass.

For `key` validation it's the same principle. The mechanism for the
heuristic is still the same - if it passes statically through a parent
JSX/createElement call then it's considered validated. We already did
print the error later from the renderer so this also disables the early
log in the `enableOwnerStacks` flag.

I also added logging to Fizz so that key warnings can print in SSR logs.

Flight is a bit more complex. For elements that end up on the client we
just pass the `validated` flag along to the client and let the client
renderer print the error once rendered. For server components we log the
error from Flight with the server component as the owner on the stack
which will allow us to print the right stack for context. The factoring
of this is a little tricky because we only want to warn if it's in an
array parent but we want to log the error later to get the right debug
info.

Fiber/Fizz has a similar factoring problem that causes us to create a
fake Fiber for the owner which means the logs won't be associated with
the right place in DevTools.
2024-05-23 12:48:57 -04:00
Joe Savona
5fe8c0b4ec compiler: repro for unmerged scopes due to intermediates
Repro of a case where we should ideally merge consecutive scopes, but where intermediate temporaries prevent the scopes from merging.

We'd need to reorder instructions in order to merge these.

ghstack-source-id: 4f05672604eeb547fc6c26ef99db6572843ac646
Pull Request resolved: https://github.com/facebook/react/pull/29197
2024-05-23 17:44:56 +01:00
Joe Savona
b687fd27b5 compiler: Use types to decide which scopes are eligible for merging
In MergeReactiveScopesThatInvalidateTogether when deciding which scopes were eligible for mergin at all, we looked specifically at the instructions whose lvalue produces the declaration. So if a scope declaration was `t0`, we'd love for the instruction where `t0` was the lvalue and look at the instruction type to decide if it is eligible for merging.

Here, we use the inferred type instead (now that the inferred types support the same set of types of instructions we looked at before). This allows us to find more cases where scopes can be merged.

ghstack-source-id: 0e3e05f24ea0ac6e3c43046bc3e114f906747a04
Pull Request resolved: https://github.com/facebook/react/pull/29157
2024-05-23 17:44:56 +01:00
Joe Savona
890896b2db compiler: Improve merging of memo scopes that invalidate together
Improves merging of consecutive scopes so that we now merge two scopes if the dependencies of the second scope are a subset of the previous scope's output *and* that dependency has a type that will always produce a new value (array, object, jsx, function) if it is re-evaluated.

To make this easier, we extend the set of builtin types to include ones for function expressions and JSX and to infer these types in InferTypes. This allows using the already inferred types in MergeReactiveScopesThatInvalidateTogether.

ghstack-source-id: e9119fc4e02b3665848113d71fdff0c5bac3348a
Pull Request resolved: https://github.com/facebook/react/pull/29156
2024-05-23 17:44:55 +01:00
Joe Savona
82a0a5f88a compiler: fixture for suboptimal jsx sibling memo block merging
React Compiler attempts to merge consecutive reactive scopes in order to reduce overhead. The basic idea is that if two consecutive scopes would always invalidate together then we should merge them. It gets more complicated, though, because values produced by the earlier scope may not always invalidate when their inputs do. For example, a scope that produces `fn(x)` may not invalidate on all changes to `x` if the function is `Math.max(x, 10)` (changing x from 8 to 9 won't change the output).

Previously we were conservative and only merged if either:
* the two scopes had the same dependencies
* the second scope's deps exactly matched the previous scope's outputs.

You can see this in the new fixture, where the second `<button>` gets its own scope, which happens because the preceding scope has an extra output that isn't a dep of the `<button>`'s scope.

ghstack-source-id: d869c8d4df5aa4105bbdae01b5dd7f106145b351
Pull Request resolved: https://github.com/facebook/react/pull/29155
2024-05-23 17:44:55 +01:00
Sebastian Markbåge
2e540e22b2 Set the current fiber to the source of the error during error reporting (#29044)
This lets us expose the component stack to the error reporting that
happens here as `console.error` patching. Now if you just call
`console.error` in the error handlers it'll get the component stack
added to the end by React DevTools.

However, unfortunately this happens a little too late so the Fiber will
be disconnected with its `.return` pointer set to null already. So it'll
be too late to extract a parent component stack from but you can at
least get the stack from source to error boundary. To work around this I
manually add the parent component stack in our default handlers when
owner stacks are off. We could potentially fix this but you can also
just include it yourself if you're calling `console.error` and it's not
a problem for owner stacks.

This is not a problem for owner stacks because we'll still have those
and so for those just calling `console.error` just works. However, the
main feature is that by letting React add them, we can switch to using
native error stacks when available.
2024-05-23 12:39:52 -04:00
Sebastian Markbåge
2e3e6a9b1c Unify ReactFiberCurrentOwner and ReactCurrentFiber (#29038)
We previously had two slightly different concepts for "current fiber".

There's the "owner" which is set inside of class components in prod if
string refs are enabled, and sometimes inside function components in DEV
but not other contexts.

Then we have the "current fiber" which is only set in DEV for various
warnings but is enabled in a bunch of contexts.

This unifies them into a single "current fiber".

The concept of string refs shouldn't really exist so this should really
be a DEV only concept. In the meantime, this sets the current fiber
inside class render only in prod, however, in DEV it's now enabled in
more contexts which can affect the string refs. That was already the
case that a string ref in a Function component was only connecting to
the owner in prod. Any string ref associated with any non-class won't
work regardless so that's not an issue. The practical change here is
that an element with a string ref created inside a life-cycle associated
with a class will work in DEV but not in prod. Since we need the current
fiber to be available in more contexts in DEV for the debugging
purposes. That wouldn't affect any old code since it would have a broken
ref anyway. New code shouldn't use string refs anyway.

The other implication is that "owner" doesn't necessarily mean
"rendering" since we need the "owner" to track other debug information
like stacks - in other contexts like useEffect, life cycles, etc.
Internally we have a separate `isRendering` flag that actually means
we're rendering but even that is a very overloaded concept. So anything
that uses "owner" to imply rendering might be wrong with this change.

This is a first step to a larger refactor for tracking current rendering
information.

---------

Co-authored-by: Sebastian Silbermann <silbermann.sebastian@gmail.com>
2024-05-23 12:25:23 -04:00
Henry Q. Dineen
4c2e457c7c compiler: Handle TSNonNullAssertion expressions (#29218)
## Summary

We ran React compiler against part of our codebase and collected
compiler errors. One of the more common non-actionable errors is caused
by usage of the `!` TypeScript non-null assertion operation:

```
(BuildHIR::lowerExpression) Handle TSNonNullExpression expressions
```

It seems like React Compiler _should_ be able to support this by just
ignoring the syntax and using the underlying expression. I'm sure a lot
of our non-null assertion usage should not exist and I understand if
React Compiler does not want to support this syntax. It wasn't obvious
to me if this omission was intentional or if there are future plans to
use `TSNonNullExpression` as part of the compiler's analysis. If there
are no future plans it seems like just ignoring it should be fine.

## How did you test this change?

```sh
❯ yarn snap --filter
yarn run v1.17.3
$ yarn workspace babel-plugin-react-compiler run snap --filter
$ node ../snap/dist/main.js --filter
 PASS  non-null-assertion
1 Tests, 1 Passed, 0 Failed
```
2024-05-22 23:42:06 +01:00
Sebastian Silbermann
f994737d14 [Flight] Allow temporary references in decodeReplyFromBusboy (#29219) 2024-05-22 21:15:14 +02:00
Sebastian Silbermann
3ac551e855 Dim console calls on additional Effect invocations due to StrictMode (#29007) 2024-05-22 11:39:54 +02:00
Josh Story
81c5ff2e04 [Flight Reply] retain listeners when resolving models with existing listeners (#29207)
In #29201 a fix was made to ensure we don't "forget" about some
listeners when handling cyclic chunks.
In #29204 another fix was made for a special case when the chunk already
has listeners before it first resolves.

This implements the followup fix for Flight Reply which was originally
missed in #29204

Co-authored-by: Janka Uryga <lolzatu2@gmail.com>
2024-05-21 16:16:20 -07:00
Josh Story
217b2ccf16 [Fiber] render boundary in fallback if it contains a new stylesheet during sync update (#28965)
Updates Suspensey instances and resources to preload even during urgent
updates and to potentially suspend.

The current implementation is unchanged for transitions but for sync
updates if there is a suspense boundary above the resource/instance it
will be rendered in fallback mode instead.

Note: This behavior is not what we want for images once we make them
suspense enabled. We will need to have forked behavior here to
distinguish between stylesheets which should never commit when not
loaded and images which should commit after a small delay
2024-05-21 16:03:46 -07:00
Lauren Tan
0f58454803 [compiler:playground] Update babel.config.js
Use new defaults

ghstack-source-id: d2da1df69db9f1b01578af6d52ea4a2613769f9b
Pull Request resolved: https://github.com/facebook/react/pull/29205
2024-05-21 18:12:30 -04:00
Lauren Tan
b759b71ead [compiler:playground] JS tab is expanded by default
When using the playground you typically want to see what it outputs, so
let's make the JS tab expanded by default.

ghstack-source-id: 721bc4c381c50db008058b31e1f976e92eab8548
Pull Request resolved: https://github.com/facebook/react/pull/29203
2024-05-21 18:12:30 -04:00
Janka Uryga
9b3f909cc1 [Flight] don't overwrite existing chunk listeners in 'wakeChunkIfInitialized' (#29204)
Follow up to https://github.com/facebook/react/pull/29201. If a chunk
had listeners attached already (e.g. because `.then` was called on the
chunk returned from `createFromReadableStream`),
`wakeChunkIfInitialized` would overwrite any listeners added during
chunk initialization. This caused cyclic [path
references](https://github.com/facebook/react/pull/28996) within that
chunk to never resolve. Fixed by merging the two arrays of listeners.
2024-05-21 14:04:46 -07:00
Lauren Tan
0a0a5c02f1 [compiler:playground] Wait for build before running playground
Explicitly waits for the build to finish since the playground requires
them to run

ghstack-source-id: 0bd7d5272d7fa09dc3a140b82a962dc4a3ae585b
Pull Request resolved: https://github.com/facebook/react/pull/29180
2024-05-21 15:44:04 -04:00
Sebastian Markbåge
8f3c0525f9 [Flight / Flight Reply] Don't clear pending listeners when entering blocked state (#29201)
Fixes #29200

The cyclic state might have added listeners that will still need to be
invoked. This happens if we have a cyclic reference AND end up blocked.

We have already cleared these before entering the parsing when we enter
the CYCLIC state so we they already have the right type. If listeners
are added during this phase they should carry over to the blocked state.

---------

Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>
2024-05-21 14:12:20 -04:00
Sebastian Silbermann
5cc9f69a74 Fix assertConsoleErrorDev on message mismatch with withoutStack: true (#29198)
## Summary

```js
assertConsoleErrorDev([
  ['Hello', {withoutStack: true}]
])
```

now errors with a helpful diff message if the message mismatched. See
first commit for previous behavior.

## How did you test this change?

- `yarn test --watch
packages/internal-test-utils/__tests__/ReactInternalTestUtils-test.js`
2024-05-21 12:48:41 -04:00
Timothy Yung
7621466b1b Enable disableStringRefs and enableRefAsProp for React Native (Meta) (#29177)
## Summary

Enables the `disableStringRefs` and `enableRefAsProp` feature flags for
React Native (Meta).

## How did you test this change?

```
$ yarn test
$ yarn flow fabric
```
2024-05-21 11:19:12 +01:00
Sebastian Silbermann
55dd0b1d0e Stop using Scheduler.log to test double invocations (#29008) 2024-05-21 00:30:45 +02:00
Sebastian Silbermann
bf046e8653 React DOM: Treat toggle and beforetoggle as discrete events (#29176) 2024-05-20 23:44:51 +02:00
Sebastian Silbermann
6f90365128 React DOM: Add support for Popover API (#27981) 2024-05-20 22:01:39 +02:00
Abishek Ilango
d3ce0d3ea9 feat(compiler-healthcheck): Support strict mode check for nextjs apps (#29167)
## Summary

Closes #29130 

## How did you test this change?

Run the healthcheck in the compiler playground and the nodejs.org repo
for the next config with a `.mjs` extension. Sanity with Vite React
template.

Signed-off-by: abizek <abishekilango@protonmail.com>
2024-05-20 08:55:35 -07:00
Jack Youstra
57fbe3ba37 [Compiler playground] bold changed passes (#29159)
<!--
  Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.

Before submitting a pull request, please make sure the following is
done:

1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
  2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
  9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
  10. If you haven't already, complete the CLA.

Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->

## Summary

<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->

In the playground, it's hard to see at a glance what compiler passes are
involved in introducing changes.
This PR bolds every pass that introduces a change.

## How did you test this change?

<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
  If you leave this empty, your PR will very likely be closed.
-->

Before:
<img width="1728" alt="image"
src="https://github.com/facebook/react/assets/5144292/803ca786-0726-4456-b0db-520dc90a6771">

After:
<img width="1728" alt="image"
src="https://github.com/facebook/react/assets/5144292/38885644-00e9-4065-9c44-db533000d13a">
2024-05-20 08:05:48 -07:00
Ruslan Lesiutin
d14ce51327 refactor[react-devtools]: rewrite context menus (#29049)
## Summary
- While rolling out RDT 5.2.0 on Fusebox, we've discovered that context
menus don't work well with this environment. The reason for it is the
context menu state implementation - in a global context we define a map
of registered context menus, basically what is shown at the moment (see
deleted Contexts.js file). These maps are not invalidated on each
re-initialization of DevTools frontend, since the bundle
(react-devtools-fusebox module) is not reloaded, and this results into
RDT throwing an error that some context menu was already registered.
- We should not keep such data in a global state, since there is no
guarantee that this will be invalidated with each re-initialization of
DevTools (like with browser extension, for example).
- The new implementation is based on a `ContextMenuContainer` component,
which will add all required `contextmenu` event listeners to the
anchor-element. This component will also receive a list of `items` that
will be displayed in the shown context menu.
- The `ContextMenuContainer` component is also using
`useImperativeHandle` hook to extend the instance of the component, so
context menus can be managed imperatively via `ref`:
`contextMenu.current?.hide()`, for example.
- **Changed**: The option for copying value to clipboard is now hidden
for functions. The reasons for it are:
- It is broken in the current implementation, because we call
`JSON.stringify` on the value, see
`packages/react-devtools-shared/src/backend/utils.js`.
- I don't see any reasonable value in doing this for the user, since `Go
to definition` option is available and you can inspect the real code and
then copy it.
- We already filter out fields from objects, if their value is a
function, because the whole object is passed to `JSON.stringify`.

## How did you test this change?
### Works with element props and hooks:
- All context menu items work reliably for props items
- All context menu items work reliably or hooks items


https://github.com/facebook/react/assets/28902667/5e2d58b0-92fa-4624-ad1e-2bbd7f12678f

### Works with timeline profiler:
- All context menu items work reliably: copying, zooming, ...
- Context menu automatically closes on the scroll event


https://github.com/facebook/react/assets/28902667/de744cd0-372a-402a-9fa0-743857048d24

### Works with Fusebox:
- Produces no errors
- Copy to clipboard context menu item works reliably


https://github.com/facebook/react/assets/28902667/0288f5bf-0d44-435c-8842-6b57bc8a7a24
2024-05-20 15:12:21 +01:00
dependabot[bot]
c325aec1ee Bump word-wrap from 1.2.3 to 1.2.5 in /compiler (#29064)
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3
to 1.2.5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/jonschlinkert/word-wrap/releases">word-wrap's
releases</a>.</em></p>
<blockquote>
<h2>1.2.5</h2>
<p><strong>Changes</strong>:</p>
<p>Reverts default value for <code>options.indent</code> to two spaces
<code>' '</code>.</p>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/jonschlinkert/word-wrap/compare/1.2.4...1.2.5">https://github.com/jonschlinkert/word-wrap/compare/1.2.4...1.2.5</a></p>
<h2>1.2.4</h2>
<h2>What's Changed</h2>
<ul>
<li>Remove default indent by <a
href="https://github.com/mohd-akram"><code>@​mohd-akram</code></a> in <a
href="https://redirect.github.com/jonschlinkert/word-wrap/pull/24">jonschlinkert/word-wrap#24</a></li>
<li>🔒fix: CVE 2023 26115 (2) by <a
href="https://github.com/OlafConijn"><code>@​OlafConijn</code></a> in <a
href="https://redirect.github.com/jonschlinkert/word-wrap/pull/41">jonschlinkert/word-wrap#41</a></li>
<li>🔒 fix: CVE-2023-26115 by <a
href="https://github.com/aashutoshrathi"><code>@​aashutoshrathi</code></a>
in <a
href="https://redirect.github.com/jonschlinkert/word-wrap/pull/33">jonschlinkert/word-wrap#33</a></li>
<li>chore: publish workflow by <a
href="https://github.com/OlafConijn"><code>@​OlafConijn</code></a> in <a
href="https://redirect.github.com/jonschlinkert/word-wrap/pull/42">jonschlinkert/word-wrap#42</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/mohd-akram"><code>@​mohd-akram</code></a> made
their first contribution in <a
href="https://redirect.github.com/jonschlinkert/word-wrap/pull/24">jonschlinkert/word-wrap#24</a></li>
<li><a
href="https://github.com/OlafConijn"><code>@​OlafConijn</code></a> made
their first contribution in <a
href="https://redirect.github.com/jonschlinkert/word-wrap/pull/41">jonschlinkert/word-wrap#41</a></li>
<li><a
href="https://github.com/aashutoshrathi"><code>@​aashutoshrathi</code></a>
made their first contribution in <a
href="https://redirect.github.com/jonschlinkert/word-wrap/pull/33">jonschlinkert/word-wrap#33</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4">https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="207044ebda"><code>207044e</code></a>
1.2.5</li>
<li><a
href="9894315485"><code>9894315</code></a>
revert default indent</li>
<li><a
href="f64b188c72"><code>f64b188</code></a>
run verb to generate README</li>
<li><a
href="03ea08256b"><code>03ea082</code></a>
Merge pull request <a
href="https://redirect.github.com/jonschlinkert/word-wrap/issues/42">#42</a>
from jonschlinkert/chore/publish-workflow</li>
<li><a
href="420dce9a24"><code>420dce9</code></a>
Merge pull request <a
href="https://redirect.github.com/jonschlinkert/word-wrap/issues/41">#41</a>
from jonschlinkert/fix/CVE-2023-26115-2</li>
<li><a
href="bfa694edf5"><code>bfa694e</code></a>
Update .github/workflows/publish.yml</li>
<li><a
href="ace0b3c78f"><code>ace0b3c</code></a>
chore: bump version to 1.2.4</li>
<li><a
href="6fd7275946"><code>6fd7275</code></a>
chore: add publish workflow</li>
<li><a
href="30d6daf60f"><code>30d6daf</code></a>
chore: fix test</li>
<li><a
href="655929cabe"><code>655929c</code></a>
chore: remove package-lock</li>
<li>Additional commits viewable in <a
href="https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.5">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=word-wrap&package-manager=npm_and_yarn&previous-version=1.2.3&new-version=1.2.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/facebook/react/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-20 12:03:55 +01:00
dependabot[bot]
ba51ebe03a Bump tough-cookie from 4.1.2 to 4.1.4 in /compiler (#29065)
Bumps [tough-cookie](https://github.com/salesforce/tough-cookie) from
4.1.2 to 4.1.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/salesforce/tough-cookie/releases">tough-cookie's
releases</a>.</em></p>
<blockquote>
<h2>v4.1.4</h2>
<p><a
href="https://www.npmjs.com/package/tough-cookie/v/4.1.4">https://www.npmjs.com/package/tough-cookie/v/4.1.4</a></p>
<h2>What's Changed</h2>
<ul>
<li>Add local alias for <code>toString</code> by <a
href="https://github.com/corvidism"><code>@​corvidism</code></a> in <a
href="https://redirect.github.com/salesforce/tough-cookie/pull/409">salesforce/tough-cookie#409</a></li>
<li>Fix incorrect string validation for URL by <a
href="https://github.com/coditva"><code>@​coditva</code></a> in <a
href="https://redirect.github.com/salesforce/tough-cookie/pull/261">salesforce/tough-cookie#261</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/corvidism"><code>@​corvidism</code></a>
made their first contribution in <a
href="https://redirect.github.com/salesforce/tough-cookie/pull/409">salesforce/tough-cookie#409</a></li>
<li><a href="https://github.com/coditva"><code>@​coditva</code></a> made
their first contribution in <a
href="https://redirect.github.com/salesforce/tough-cookie/pull/261">salesforce/tough-cookie#261</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/salesforce/tough-cookie/compare/v4.1.3...v4.1.4">https://github.com/salesforce/tough-cookie/compare/v4.1.3...v4.1.4</a></p>
<h2>4.1.3</h2>
<p>Security fix for Prototype Pollution discovery in <a
href="https://redirect.github.com/salesforce/tough-cookie/issues/282">#282</a>.
This is a minor release, although output from the <code>inspect</code>
utility is affected by this change, we felt this change was important
enough to be pushed into the next patch.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="cacbc37936"><code>cacbc37</code></a>
Bump version to 4.1.4</li>
<li><a
href="a48fb3a6ba"><code>a48fb3a</code></a>
Add tests for url validation</li>
<li><a
href="50e69bf937"><code>50e69bf</code></a>
Merge pull request <a
href="https://redirect.github.com/salesforce/tough-cookie/issues/261">#261</a>
from postmanlabs/fix/url-string-validation</li>
<li><a
href="1253d58825"><code>1253d58</code></a>
Merge pull request <a
href="https://redirect.github.com/salesforce/tough-cookie/issues/409">#409</a>
from corvidism/validators-to-string</li>
<li><a
href="238367e2f1"><code>238367e</code></a>
Add local alias for <code>toString</code></li>
<li><a
href="4ff4d29f6c"><code>4ff4d29</code></a>
4.1.3 release preparation, update the package and lib/version to 4.1.3.
(<a
href="https://redirect.github.com/salesforce/tough-cookie/issues/284">#284</a>)</li>
<li><a
href="12d474791b"><code>12d4747</code></a>
Prevent prototype pollution in cookie memstore (<a
href="https://redirect.github.com/salesforce/tough-cookie/issues/283">#283</a>)</li>
<li><a
href="f06b72d1d4"><code>f06b72d</code></a>
Fix documentation for store.findCookies, missing allowSpecialUseDomain
proper...</li>
<li><a
href="cf6debd15f"><code>cf6debd</code></a>
Fix incorrect string validation for URL</li>
<li>See full diff in <a
href="https://github.com/salesforce/tough-cookie/compare/v4.1.2...v4.1.4">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a
href="https://www.npmjs.com/~ccasey">ccasey</a>, a new releaser for
tough-cookie since your current version.</p>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tough-cookie&package-manager=npm_and_yarn&previous-version=4.1.2&new-version=4.1.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/facebook/react/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-20 12:03:29 +01:00
dependabot[bot]
9abea0cb7c Bump rustix from 0.37.22 to 0.37.27 in /compiler (#29173)
Bumps [rustix](https://github.com/bytecodealliance/rustix) from 0.37.22
to 0.37.27.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="b38dc51262"><code>b38dc51</code></a>
chore: Release rustix version 0.37.27</li>
<li><a
href="a2d9c8ee1a"><code>a2d9c8e</code></a>
Fix p{read,write}v{,v2}'s encoding of the offset argument on Linux. (<a
href="https://redirect.github.com/bytecodealliance/rustix/issues/896">#896</a>)
(#...</li>
<li><a
href="dce2777622"><code>dce2777</code></a>
chore: Release rustix version 0.37.26</li>
<li><a
href="06dbe83c60"><code>06dbe83</code></a>
Fix <code>sendmsg_unix</code>'s address encoding. (<a
href="https://redirect.github.com/bytecodealliance/rustix/issues/885">#885</a>)
(<a
href="https://redirect.github.com/bytecodealliance/rustix/issues/886">#886</a>)</li>
<li><a
href="00b84d6aac"><code>00b84d6</code></a>
chore: Release rustix version 0.37.25</li>
<li><a
href="cad15a7076"><code>cad15a7</code></a>
Fixes for <code>Dir</code> on macOS, FreeBSD, and WASI.</li>
<li><a
href="df3c3a192c"><code>df3c3a1</code></a>
Merge pull request from GHSA-c827-hfw6-qwvm</li>
<li><a
href="b78aeff1a2"><code>b78aeff</code></a>
chore: Release rustix version 0.37.24</li>
<li><a
href="c0c3f01d7c"><code>c0c3f01</code></a>
Add GNU/Hurd support (<a
href="https://redirect.github.com/bytecodealliance/rustix/issues/852">#852</a>)</li>
<li><a
href="f416b6b27b"><code>f416b6b</code></a>
Fix the <code>test_ttyname_ok</code> test when /dev/stdin is
inaccessable. (<a
href="https://redirect.github.com/bytecodealliance/rustix/issues/821">#821</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/bytecodealliance/rustix/compare/v0.37.22...v0.37.27">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rustix&package-manager=cargo&previous-version=0.37.22&new-version=0.37.27)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/facebook/react/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-20 12:02:35 +01:00
dependabot[bot]
8287cb9290 Bump postcss from 8.4.24 to 8.4.31 in /compiler (#29063)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.24 to
8.4.31.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/postcss/postcss/releases">postcss's
releases</a>.</em></p>
<blockquote>
<h2>8.4.31</h2>
<ul>
<li>Fixed <code>\r</code> parsing to fix CVE-2023-44270.</li>
</ul>
<h2>8.4.30</h2>
<ul>
<li>Improved source map performance (by <a
href="https://github.com/romainmenke"><code>@​romainmenke</code></a>).</li>
</ul>
<h2>8.4.29</h2>
<ul>
<li>Fixed <code>Node#source.offset</code> (by <a
href="https://github.com/idoros"><code>@​idoros</code></a>).</li>
<li>Fixed docs (by <a
href="https://github.com/coliff"><code>@​coliff</code></a>).</li>
</ul>
<h2>8.4.28</h2>
<ul>
<li>Fixed <code>Root.source.end</code> for better source map (by <a
href="https://github.com/romainmenke"><code>@​romainmenke</code></a>).</li>
<li>Fixed <code>Result.root</code> types when <code>process()</code> has
no parser.</li>
</ul>
<h2>8.4.27</h2>
<ul>
<li>Fixed <code>Container</code> clone methods types.</li>
</ul>
<h2>8.4.26</h2>
<ul>
<li>Fixed clone methods types.</li>
</ul>
<h2>8.4.25</h2>
<ul>
<li>Improve stringify performance (by <a
href="https://github.com/romainmenke"><code>@​romainmenke</code></a>).</li>
<li>Fixed docs (by <a
href="https://github.com/vikaskaliramna07"><code>@​vikaskaliramna07</code></a>).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/postcss/postcss/blob/main/CHANGELOG.md">postcss's
changelog</a>.</em></p>
<blockquote>
<h2>8.4.31</h2>
<ul>
<li>Fixed <code>\r</code> parsing to fix CVE-2023-44270.</li>
</ul>
<h2>8.4.30</h2>
<ul>
<li>Improved source map performance (by Romain Menke).</li>
</ul>
<h2>8.4.29</h2>
<ul>
<li>Fixed <code>Node#source.offset</code> (by Ido Rosenthal).</li>
<li>Fixed docs (by Christian Oliff).</li>
</ul>
<h2>8.4.28</h2>
<ul>
<li>Fixed <code>Root.source.end</code> for better source map (by Romain
Menke).</li>
<li>Fixed <code>Result.root</code> types when <code>process()</code> has
no parser.</li>
</ul>
<h2>8.4.27</h2>
<ul>
<li>Fixed <code>Container</code> clone methods types.</li>
</ul>
<h2>8.4.26</h2>
<ul>
<li>Fixed clone methods types.</li>
</ul>
<h2>8.4.25</h2>
<ul>
<li>Improve stringify performance (by Romain Menke).</li>
<li>Fixed docs (by <a
href="https://github.com/vikaskaliramna07"><code>@​vikaskaliramna07</code></a>).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="90208de880"><code>90208de</code></a>
Release 8.4.31 version</li>
<li><a
href="58cc860b4c"><code>58cc860</code></a>
Fix carrier return parsing</li>
<li><a
href="4fff8e4cdc"><code>4fff8e4</code></a>
Improve pnpm test output</li>
<li><a
href="cd43ed1232"><code>cd43ed1</code></a>
Update dependencies</li>
<li><a
href="caa916bdcb"><code>caa916b</code></a>
Update dependencies</li>
<li><a
href="8972f76923"><code>8972f76</code></a>
Typo</li>
<li><a
href="11a5286f78"><code>11a5286</code></a>
Typo</li>
<li><a
href="45c5501777"><code>45c5501</code></a>
Release 8.4.30 version</li>
<li><a
href="bc3c341f58"><code>bc3c341</code></a>
Update linter</li>
<li><a
href="b2be58a2eb"><code>b2be58a</code></a>
Merge pull request <a
href="https://redirect.github.com/postcss/postcss/issues/1881">#1881</a>
from romainmenke/improve-sourcemap-performance--phil...</li>
<li>Additional commits viewable in <a
href="https://github.com/postcss/postcss/compare/8.4.24...8.4.31">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=postcss&package-manager=npm_and_yarn&previous-version=8.4.24&new-version=8.4.31)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/facebook/react/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-20 11:47:51 +01:00
Herrington Darkholme
68de7d0535 [compiler:chore] fix SSA pdf reference link (#29162)
## Summary

This PR fixes the deadlink in the SSA comment. Previously the attached
link is down now.

## How did you test this change?

1. I can confirm the PDF in the new link is the same as the old now.
Reference: https://www.recompiled.dev/blog/ssa/
2. I can confirm the old link is down, and it is [not just
me](https://downforeveryoneorjustme.com/pp.info.uni-karlsruhe.de?proto=https)

<img width="580" alt="image"
src="https://github.com/facebook/react/assets/2883231/eeca5eda-6f61-4ac4-a113-1b04370533d9">

---------

Co-authored-by: Jan Kassens <jkassens@meta.com>
2024-05-20 11:10:10 +01:00
Joe Savona
cca15a2139 compiler: fix accidental propagation of function effects from StartMemoize/FinishMemoize
By default, React Compiler will skip compilation if it cannot preserve existing memoization. Ie, if the code has an existing `useMemo()` or `useCallback()` and the compiler cannot determine that it is safe to keep that memoization — or do even better — then we'll leave the code alone. The actual compilation doesn't use any hints from existing memo calls, this is purely to check and avoid regressing any specific memoization that developers may have already applied.

However, we were accidentally reporting some false-positive _validation_ errors due to the StartMemoize and FinishMemoize instructions that we emit to track where the memoization was in the source code. This is now fixed.

Fixes #29131
Fixes #29132

ghstack-source-id: 9f6b8dbc5074ccc96e6073cf11c4920b5375faf6
Pull Request resolved: https://github.com/facebook/react/pull/29154
2024-05-19 19:12:53 -07:00
Joe Savona
85923690e9 compiler: Improve ValidateNoRefAccessInRender to ignore access in effects
Improves ValidateNoRefAccessInRender (still disabled by default) to properly ignore ref access within effects. This includes allowing ref access within functions that are only transitively called from an effect.

While I was here I also added some extra test fixtures for allowing global mutation in effects.

ghstack-source-id: fb6352a1788b7bdbebb40d5b844b711ef87d6771
Pull Request resolved: https://github.com/facebook/react/pull/29151
2024-05-19 19:12:53 -07:00
Januda Bethmin
f74c5ccf94 feat(hyperlink): added a hyperlink to Node in README.md file (#28940)
As a fellow beginner to React, I didn't even know React runs on top of
Node when I started.

So, some beginners might get confused about what is Node and how to find
details about it or how to download it.

So, I thought to add a hyperlink to Node replacing the word Node in the
README.md file. I think this might be a valuable contribution.

- Januda
2024-05-18 23:17:52 -04:00
NickeSteenen
ccb06b0585 Add hyperlink for Good First Issues header (#29103)
## Summary

The "Good First Issues" header in the README was missing a hyperlink
where the other similar headlines had one.

<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->

## How did you test this change?

N/A

<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
  If you leave this empty, your PR will very likely be closed.
-->
2024-05-18 23:17:28 -04:00
Risto Keravuori
ff60b11b1c Add packageManager fields for Corepack compat (#29114) 2024-05-18 10:21:41 -04:00
Lauren Tan
597ad74e38 Bump version to 0.0.0-experimental-f978439-20240517 2024-05-17 15:17:05 -07:00
Lauren Tan
630c6fb89b Bump version to 0.0.0-experimental-c8b3f72-20240517 2024-05-17 15:17:05 -07:00
Lauren Tan
c2a64b0713 Bump version to 0.0.0-experimental-592953e-20240517 2024-05-17 15:17:04 -07:00
Lauren Tan
1052a5f2dd [compiler:publish] Don't check branch in debug mode
ghstack-source-id: 465f5a358096c55310ebf5a0beef581646ab553e
Pull Request resolved: https://github.com/facebook/react/pull/29153
2024-05-17 15:11:17 -07:00
Lauren Tan
38d961f8ff [compiler:publish] Bump time to reconsider to 3s
ghstack-source-id: afc8976a8956db79ed824f30cf575d3f5cf93cfe
Pull Request resolved: https://github.com/facebook/react/pull/29150
2024-05-17 15:07:04 -07:00
Lauren Tan
b195a947de [compiler:publish] Prompt for OTP
Makes running the script a little more ergonomic by prompting for OTP
upfront.

ghstack-source-id: e9967bfde1ab01ff9417a848154743ae1926318d
Pull Request resolved: https://github.com/facebook/react/pull/29149
2024-05-17 15:07:04 -07:00
Lauren Tan
4fbc3a37f5 [compiler:publish] Don't hash node_modules
ghstack-source-id: 4ac2ecc8654b1a8c149ab9d00c06cb4c401473da
Pull Request resolved: https://github.com/facebook/react/pull/29147
2024-05-17 15:07:04 -07:00
Lauren Tan
e7673e496c [compiler:publish] Rename publish script
ghstack-source-id: 1cccc91b5d0e69240b2f4f539c60eab0f156e15f
Pull Request resolved: https://github.com/facebook/react/pull/29148
2024-05-17 15:07:04 -07:00
Joe Savona
5a12a0d330 compiler: workaround babel issue with html entity escaping
Babel doesn't seem to properly preserve escaping of HTML entities when emitting JSX text children, so this commit works around the issue by emitting a JsxExpressionContainer for JSX children that contain ">", "<", or "&" characters.

Closes #29100

ghstack-source-id: 2d0622397cc067c6336f3635073e07daef854084
Pull Request resolved: https://github.com/facebook/react/pull/29143
2024-05-17 14:36:28 -07:00
Abishek Ilango
541e3c516d [compiler:playground] Resizable tabs (#29133)
## Summary

Every tab wraps the text around but there is no way to resize it. It was
also hard to use the source map tab. It doesn't occupy the full height
nor is the tab resizable. So I made all the tabs resizable.
> Also,
> * make the source map tab occupy full height
> * make it a teeny tiny bit easier to work with the compiler playground
(especially source map)

## How did you test this change?


https://github.com/facebook/react/assets/91976421/cdec30e8-cadb-4958-8786-31c54ea83bd6

Signed-off-by: abizek <abishekilango@protonmail.com>
2024-05-17 15:31:24 -04:00
mofeiZ
1d6eebfb7f [Compiler][script] Dedupe error report counts before reporting in healthcheck (#29085)
Certain compiler passes currently may collect a few error events before
reporting (see
https://github.com/facebook/react/blob/main/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts#L101-L107)
2024-05-17 11:58:15 -07:00
Joe Savona
6400172315 compiler: update comment on memo cache import code
ghstack-source-id: b6c98c78854c9125bef504731515b9854c01960d
Pull Request resolved: https://github.com/facebook/react/pull/29142
2024-05-17 11:37:16 -07:00
Lauren Tan
ecddf3cb07 Add issue template for React Compiler
Adds a GitHub issue template form so we can automatically categorize
issues and get more information upfront. I mostly referenced the
DevTools bug report template and made some tweaks.

ghstack-source-id: 5bfc728a625f367932fc21263e82681079d3ac65
Pull Request resolved: https://github.com/facebook/react/pull/29140
2024-05-17 11:33:53 -07:00
Joe Savona
be6712f72c compiler: Workaround Babel bug with unicode in jsx string attrs
Workaround for a bug in older versions of Babel, where strings with unicode are incorrectly escaped when emitted as JSX attributes, causing double-escaping by later processing.

Closes #29120
Closes #29124

ghstack-source-id: 065440d4fb97e164beb8a8f15f252f372a59c5a0
Pull Request resolved: https://github.com/facebook/react/pull/29141
2024-05-17 10:59:04 -07:00
Sebastian Markbåge
af3a55e67f Lazily freeze in case anything in the currently initializing chunk is blocked (#29139)
Fixed #29129.

---------

Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>
2024-05-17 13:58:42 -04:00
Joe Savona
477a3d1ef5 [compiler] Todo for for-await loops
ghstack-source-id: 13f11edfbef2af2e2a9ab876c18b0a4013a3eae0
Pull Request resolved: https://github.com/facebook/react/pull/29118
2024-05-17 10:43:14 -07:00
Lauren Tan
3f1436cca1 [compiler:playground] Fix broken builds
Now that the compiler is public, the `*` version was grabbing the latest
version of the compiler off of npm and was resolving to my very first
push to npm (an empty package containing only a single package.json).
This was breaking the playground as it would attempt to load the
compiler but then crash the babel pipeline due to the node module not
being found.

ghstack-source-id: 695fd9caac
Pull Request resolved: https://github.com/facebook/react/pull/29122
2024-05-16 23:25:57 -07:00
Joseph Savona
5ab54718a5 compiler: merge reactive scopes across const StoreLocal (#29095)
@jbonta nerd-sniped me into making this optimization during conference
prep, posting this as a PR now that keynote is over.

Consider these two cases:

```javascript
export default function MyApp1({ count }) {
  const cb = () => count;
  return <div onclick={cb}>Hello World</div>;
}

export default function MyApp2({ count }) {
  return <div onclick={() => count}>Hello World</div>;
}
```

Previously, the former would create two reactive scopes (one for `cb`,
one for the div) while the latter would only have a single scope for the
`div` and its inline callback. The reason we created separate scopes
before is that there's a `StoreLocal 'cb' = t0` instruction in-between,
and i had conservatively implemented the merging pass to not allow
intervening StoreLocal instructions.

The observation is that intervening StoreLocals are fine _if_ the
assigned variable's last usage is the next scope. We already have a
check that the intervening lvalues are last-used at/before the next
scope, so it's trivial to extend this to support StoreLocal.

Note that we already don't merge scopes if there are intervening
terminals, so we don't have to worry about things like conditional
StoreLocal, conditional access of the resulting value, etc.
2024-05-16 17:29:05 -07:00
Yuta Sakou
7a149aa162 docs: fix typo DESIGN_GOALS.md (#29102)
## Summary
Fixes typo  DESIGN_GOALS.md  in react compiler docs

## How did you test this change?
it's document fix, so no tests needed.
2024-05-16 16:11:22 -07:00
Jason Bonta
91e88f94cc allow compiler playground to scroll horizontally 2024-05-16 13:15:13 -07:00
Jan Amann
94896cb8c5 [React Compiler] Fix rule name for ESLint plugin (#29096)
/cc @poteto

<!--
  Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.

Before submitting a pull request, please make sure the following is
done:

1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
  2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
  9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
  10. If you haven't already, complete the CLA.

Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->

## Summary

<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->

Seems like the README of the package was outdated.

## How did you test this change?

Tried this configuration in a project of mine.

<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
  If you leave this empty, your PR will very likely be closed.
-->
2024-05-16 12:36:22 -04:00
Kirill Zyusko
e8779a9a9a [React Compiler] use filename instead of context.filename in ESLint plugin (#29104)
<!--
  Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.

Before submitting a pull request, please make sure the following is
done:

1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
  2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
  9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
  10. If you haven't already, complete the CLA.

Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->

## Summary

Use `filename` instead of `context.filename` in eslint compiler.

The problem is that in `react-native` + `typescript` project the context
may not have `filename`:

<img width="384" alt="image"
src="https://github.com/facebook/react/assets/22820318/e5d184fa-5ac9-4512-96b9-644baa3d5f25">

And eslint will crash with:

```bash
TypeError: Error while loading rule 'react-compiler/react-compiler': Cannot read properties of undefined (reading 'endsWith')
```

But in fact we already derive `filename` variable earlier so we can
simply reuse the variable (I guess).

## How did you test this change?

- add `eslint` plugin to RN project;
- run eslint
2024-05-16 12:35:52 -04:00
Lauren Tan
149b917c8a Bump version to 0.0.0-experimental-8d5174c-20240515 2024-05-15 17:43:42 -07:00
Lauren Tan
c912057093 Bump version to 0.0.0-experimental-53bb89e-20240515 2024-05-15 17:43:41 -07:00
Lauren Tan
f470ba3218 Bump version to 0.0.0-experimental-c23de8d-20240515 2024-05-15 17:43:40 -07:00
Lauren Tan
7f22c5243e [compiler:publish] Specify https for registry
Uses https for the npm registry so the publishing script isn't rejected.
Fixes:

```
Beginning October 4, 2021, all connections to the npm registry - including for package installation - must use TLS 1.2 or higher. You are currently using plaintext http to connect. Please visit the GitHub blog for more information: https://github.blog/2021-08-23-npm-registry-deprecating-tls-1-0-tls-1-1/
```

ghstack-source-id: b247d044ea48f3007cf8bd13445fb7ece0f5b02f
Pull Request resolved: https://github.com/facebook/react/pull/29087
2024-05-15 17:42:19 -07:00
Steven
5052bfba8b chore(docs): fix typo (plugion => plugin) in DESIGN_GOALS.md (#29086)
<!--
  Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.

Before submitting a pull request, please make sure the following is
done:

1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
  2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
  9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
  10. If you haven't already, complete the CLA.

Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->

## Summary

This PR fixes a typo in `./compiler/docs/DESIGN_GOALS.md`.

I believe `plugion` should be `plugin`.

## How did you test this change?

Rendered the markdown to html.
2024-05-15 20:41:05 -04:00
Lauren Tan
50c999eb0e [compiler] Add readme for babel plugin
Adds a simple readme for the babel plugin with links to the docs.

ghstack-source-id: 4dac0b2a4b
Pull Request resolved: https://github.com/facebook/react/pull/29084
2024-05-15 17:29:49 -07:00
Lauren Tan
a0eb2653b8 [compiler] Check if current branch is main
This script needs to run from `main` since it commits version bumps for
packages, and those need to point to publicly available hashes. So,
throw an error if we're not already on main.

ghstack-source-id: ce0168e826
Pull Request resolved: https://github.com/facebook/react/pull/29083
2024-05-15 16:17:23 -07:00
Lauren Tan
5572edc929 [compiler] Various fixes for publishing script
- Specify a registry for npm publish because otherwise it tries to use
  the yarn registry
- `packages` option actually works

This _should_ work now (note last line of output), will test it once we
land this since i want to publish a new version of the eslint plugin
with some important fixes.

```
npm notice
npm notice 📦  eslint-plugin-react-compiler@0.0.0-experimental-53bb89e-20240515
npm notice === Tarball Contents ===
npm notice 827B  README.md
npm notice 2.1MB dist/index.js
npm notice 1.0kB package.json
npm notice === Tarball Details ===
npm notice name:          eslint-plugin-react-compiler
npm notice version:       0.0.0-experimental-53bb89e-20240515
npm notice filename:      eslint-plugin-react-compiler-0.0.0-experimental-53bb89e-20240515.tgz
npm notice package size:  300.9 kB
npm notice unpacked size: 2.1 MB
npm notice shasum:        cb99823f3a483c74f470085cac177bd020f7a85a
npm notice integrity:     sha512-L3HV9qja1dnCl[...]IaRSZJ3P/v6yQ==
npm notice total files:   3
npm notice
npm notice Publishing to http://registry.npmjs.org/ with tag latest and default access (dry-run)
```

ghstack-source-id: 63067ef772
Pull Request resolved: https://github.com/facebook/react/pull/29082
2024-05-15 16:17:23 -07:00
Lauren Tan
cf7d895db6 [compiler:eslint] Fix false positive with TS type param syntax
Previously we would attempt to parse code in the eslint plugin with the
HermesParser first as it can handle some TS syntax. However, this was
leading to a mis-parse of React hook calls with type params (eg,
`useRef<null>()` as a BinaryExpression rather than a CallExpression with
a type param. This triggered our validation that Hooks should not be
used as normal values.

To fix this, we now try to parse with the babel parser (with TS support)
for filenames that end with ts/tsx, and fallback to HermesParser for
regular JS files.

ghstack-source-id: 5b7231031c
Pull Request resolved: https://github.com/facebook/react/pull/29081
2024-05-15 15:44:21 -07:00
Joseph Savona
3adca7a477 compiler: fix jsx text attributes with double quotes (#29079)
Fixes #29069 by detecting the presence of double-quotes in JSX attribute
strings and falling back to using an expression container.
2024-05-15 14:40:33 -07:00
Sathya Gunasekaran
5e11e7fc20 [heathcheck] Check for namespaced StrictMode (#29076)
Previously, we only checked for StrictMode by searching for
`<StrictMode>` but we should also check for the namespaced version,
`<React.StrictMode>`.

Fixes https://github.com/facebook/react/issues/29075
2024-05-15 14:15:32 -07:00
Joseph Savona
c93c30f9d4 Fix ESLint and Prettier configs for React Compiler (#29073)
Fixes the top-level ESLint and Prettier configs to ignore the compiler.
For now the compiler has its own prettier and linting setup with
different versions/configs.
2024-05-15 14:02:57 -07:00
Christoph Nakazawa
cfeb491ee7 Add a main field to eslint-plugin-react-compiler, fixes #29068. (#29072)
## Summary

The main field is missing, this fixes it.

Fixes #29068.

## How did you test this change?

Manually patched the package and tried it in my codebase.
2024-05-15 17:02:20 -04:00
Joseph Savona
71565a932c Merge pull request #29061 from facebook/compiler
Open-source React Compiler
2024-05-15 11:16:19 -07:00
Joe Savona
575477a2e8 Publish React Compiler to open source 2024-05-15 10:58:51 -07:00
Lauren Tan
740b7c1b0a Bump version to 0.0.0-experimental-410375f-20240515 2024-05-15 08:05:02 -07:00
Lauren Tan
5501ebceec Bump version to 0.0.0-experimental-e04a001-20240515 2024-05-15 08:05:02 -07:00
Lauren Tan
d794e4a86e Bump version to 0.0.0-experimental-4690415-20240515 2024-05-15 08:05:01 -07:00
Lauren Tan
41142f306e Unthrow publish script 2024-05-15 08:03:50 -07:00
Andrew Clark
915b914b3a Bump React 19 beta to RC (#29060)
This updates the Canary label from "beta" to "rc".

We will publish an actual RC (e.g. 19.0.0-rc.0) too; this only changes
the label in the canary releases.
2024-05-15 11:01:28 -04:00
Lauren Tan
ecccaf6e01 Fix CI for compiler paths
ghstack-source-id: 43bb0c8c957fc550203445868c90fa706e3a8d11
Pull Request resolved: https://github.com/facebook/react-forget/pull/2968
2024-05-14 11:02:47 -04:00
Lauren Tan
6457343cd3 Don't emit types for babel-plugin-react-compiler
ghstack-source-id: a1776f97d5f7d4d8e02886906eb001e358628fc8
Pull Request resolved: https://github.com/facebook/react-forget/pull/2967
2024-05-14 11:02:30 -04:00
Lauren Tan
6948639df6 [publish] Add script to publish packages
Adds a script to publish babel-plugin-react-compiler,
eslint-plugin-react-compiler, and react-compiler-healthcheck to npm.

Instructions are in a comment in scripts/publish.js, please read
carefully before using this script!

Test plan:

```
$ yarn npm:publish

yarn run v1.22.10
$ node scripts/publish
ℹ Preparing to publish (dry run) [debug=false]
ℹ Building packages
✔ Successfully built babel-plugin-react-compiler
✔ Successfully built eslint-plugin-react-compiler
✔ Successfully built react-compiler-healthcheck
ℹ Dry run: Report tarball contents

========== babel-plugin-react-compiler ==========

⠴ Running npm pack --dry-run
npm WARN config init.author.name Use `--init-author-name` instead.
npm WARN config init.author.email Use `--init-author-email` instead.
⠦ Running npm pack --dry-run
npm notice
npm notice 📦  babel-plugin-react-compiler@0.0.1
npm notice === Tarball Contents ===
npm notice 11B    dist/__tests__/DisjointSet-test.d.ts
npm notice 11B    dist/__tests__/envConfig-test.d.ts
npm notice 11B    dist/__tests__/Logger-test.d.ts
npm notice 11B    dist/__tests__/parseConfigPragma-test.d.ts
npm notice 11B    dist/__tests__/Result-test.d.ts
npm notice 145B   dist/__tests__/test-utils/validateNoUseBeforeDefine.d.ts
npm notice 144B   dist/Babel/BabelPlugin.d.ts
npm notice 350B   dist/Babel/RunReactCompilerBabelPlugin.d.ts
npm notice 2.4kB  dist/CompilerError.d.ts
npm notice 455B   dist/Entrypoint/Gating.d.ts
npm notice 376B   dist/Entrypoint/Imports.d.ts
npm notice 166B   dist/Entrypoint/index.d.ts
npm notice 1.8kB  dist/Entrypoint/Options.d.ts
npm notice 1.3kB  dist/Entrypoint/Pipeline.d.ts
npm notice 819B   dist/Entrypoint/Program.d.ts
npm notice 295B   dist/Entrypoint/Reanimated.d.ts
npm notice 753B   dist/Entrypoint/Suppression.d.ts
npm notice 113B   dist/HIR/AssertConsistentIdentifiers.d.ts
npm notice 115B   dist/HIR/AssertTerminalSuccessorsExist.d.ts
npm notice 529B   dist/HIR/AssertValidBlockNesting.d.ts
npm notice 110B   dist/HIR/AssertValidMutableRanges.d.ts
npm notice 585B   dist/HIR/BuildHIR.d.ts
npm notice 116B   dist/HIR/BuildReactiveScopeTerminalsHIR.d.ts
npm notice 125B   dist/HIR/ComputeUnconditionalBlocks.d.ts
npm notice 602B   dist/HIR/Dominator.d.ts
npm notice 12.7kB dist/HIR/Environment.d.ts
npm notice 184B   dist/HIR/FindContextIdentifiers.d.ts
npm notice 401B   dist/HIR/Globals.d.ts
npm notice 22.2kB dist/HIR/HIR.d.ts
npm notice 2.8kB  dist/HIR/HIRBuilder.d.ts
npm notice 1.1kB  dist/HIR/index.d.ts
npm notice 108B   dist/HIR/MergeConsecutiveBlocks.d.ts
npm notice 115B   dist/HIR/MergeOverlappingReactiveScopesHIR.d.ts
npm notice 2.2kB  dist/HIR/ObjectShape.d.ts
npm notice 1.4kB  dist/HIR/PrintHIR.d.ts
npm notice 106B   dist/HIR/PruneUnusedLabelsHIR.d.ts
npm notice 1.1kB  dist/HIR/Types.d.ts
npm notice 1.9kB  dist/HIR/visitors.d.ts
npm notice 776B   dist/index.d.ts
npm notice 5.4MB  dist/index.js
npm notice 429B   dist/Inference/AnalyseFunctions.d.ts
npm notice 324B   dist/Inference/DropManualMemoization.d.ts
npm notice 436B   dist/Inference/index.d.ts
npm notice 224B   dist/Inference/InferAlias.d.ts
npm notice 204B   dist/Inference/InferAliasForPhis.d.ts
npm notice 206B   dist/Inference/InferAliasForStores.d.ts
npm notice 115B   dist/Inference/InferMutableContextVariables.d.ts
npm notice 151B   dist/Inference/InferMutableLifetimes.d.ts
npm notice 109B   dist/Inference/InferMutableRanges.d.ts
npm notice 212B   dist/Inference/InferMutableRangesForAlias.d.ts
npm notice 106B   dist/Inference/InferReactivePlaces.d.ts
npm notice 368B   dist/Inference/InferReferenceEffects.d.ts
npm notice 201B   dist/Inference/InferTryCatchAliases.d.ts
npm notice 130B   dist/Inference/InlineImmediatelyInvokedFunctionExpressions.d.ts
npm notice 106B   dist/Optimization/ConstantPropagation.d.ts
npm notice 258B   dist/Optimization/DeadCodeElimination.d.ts
npm notice 177B   dist/Optimization/index.d.ts
npm notice 103B   dist/Optimization/PruneMaybeThrows.d.ts
npm notice 108B   dist/ReactiveScopes/AlignMethodCallScopes.d.ts
npm notice 110B   dist/ReactiveScopes/AlignObjectMethodScopes.d.ts
npm notice 133B   dist/ReactiveScopes/AlignReactiveScopesToBlockScopes.d.ts
npm notice 126B   dist/ReactiveScopes/AlignReactiveScopesToBlockScopesHIR.d.ts
npm notice 132B   dist/ReactiveScopes/AssertScopeInstructionsWithinScope.d.ts
npm notice 125B   dist/ReactiveScopes/AssertWellFormedBreakTargets.d.ts
npm notice 363B   dist/ReactiveScopes/BuildReactiveBlocks.d.ts
npm notice 142B   dist/ReactiveScopes/BuildReactiveFunction.d.ts
npm notice 759B   dist/ReactiveScopes/CodegenReactiveFunction.d.ts
npm notice 154B   dist/ReactiveScopes/CollectReactiveIdentifiers.d.ts
npm notice 128B   dist/ReactiveScopes/CollectReferencedGlobals.d.ts
npm notice 698B   dist/ReactiveScopes/DeriveMinimalDependencies.d.ts
npm notice 138B   dist/ReactiveScopes/ExtractScopeDeclarationsFromDestructuring.d.ts
npm notice 121B   dist/ReactiveScopes/FlattenReactiveLoops.d.ts
npm notice 124B   dist/ReactiveScopes/FlattenScopesWithHooksOrUse.d.ts
npm notice 2.1kB  dist/ReactiveScopes/index.d.ts
npm notice 370B   dist/ReactiveScopes/InferReactiveScopeVariables.d.ts
npm notice 217B   dist/ReactiveScopes/MemoizeFbtOperandsInSameScope.d.ts
npm notice 127B   dist/ReactiveScopes/MergeOverlappingReactiveScopes.d.ts
npm notice 138B   dist/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.d.ts
npm notice 1.0kB  dist/ReactiveScopes/PrintReactiveFunction.d.ts
npm notice 123B   dist/ReactiveScopes/PromoteUsedTemporaries.d.ts
npm notice 118B   dist/ReactiveScopes/PropagateEarlyReturns.d.ts
npm notice 127B   dist/ReactiveScopes/PropagateScopeDependencies.d.ts
npm notice 123B   dist/ReactiveScopes/PruneAllReactiveScopes.d.ts
npm notice 126B   dist/ReactiveScopes/PruneAlwaysInvalidatingScopes.d.ts
npm notice 117B   dist/ReactiveScopes/PruneHoistedContexts.d.ts
npm notice 227B   dist/ReactiveScopes/PruneNonEscapingScopes.d.ts
npm notice 125B   dist/ReactiveScopes/PruneNonReactiveDependencies.d.ts
npm notice 122B   dist/ReactiveScopes/PruneTemporaryLValues.d.ts
npm notice 118B   dist/ReactiveScopes/PruneUnusedLabels.d.ts
npm notice 118B   dist/ReactiveScopes/PruneUnusedScopes.d.ts
npm notice 123B   dist/ReactiveScopes/RenameVariables.d.ts
npm notice 114B   dist/ReactiveScopes/StabilizeBlockIds.d.ts
npm notice 3.1kB  dist/ReactiveScopes/visitors.d.ts
npm notice 170B   dist/SSA/EliminateRedundantPhi.d.ts
npm notice 101B   dist/SSA/EnterSSA.d.ts
npm notice 154B   dist/SSA/index.d.ts
npm notice 99B    dist/SSA/LeaveSSA.d.ts
npm notice 63.5kB dist/tsconfig.tsbuildinfo
npm notice 43B    dist/TypeInference/index.d.ts
npm notice 103B   dist/TypeInference/InferTypes.d.ts
npm notice 352B   dist/Utils/ComponentDeclaration.d.ts
npm notice 256B   dist/Utils/DisjointSet.d.ts
npm notice 322B   dist/Utils/HookDeclaration.d.ts
npm notice 613B   dist/Utils/logger.d.ts
npm notice 2.3kB  dist/Utils/Result.d.ts
npm notice 119B   dist/Utils/RuntimeDiagnosticConstants.d.ts
npm notice 1.1kB  dist/Utils/Stack.d.ts
npm notice 149B   dist/Utils/todo.d.ts
npm notice 782B   dist/Utils/utils.d.ts
npm notice 603B   dist/Validation/index.d.ts
npm notice 117B   dist/Validation/ValidateContextVariableLValues.d.ts
npm notice 109B   dist/Validation/ValidateHooksUsage.d.ts
npm notice 214B   dist/Validation/ValidateMemoizedEffectDependencies.d.ts
npm notice 113B   dist/Validation/ValidateNoCapitalizedCalls.d.ts
npm notice 114B   dist/Validation/ValidateNoRefAccesInRender.d.ts
npm notice 113B   dist/Validation/ValidateNoSetStateInRender.d.ts
npm notice 131B   dist/Validation/ValidatePreservedManualMemoization.d.ts
npm notice 102B   dist/Validation/ValidateUseMemo.d.ts
npm notice 2.3kB  package.json
npm notice === Tarball Details ===
npm notice name:          babel-plugin-react-compiler
npm notice version:       0.0.1
npm notice filename:      babel-plugin-react-compiler-0.0.1.tgz
npm notice package size:  1.1 MB
npm notice unpacked size: 5.5 MB
npm notice shasum:        9a0fb71bdc904d6ab92432a506b0e037f10dd7ce
npm notice integrity:     sha512-hXtObyIEP4MbO[...]jstRbpztyLf4g==
npm notice total files:   119
npm notice

========== eslint-plugin-react-compiler ==========

⠴ Running npm pack --dry-run
npm WARN config init.author.name Use `--init-author-name` instead.
npm WARN config init.author.email Use `--init-author-email` instead.
⠋ Running npm pack --dry-run
npm notice
npm notice 📦  eslint-plugin-react-compiler@0.0.0
npm notice === Tarball Contents ===
npm notice 827B  README.md
npm notice 2.1MB dist/index.js
npm notice 968B  package.json
npm notice === Tarball Details ===
npm notice name:          eslint-plugin-react-compiler
npm notice version:       0.0.0
npm notice filename:      eslint-plugin-react-compiler-0.0.0.tgz
npm notice package size:  300.5 kB
npm notice unpacked size: 2.1 MB
npm notice shasum:        f24dab544b03d36d9bb676a16256f114bd5e0ed6
npm notice integrity:     sha512-8PcKZXZ+RVBDP[...]D7jFmkvHJoBeA==
npm notice total files:   3
npm notice

========== react-compiler-healthcheck ==========

⠼ Running npm pack --dry-run
npm WARN config init.author.name Use `--init-author-name` instead.
npm WARN config init.author.email Use `--init-author-email` instead.
⠏ Running npm pack --dry-run
npm notice
npm notice 📦  react-compiler-healthcheck@0.0.0
npm notice === Tarball Contents ===
npm notice 2.1MB dist/index.js
npm notice 677B  package.json
npm notice 1.8kB rollup.config.js
npm notice 1.1kB src/checks/libraryCompat.ts
npm notice 3.2kB src/checks/reactCompiler.ts
npm notice 759B  src/checks/strictMode.ts
npm notice 67B   src/config.ts
npm notice 1.5kB src/index.ts
npm notice 602B  tsconfig.json
npm notice === Tarball Details ===
npm notice name:          react-compiler-healthcheck
npm notice version:       0.0.0
npm notice filename:      react-compiler-healthcheck-0.0.0.tgz
npm notice package size:  290.4 kB
npm notice unpacked size: 2.1 MB
npm notice shasum:        55c0eb57aebc7305270a29e78ebf6c056044bae0
npm notice integrity:     sha512-nPVhGyh8i9PoO[...]v52c3U0tKGpzA==
npm notice total files:   9
npm notice
✔ Please confirm contents of packages before publishing. You can run this command again with --for-real to publish to npm
  Done in 61.19s.
```

```
$ yarn npm:publish --debug --for-real

yarn run v1.22.10
$ node scripts/publish --debug --for-real
ℹ Preparing to publish (for real) [debug=true]
ℹ Building packages
✔ Successfully built babel-plugin-react-compiler
✔ Successfully built eslint-plugin-react-compiler
✔ Successfully built react-compiler-healthcheck
✔ Bumped babel-plugin-react-compiler to 0.0.0-experimental-2769f7a-20240513
✔ Bumped eslint-plugin-react-compiler to 0.0.0-experimental-e04a001-20240513
✔ Bumped react-compiler-healthcheck to 0.0.0-experimental-410375f-20240513

========== babel-plugin-react-compiler ==========

⠋ Publishing babel-plugin-react-compiler to npm
Command that would have run: npm publish --dry-run
/Users/lauren/code/react-forget/compiler/scripts/publish.js:188
      throw new Error(
            ^

Error: This error is intentional, please double check scripts/publish.js and remove this error prior to publishing
    at main (/Users/lauren/code/react-forget/compiler/scripts/publish.js:188:13)

Node.js v20.11.0
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```

On the day of launch, remove the hardcoded error, then run `yarn
npm:publish --debug --for-real` first as a sanity check. Confirm the
contents are correct.

Then when you are certain, run `yarn npm:publish --for-real`, and push
the commited version bumps to GitHub so the hashes that the packages
originate from are public.

ghstack-source-id: 403bc22d4b340056fd112fcfc38580f28ed6c986
Pull Request resolved: https://github.com/facebook/react-forget/pull/2966
2024-05-14 11:02:29 -04:00
Lauren Tan
67ea821e5f [publish] Fix files field for packages, invalid version range
The [`files` field](https://docs.npmjs.com/cli/v10/commands/npm-publish#files-included-in-package)
controls what files get included in the published package.

This PR specifies the `files` field on our publishable packages to only
include the `dist` directory, since we don't need to ship any types or
sourcemaps with 3 of them.

react-compiler-runtime is a runtime package which has sourcemaps, so we
also include the `src` directory in the published package.

Also fixes an invalid version range for the react peer dependency in
react-compiler-runtime, tested that it works via https://semver.npmjs.com/

ghstack-source-id: 12b36c203fc9fd8d72a1995fb3fba2312de4aa51
Pull Request resolved: https://github.com/facebook/react-forget/pull/2965
2024-05-14 11:02:29 -04:00
Lauren Tan
5a842dbfb1 Fix healthcheck package.json bin field
ghstack-source-id: 28045c6edb342dea125ea8fcea564ce90626f21d
Pull Request resolved: https://github.com/facebook/react-forget/pull/2964
2024-05-14 11:02:29 -04:00
Jan Kassens
26f2496093 Remove 2 no longer existing files from inlinedHostConfig (#29027)
Remove 2 no longer existing files from inlinedHostConfig

While looking for `.classic` forks, I noticed these files no longer
exist.
2024-05-14 10:07:00 -04:00
Timothy Yung
5f28f51759 Enable enableUnifiedSyncLane for React Native (Meta) (#29052)
## Summary

Enables the `enableUnifiedSyncLane` feature flag for React Native
(Meta).

## How did you test this change?

```
$ yarn test
$ yarn flow fabric
```
2024-05-13 22:56:42 -07:00
Joe Savona
b956024f3d Fix Jest tests
ghstack-source-id: e2507b7285bb52ee02d2cece35aa16eb0bc0baa5
Pull Request resolved: https://github.com/facebook/react-forget/pull/2963
2024-05-13 14:10:16 -07:00
Lauren Tan
ce0c08eaad [healthcheck] Clean up ignorelist and check if deps exist
ghstack-source-id: b6d58988eeb86e657a6db2a481b6c2fe2fba0344
Pull Request resolved: https://github.com/facebook/react-forget/pull/2962
2024-05-13 17:08:31 -04:00
Jan Kassens
4cf7ee23fb CI: remove non-functional Rust compiler benchmark workflow (#2948)
`forget_napi` doesn't exist and given we're not currently working on the Rust compiler, I'm not sure we need to keep this around until we know that we do want to invest into this area again.
2024-05-13 17:04:18 -04:00
Joe Savona
7e3be125e8 Use unique name for c import local identifier
ghstack-source-id: 93055b972f
Pull Request resolved: https://github.com/facebook/react-forget/pull/2961
2024-05-13 12:59:19 -07:00
Lauren Tan
669e8d02b1 [healthcheck] Refine glob pattern
ghstack-source-id: 6f9e7f9534
Pull Request resolved: https://github.com/facebook/react-forget/pull/2960
2024-05-11 00:56:48 -04:00
Lauren Tan
9ef7557730 [healthcheck] Don't compile mjs
ghstack-source-id: 087985cd23
Pull Request resolved: https://github.com/facebook/react-forget/pull/2959
2024-05-11 00:56:48 -04:00
Lauren Tan
78da5becaf [healthcheck] Remove console.error
ghstack-source-id: c06d44960c
Pull Request resolved: https://github.com/facebook/react-forget/pull/2958
2024-05-11 00:56:48 -04:00
Lauren Tan
76b7331b0d [healthcheck] Only glob files with certain extensions by default
ghstack-source-id: b8db172669
Pull Request resolved: https://github.com/facebook/react-forget/pull/2957
2024-05-11 00:56:48 -04:00
Lauren Tan
63e5330260 [healthcheck] Rename to react-compiler-healthcheck
Don't really have time to implement the react-compiler/healthcheck
version of this script, so for now i propose we just publish this as
react-compiler-healthcheck

the command for running this would be

```
$ npx react-compiler-healthcheck --src 'whatever/**/*.*'
```

ghstack-source-id: e2c443a912
Pull Request resolved: https://github.com/facebook/react-forget/pull/2956
2024-05-11 00:56:48 -04:00
Lauren Tan
9a6ce3379d [healthcheck] Fix build
runReactBabelPluginReactCompiler brings in fbt which is unnecessary for
OSS so I removed it.

Also makes it so healthckeck is installed as an executable

ghstack-source-id: ec6c76f8be
Pull Request resolved: https://github.com/facebook/react-forget/pull/2955
2024-05-11 00:56:48 -04:00
Lauren Tan
db34843c42 [healthcheck] Build with rollup
ghstack-source-id: cd40da2ddc
Pull Request resolved: https://github.com/facebook/react-forget/pull/2954
2024-05-11 00:56:48 -04:00
Lauren Tan
9b0044e213 Better detection for reanimated
We found this issue through enabling the compiler on the React Conf app.

`babel-preset-expo` automatically adds the `react-native-animated`
plugin to apps that use the preset. This means that Expo apps sometimes
omit the react-native-animated plugin from their config, which was
failing our existing check. This PR copies the same detection that Expo
does for adding reanimated as a fallback

ghstack-source-id: 46f7aec0bc
Pull Request resolved: https://github.com/facebook/react-forget/pull/2953
2024-05-11 00:56:48 -04:00
Lauren Tan
c58142ba35 ReAnimated -> Reanimated
Fixes a small typo by request

ghstack-source-id: 00889150a9
Pull Request resolved: https://github.com/facebook/react-forget/pull/2952
2024-05-11 00:56:48 -04:00
Sebastian Markbåge
9d76c954cf [Flight] Error if a legacy React Element is attempted to be rendered (#29043)
This errors on the client normally but in the case the `type` is a
function - i.e. a Server Component - it wouldn't be transferred to error
on the client so you end up with a worse error message. So this just
implements the same check as ChildFiber.
2024-05-10 09:37:42 -04:00
Dmytro Rykun
2c022b847e Clean up the enableEarlyReturnForPropDiffing experiment (#29041)
## Summary

The experiment has shown no significant performance changes. This PR
removes it.

## How did you test this change?
```
yarn flow native
yarn lint
```
2024-05-10 11:00:03 +01:00
Sebastian Markbåge
6ef0dd4f2c [Flight] Enable Binary and ReadableStreams in Stable (#29035)
These are ready to ship in stable.
2024-05-09 20:56:15 -04:00
Sebastian Markbåge
6c409acefd [Flight Reply] Encode Objects Returned to the Client by Reference (#29010)
Stacked on #28997.

We can use the technique of referencing an object by its row + property
name path for temporary references - like we do for deduping. That way
we don't need to generate an ID for temporary references. Instead, they
can just be an opaque marker in the slot and it has the implicit ID of
the row + path.

Then we can stash all objects, even the ones that are actually available
to read on the server, as temporary references. Without adding anything
to the payload since the IDs are implicit. If the same object is
returned to the client, it can be referenced by reference instead of
serializing it back to the client. This also helps preserve object
identity.

We assume that the objects are immutable when they pass the boundary.

I'm not sure if this is worth it but with this mechanism, if you return
the `FormData` payload from a `useActionState` it doesn't have to be
serialized on the way back to the client. This is a common pattern for
having access to the last submission as "default value" to the form
fields. However you can still control it by replacing it with another
object if you want. In MPA mode, the temporary references are not
configured and so it needs to be serialized in that case. That's
required anyway for hydration purposes.

I'm not sure if people will actually use this in practice though or if
FormData will always be destructured into some other object like with a
library that turns it into typed data, and back. If so, the object
identity is lost.
2024-05-09 20:00:56 -04:00
Sebastian Markbåge
38d9f156b8 [Flight Reply] Dedupe Objects and Support Cyclic References (#28997)
Uses the same technique as in #28996 to encode references to already
emitted objects. This now means that Reply can support cyclic objects
too for parity.
2024-05-09 19:24:37 -04:00
Sebastian Markbåge
7a78d03028 [Flight] Encode references to existing objects by property path (#28996)
Instead of forcing an object to be outlined to be able to refer to it
later we can refer to it by the property path inside another parent
object.

E.g. this encodes such a reference as `'$123:props:children:foo:bar'`.

That way we don't have to preemptively outline object and we can dedupe
after the first time we've found it.

There's no cost on the client if it's not used because we're not storing
any additional information preemptively.

This works mainly because we only have simple JSON objects from the root
reference. Complex objects like Map, FormData etc. are stored as their
entries array in the look up and not the complex object. Other complex
objects like TypedArrays or imports don't have deeply nested objects in
them that can be referenced.

This solves the problem that we only dedupe after the third instance.
This dedupes at the second instance. It also solves the problem where
all nested objects inside deduped instances also are outlined.

The property paths can get pretty large. This is why a test on payload
size increased. We could potentially outline the reference itself at the
first dupe. That way we get a shorter ID to refer to in the third
instance.
2024-05-09 19:16:14 -04:00
Joe Savona
6b23c25ff9 Support HMR
Adds supports for hot module reloading (HMR) by resetting the cache if a hash of the source file changes. This is enabled via a compiler flag, but also enabled automatically via the babel plugin when NODE_ENV=development.

ghstack-source-id: 5cd1ad5c89
Pull Request resolved: https://github.com/facebook/react-forget/pull/2951
2024-05-09 13:59:31 -07:00
Andrew Clark
c3345638cb Support useFormStatus in progressively-enhanced forms (#29019)
Before this change, `useFormStatus` is only activated if a form is
submitted by an action function (either `<form action={actionFn}>` or
`<button formAction={actionFn}>`).

After this change, `useFormStatus` will also be activated if you call
`startTransition(actionFn)` inside a submit event handler that is
`preventDefault`-ed.

This is the last missing piece for implementing a custom `action` prop
that is progressively enhanced using `onSubmit` while maintaining the
same behavior as built-in form actions.

Here's the basic recipe for implementing a progressively-enhanced form
action. This would typically be implemented in your UI component
library, not regular application code:

```js
import {requestFormReset} from 'react-dom';

// To implement progressive enhancement, pass both a form action *and* a
// submit event handler. The action is used for submissions that happen
// before hydration, and the submit handler is used for submissions that
// happen after.
<form
  action={action}
  onSubmit={(event) => {
    // After hydration, we upgrade the form with additional client-
    // only behavior.
    event.preventDefault();

    // Manually dispatch the action.
    startTransition(async () => {
      // (Optional) Reset any uncontrolled inputs once the action is
      // complete, like built-in form actions do.
      requestFormReset(event.target);

      // ...Do extra action-y stuff in here, like setting a custom
      // optimistic state...

      // Call the user-provided action
      const formData = new FormData(event.target);
      await action(formData);
    });
  }}
/>
```
2024-05-09 13:16:08 -04:00
Sebastian Markbåge
151cce3740 Track Stack of JSX Calls (#29032)
This is the first step to experimenting with a new type of stack traces
behind the `enableOwnerStacks` flag - in DEV only.

The idea is to generate stacks that are more like if the JSX was a
direct call even though it's actually a lazy call. Not only can you see
which exact JSX call line number generated the erroring component but if
that's inside an abstraction function, which function called that
function and if it's a component, which component generated that
component. For this to make sense it really need to be the "owner" stack
rather than the parent stack like we do for other component stacks. On
one hand it has more precise information but on the other hand it also
loses context. For most types of problems the owner stack is the most
useful though since it tells you which component rendered this
component.

The problem with the platform in its current state is that there's two
ways to deal with stacks:

1) `new Error().stack` 
2) `console.createTask()`

The nice thing about `new Error().stack` is that we can extract the
frames and piece them together in whatever way we want. That is great
for constructing custom UIs like error dialogs. Unfortunately, we can't
take custom stacks and set them in the native UIs like Chrome DevTools.

The nice thing about `console.createTask()` is that the resulting stacks
are natively integrated into the Chrome DevTools in the console and the
breakpoint debugger. They also automatically follow source mapping and
ignoreLists. The downside is that there's no way to extract the async
stack outside the native UI itself so this information cannot be used
for custom UIs like errors dialogs. It also means we can't collect this
on the server and then pass it to the client for server components.

The solution here is that we use both techniques and collect both an
`Error` object and a `Task` object for every JSX call.

The main concern about this approach is the performance so that's the
main thing to test. It's certainly too slow for production but it might
also be too slow even for DEV.

This first PR doesn't actually use the stacks yet. It just collects them
as the first step. The next step is to start utilizing this information
in error printing etc.

For RSC we pass the stack along across over the wire. This can be
concatenated on the client following the owner path to create an owner
stack leading back into the server. We'll later use this information to
restore fake frames on the client for native integration. Since this
information quickly gets pretty heavy if we include all frames, we strip
out the top frame. We also strip out everything below the functions that
call into user space in the Flight runtime. To do this we need to figure
out the frames that represents calling out into user space. The
resulting stack is typically just the one frame inside the owner
component's JSX callsite. I also eagerly strip out things we expect to
be ignoreList:ed anyway - such as `node_modules` and Node.js internals.
2024-05-09 12:23:05 -04:00
Jon Jensen
04b058868c Upgrade jest and jsdom (#29026)
## Summary

This brings:
 - jest* up from 29.4.2 -> 29.7.0
 - jsdom up from 20.0.0 -> 22.1.0

While the latest version of jest-dom-environment still wants
`jsdom@^20.0.0`, it can safely use at least up to `jsdom@22.1.0`. See
https://github.com/jestjs/jest/pull/13825#issuecomment-1564015010 for
details.

Upgrading to latest versions lets us improve some WheelEvent tests and
will make it possible to test a much simpler FormData construction
approach (see #29018)

## How did you test this change?

Ran `yarn test` and `yarn test --prod` successfully
2024-05-08 13:57:25 -04:00
Jan Kassens
b1d4096396 Facebook: merge react index.classic.fb and index.modern.fb (#29025)
Facebook: merge react index.classic.fb and index.modern.fb

These export the same.

NOTE: The 2 builds are still different based on flags and other forked
files.
2024-05-08 13:37:47 -04:00
Jan Kassens
6946ebe620 Cleanup enableServerComponentKeys flag (#28743)
Cleanup enableServerComponentKeys flag

Flag is `true` everywhere but RN where it doesn't apply.
2024-05-08 10:52:49 -04:00
Ruslan Lesiutin
1717ab0171 React DevTools 5.1.0 -> 5.2.0 (#29022)
Full list of changes (not a public changelog):
* fix[react-devtools/ci]: fix configurations for e2e testing
([hoxyq](https://github.com/hoxyq) in
[#29016](https://github.com/facebook/react/pull/29016))
* feat[react-devtools]: display forget badge for components in profiling
session ([hoxyq](https://github.com/hoxyq) in
[#29014](https://github.com/facebook/react/pull/29014))
* fix[react-devtools]: add backwards compat with legacy element type
symbol ([hoxyq](https://github.com/hoxyq) in
[#28982](https://github.com/facebook/react/pull/28982))
* Expose "view source" options to Fusebox integration
([motiz88](https://github.com/motiz88) in
[#28973](https://github.com/facebook/react/pull/28973))
* Enable inspected element context menu in Fusebox
([motiz88](https://github.com/motiz88) in
[#28972](https://github.com/facebook/react/pull/28972))
* Check in `frontend.d.ts` for react-devtools-fusebox, include in build
output ([motiz88](https://github.com/motiz88) in
[#28970](https://github.com/facebook/react/pull/28970))
* Devtools: Fix build-for-devtools
([eps1lon](https://github.com/eps1lon) in
[#28976](https://github.com/facebook/react/pull/28976))
* Move useMemoCache hook to react/compiler-runtime
([kassens](https://github.com/kassens) in
[#28954](https://github.com/facebook/react/pull/28954))
* warn -> error for Test Renderer deprecation
([acdlite](https://github.com/acdlite) in
[#28904](https://github.com/facebook/react/pull/28904))
* [react-dom] move all client code to `react-dom/client`
([gnoff](https://github.com/gnoff) in
[#28271](https://github.com/facebook/react/pull/28271))
* Rename the react.element symbol to react.transitional.element
([sebmarkbage](https://github.com/sebmarkbage) in
[#28813](https://github.com/facebook/react/pull/28813))
* Rename Forget badge ([jbonta](https://github.com/jbonta) in
[#28858](https://github.com/facebook/react/pull/28858))
* Devtools: Add support for useFormStatus
([eps1lon](https://github.com/eps1lon) in
[#28413](https://github.com/facebook/react/pull/28413))
2024-05-08 13:26:14 +01:00
Ruslan Lesiutin
0e6ea6991e fix[react-devtools/InspectedElementView.css]: dont draw bottom border for empty badge list (#29023)
Forward fix to https://github.com/facebook/react/pull/29014, the bug was
discovered while testing v5.2.0.
2024-05-08 13:16:27 +01:00
Dmytro Rykun
b37e4b4e61 Clean up fastAddProperties and make it more correct (#29015)
## Summary

This PR makes some fixes to the `fastAddProperties` function:
- Use `if (!attributeConfig)` instead of `if (attributeConfig ===
undefined)` to account for `null`.
- If a prop has an Object `attributeConfig` with a `diff` function
defined on it, treat it as an atomic value to keep the semantics of
`diffProperties`.

## How did you test this change?

Build and run RNTester app.
2024-05-08 13:10:04 +01:00
Ruslan Lesiutin
e150a32425 fix[react-devtools/ci]: fix configurations for e2e testing (#29016)
This should fix failing DevTools e2e tests on `main`.

With these changes, running tests locally successfully passes all cases.
2024-05-08 12:04:51 +01:00
Sebastian Markbåge
ec15267a00 [Flight Reply] Resolve outlined models async in Reply just like in Flight Client (#28988)
This is the same change as #28780 but for the Flight Reply receiver.

While it's not possible to create an "async module" reference in this
case - resolving a server reference can still be async if loading it
requires loading chunks like in a new server instance.

Since extracting a typed array from a Blob is async, that's also a case
where a dependency can be async.
2024-05-07 22:19:59 -04:00
Sebastian Markbåge
6bac4f2f31 [Fizz] Fallback to client replaying actions if we're trying to serialize a Blob (#28987)
This follows the same principle as in #28611.

We cannot serialize Blobs of a form data into HTML because you can't
initialize a file input to some value. However the serialization of
state in an Action can contain blobs. In this case we do error but
outside the try/catch that recovers to error to client replaying instead
of MPA mode. This errors earlier to ensure that this works.

Testing this is a bit annoying because JSDOM doesn't have any of the
Blob methods but the Blob needs to be compatible with FormData and the
FormData needs to be compatible with `<form>` nodes in these tests. So I
polyfilled those in JSDOM with some hacks.

A possible future enhancement would be to encode these blobs in a base64
mode instead and have some way to receive them on the server. It's just
a matter of layering this. I think the RSC layer's `FORM_DATA`
implementation can pass some flag to encode as base64 and then have
decodeAction include some way to parse them. That way this case would
work in MPA mode too.
2024-05-07 21:53:10 -04:00
Sebastian Markbåge
826bf4e51e [Flight Reply] Encode binary streams as a single collapsed Blob (#28986)
Based on #28893.

For other streams we encode each chunk as a separate form field which is
a bit bloated. Especially for binary chunks since they also have an
indirection. We need some way to encode the chunks as separate anyway.
This way the streaming using busboy actually allows each chunk to stream
in over the network one at a time.

For binary streams the actual chunking is not important. The chunks can
be split and recombined in whatever size chunk makes sense.

Since we buffer the entire content anyway we can combine the chunks to
be consecutive. This PR does that with binary streams and also combine
them into a single Blob. That way there's no extra overhead when passing
through a binary stream.

Ideally, we'd be able to just use the stream from that one Blob but
Node.js doesn't return byob streams from Blob. Additionally, we don't
actually stream the content of Blobs due to the layering with busboy
atm. We could do that for binary streams in particular by replacing the
File layering with a stream and resolving each chunk as it comes in.
That could be a follow up.

If we stop buffering in the future, this set up still allows us to split
them and send other form fields in between while blocked since the
protocol is still the same.
2024-05-07 21:52:55 -04:00
Jan Kassens
5a1cbc77b6 CI: remove react.yml integration test workflow (#2947)
This was used to run React CI tests compiled with the React Compiler. This is currently no longer used.
2024-05-07 17:29:57 -04:00
Jan Kassens
f4c12cc4a8 CI: fix Compiler Playground workflow (#2945)
Also renamed the workflow to make more sense when merged into the main React repo.
2024-05-07 16:15:59 -04:00
Jan Kassens
257f22ac57 CI: fix Compiler TypeScript workflow (#2946) 2024-05-07 16:07:46 -04:00
Jan Kassens
fd873e8ddd Rust: add stub for ensure_sufficient_stack (#2943) 2024-05-07 15:41:03 -04:00
Ruslan Lesiutin
e7d213dfb0 feat[react-devtools]: display forget badge for components in profiling session (#29014)
# Summary
- `compiledWithForget` field for nodes is now propagated from the
backend to frontend profiler stores
- Corresponding node with such field will have a `` prefix displayed
before its displayName
<img width="1728" alt="Screenshot 2024-05-07 at 15 05 37"
src="https://github.com/facebook/react/assets/28902667/fe044d40-52cb-4169-867d-5a2d72e3275b">

- Badges are now displayed on the right panel when some fiber is
selected in a specific commit
<img width="1728" alt="Screenshot 2024-05-07 at 15 05 50"
src="https://github.com/facebook/react/assets/28902667/297ba5ca-404d-4172-b9bf-bfed7978afe5">

- Badges are also displayed when user hovers over some node in the tree
<img width="1728" alt="Screenshot 2024-05-07 at 15 25 22"
src="https://github.com/facebook/react/assets/28902667/bee47884-61d1-46b6-a483-717fc148893a">
2024-05-07 16:39:01 +01:00
Ruslan Lesiutin
c32ff0f4f1 fix[react-devtools]: add backwards compat with legacy element type symbol (#28982)
Follow-up to https://github.com/facebook/react/pull/28813.

RDT is using `typeOf` from `react-is` to determine the element display
name, I've forked an implementation of this method, but will be using
legacy element symbol.
2024-05-07 16:38:43 +01:00
Jan Kassens
90a5d48a2f CI: fix Rust CI workflows (#2942)
- update paths
- update working directory
- rename files for clarity when merged into the main react repo
2024-05-07 11:17:30 -04:00
Sebastian Silbermann
0fc9c84e63 Allow specifying timeout in tests via third argument (#29006) 2024-05-07 17:15:39 +02:00
Moti Zilberman
afe54bfcbf [DevTools] Expose "view source" options to Fusebox integration (#28973)
## Summary

Exposes the APIs needed by React Native DevTools (Fusebox) to implement
the "view element source" and "view attribute source" features.

## How did you test this change?

1. `yarn build` in `react-devtools-fusebox`
2. Copy artifacts to rn-chrome-devtools-frontend
3. Write some additional glue code to implement
`viewElementSourceFunction` in our CDT fork.
4. Test the feature manually.


https://github.com/facebook/react/assets/2246565/12667018-100a-4b3f-957a-06c07f2af41a
2024-05-07 16:06:36 +01:00
Jan Kassens
55d6c6efaa CI: move .github directory back to root (#2941)
We need this in the root to run the steps. It should merge cleanly with the React repo as there is no file name overlap.

Next step is to update the paths to make it work again.
2024-05-07 11:03:44 -04:00
Dmytro Rykun
7039834262 Create Fabric-specific version of ReactNativeAttributesPayload (#28841)
## Summary

This PR introduces Fabric-only version of
`ReactNativeAttributesPayload`. It is a copy-paste of
`ReactNativeAttributesPayload.js`, and is called
`ReactNativeAttributesPayloadFabric.js`.
The idea behind this change is that certain optimizations in prop
diffing may actually be a regression on the old architecture. For
example, removing custom diffing may result in larger updateProps
payloads. Which is, I guess, fine with JSI, but might be a problem with
the bridge.

## How did you test this change?

There should be no runtime effect of this change.
2024-05-07 11:53:36 +01:00
Lauren Tan
7caf071e01 Add failing test for eslint-plugin false positive
The eslint rule seems to false positive on this typescript syntax, but
strangely the compiler does not

ghstack-source-id: 19baa24ff7addd83f59e2b03fdb180af169a2794
Pull Request resolved: https://github.com/facebook/react-forget/pull/2913
2024-05-06 20:39:49 -04:00
Lauren Tan
b28c53dcf1 Clarify how to address ValidateNoCapitalizedCalls errors
Make it clearer how to address this error by allowlisting globals that
are known to be safe

ghstack-source-id: e7fa6464ebb561a7a1366ff70430842007c6552e
Pull Request resolved: https://github.com/facebook/react-forget/pull/2909
2024-05-06 20:13:45 -04:00
Lauren Tan
d0a51e7dfc Allow eslint rule reportable severity to be set
During the demo I might show an example of fixing a
CannotPreserveMemoization error. But I don't want to make that
reportable by default, so this PR allows configuration like so

```js
module.exports = {
  root: true,
  plugins: [
    'eslint-plugin-react-compiler',
  ],
  rules: {
    'react-compiler/react-compiler': [
      'error', {
        reportableLevels: new Set([
          'InvalidJs',
          'InvalidReact',
          'CannotPreserveMemoization'
        ])
      }
     ]
  }
}
```

ghstack-source-id: 984c6d3cb7e19c8fea2bb88108dd26335c031573
Pull Request resolved: https://github.com/facebook/react-forget/pull/2936
2024-05-06 20:07:36 -04:00
Lauren Tan
84d28ebcc5 Default to raising all errors in eslint plugin
We control what gets reported via another function anyway so it's better
to raise everything at the compiler config level. This lets us configure
what level of diagnostic is reportable later

ghstack-source-id: 996d3cbb8d8f3e1bbe943210b8d633420e0f3f3b
Pull Request resolved: https://github.com/facebook/react-forget/pull/2935
2024-05-06 20:07:35 -04:00
Jan Kassens
e2d47342be Use default runtimeModule setting in Snap tests (#2940)
Lots of updated snapshots. The only updated code is resolving this TODO:

2b14805f98/packages/snap/src/compiler.ts (L41)
2024-05-06 19:57:37 -04:00
Jan Kassens
3708c36cbd [easy] remove unstable_useMemoCache reference from a snapshot file (#2937)
This file is intended to test that the test will skip if it was already compiled.

This updates the import to remove the no longer used name `unstable_useMemoCache`.
2024-05-06 19:54:13 -04:00
Jan Kassens
e63d3d1491 Upgrade to React 19 beta (#2938)
- Updated all directly defined dependencies to the latest React 19 Beta
- `package.json`: used `resolutions` to force React 19 for `react-is` transitive dependency
- `package.json`: postinstall script to patch fbt for the React 19 element Symbol
- Match on the message in Snap to exclude a React 19 warning that `act` should be imported from `react` instead (from inside `@testing-library/react`)
- Some updated snapshots, I think due to now recovering behavior of `useMemoCache`, please review.

In a next step, we can do the following. I excluded it since it from here as it made the PR unreviewable on GitHub.

- Snapshots now use `react/compiler-runtime` as in prod, so the different default in Snap is no longer needed.
2024-05-06 19:48:39 -04:00
Joe Savona
95ba7dcff5 Update readme and other docs
ghstack-source-id: b25cc2a481dc1508bbb710dff472f2d23667ec98
Pull Request resolved: https://github.com/facebook/react-forget/pull/2939
2024-05-06 14:53:47 -07:00
Jan Kassens
b498834eab Set enableUseMemoCacheHook to true everywhere (#28964)
Set enableUseMemoCacheHook to true everywhere for the next major releases.
2024-05-06 14:20:08 -04:00
Timothy Yung
9b1300209e Setup Wave 2 of Feature Flags for React Native (#28990)
## Summary

Sets up dynamic feature flags for `disableStringRefs`, `enableFastJSX`,
and `enableRefAsProp` in React Native (at Meta).

## How did you test this change?

```
$ yarn test
$ yarn flow fabric
```
2024-05-06 10:30:40 -07:00
Jan Kassens
e1771545a2 Remove domain from playground font sources (#2934) 2024-05-06 12:00:55 -04:00
Jack Pope
5d29478716 Add FB build for ReactReconcilerConstants (#29003)
In order to integrate the `react-reconciler` build created in #28880
with third party libraries, we need to have matching
`react-reconciler/constants` to go with it.
2024-05-06 11:32:43 -04:00
Timothy Yung
46abd7b1de Update compiled-rn destination paths to vendor/react (#28989)
I'm changing the destination paths in fbsource from `vendor/*` to
`vendor/react/*`.
2024-05-03 15:46:43 -07:00
Sebastian Markbåge
ec9400dc41 [Flight Reply] Encode ReadableStream and AsyncIterables (#28893)
Same as #28847 but in the other direction.

Like other promises, this doesn't actually stream in the outgoing
direction. It buffers until the stream is done. This is mainly due to
our protocol remains compatible with Safari's lack of outgoing streams
until recently.

However, the stream chunks are encoded as separate fields and so does
support the busboy streaming on the receiving side.
2024-05-03 17:23:55 -04:00
Sebastian Markbåge
5fcfd71638 Use undici polyfill for tests in old Node versions (#28887)
We currently don't test FormData / File dependent features in CI because
we use an old Node.js version in CI. We should probably upgrade to 18
since that's really the minimum version that supports all the features
out of the box.

JSDOM is not a faithful/compatible implementation of these APIs. The
recommended way to use Flight together with FormData/Blob/File in older
Node.js versions, is to polyfill using the `undici` library.

However, even in these versions the Blob implementation isn't quite
faithful so the Reply client needs a slight tweak for multi-byte typed
arrays.
2024-05-03 16:29:09 -04:00
Sebastian Markbåge
d5c303427e [Flight] Track Owner on AsyncLocalStorage When Available (#28807)
Stacked on #28798.

Add another AsyncLocalStorage to the FlightServerConfig. This context
tracks data on a per component level. Currently the only thing we track
is the owner in DEV.

AsyncLocalStorage around each component comes with a performance cost so
we only do it DEV. It's not generally a particularly safe operation
because you can't necessarily associate side-effects with a component
based on execution scope. It can be a lazy initializer or cache():ed
code etc. We also don't support string refs anymore for a reason.

However, it's good enough for optional dev only information like the
owner.
2024-05-03 16:29:00 -04:00
Pieter Vanderwerff
738ddf7419 Support <fbs> in addition to <fbt>
ghstack-source-id: 1ab99ebb5cef3f42399682a338feb12c4cf55f4c
Pull Request resolved: https://github.com/facebook/react-forget/pull/2933
2024-05-03 13:27:51 -07:00
Jan Kassens
0a0a3af75a Bundle config: inline internal hook wrapper (#28978)
Bundle config: inline internal hook wrapper

Instead of reading this wrapper from 2 files for "start" and "end" and
then string modifying the templates, just inline them like the other
wrappers in this file.
2024-05-03 14:08:10 -04:00
Jon Janzen
38cd73b25b Stop committing resources to an external repo (#28963)
This has been integrated into this repo (`builds/facebook-fbsource`) so
we no longer need the extra repo
2024-05-03 11:01:53 -07:00
Joe Savona
90863beaa8 Expand the architecture doc
ghstack-source-id: 6ab453a3c5e23677d0c178cf4ea464c7a9e7ebf6
Pull Request resolved: https://github.com/facebook/react-forget/pull/2932
2024-05-03 10:46:36 -07:00
Moti Zilberman
e3fbb51db6 [DevTools] Enable inspected element context menu in Fusebox (#28972)
## Summary

Enables the inspected element context menu in React Native DevTools
(Fusebox).

## How did you test this change?

1. `yarn build` in `react-devtools-fusebox`
2. Copy artifacts to rn-chrome-devtools-frontend
3. Manually test the context menu


https://github.com/facebook/react/assets/2246565/b35cc20f-8d67-43b0-b863-7731e10fffac

NOTE: The serialised values sometimes expose React internals (e.g. Hook
data structures instead of just the values), but that seems to be a
problem equally on web, so I'm going for native<->web parity here.
2024-05-03 17:33:21 +01:00
Moti Zilberman
8f7dd5592b [DevTools] Check in frontend.d.ts for react-devtools-fusebox, include in build output (#28970)
## Summary

The `react-devtools-fusebox` private package is used in the React Native
DevTools (Fusebox) frontend by checking build artifacts into RN's
[fork]([`facebookexperimental/rn-chrome-devtools-frontend`](https://github.com/facebookexperimental/rn-chrome-devtools-frontend))
of the Chrome DevTools (CDT) repo - see
https://github.com/facebookexperimental/rn-chrome-devtools-frontend/pull/22.

Currently, the CDT fork also includes a [manually written TypeScript
definition
file](1d5f8d5209/front_end/third_party/react-devtools/package/frontend.d.ts)
which describes `react-devtools-fusebox`'s API. This PR moves that file
into the React repo, next to the implementation of
`react-devtools-fusebox`, so we can update it atomically with changes to
the package.

As this is the first bit of TypeScript in this repo, the PR adds minimal
support for formatting `.d.ts` files with Prettier. It also opts out
`react-devtools-fusebox/dist/` from linting/formatting as a drive-by
fix.

For now, we'll just maintain the `.d.ts` file manually, but we could
consider leveraging
[`flow-api-translator`](https://www.npmjs.com/package/flow-api-translator)
to auto-generate it in the future.

## How did you test this change?

Build `react-devtools-fusebox`, observe that `dist/frontend.d.ts`
exists.
2024-05-03 17:32:41 +01:00
Jack Pope
1beb73de0f Add flag to test fast jsx (#28816)
Following #28768, add a path to testing Fast JSX on www.

We want to measure the impact of Fast JSX and enable a path to testing
before string refs are completely removed in www (which is a work in
progress).

Without `disableStringRefs`, we need to copy any object with a `ref` key
so we can pass it through `coerceStringRef()` and copy it into the
object. This de-opt path is what is gated behind
`enableFastJSXWithStringRefs`.

The additional checks should have no perf impact in OSS as the flags
remain true there and the build output is not changed. For www, I've
benchmarked the addition of the boolean checks with values cached at
module scope. There is no significant change observed from our
benchmarks and any latency will apply to test and control branches
evenly. This added experiment complexity is temporary. We should be able
to clean it up, along with the flag checks for `enableRefAsProp` and
`disableStringRefs` shortly.
2024-05-03 10:47:13 -04:00
Timothy Yung
1d618a9cf3 Enable Wave 1 of Feature Flags for React Native (#28977) 2024-05-02 20:01:30 -07:00
Joe Savona
82137ec184 Rename react-compiler-runtime
ghstack-source-id: c6c825f5efdb4f9c413050b22b7713966871338c
Pull Request resolved: https://github.com/facebook/react-forget/pull/2931
2024-05-02 17:14:26 -07:00
Joe Savona
1c8f8bfb89 Rename ReactForgetBabelPlugin locals
ghstack-source-id: 9cd5fe41ae322632dfeb3d5c7468268678006d94
Pull Request resolved: https://github.com/facebook/react-forget/pull/2929
2024-05-02 17:14:25 -07:00
Joe Savona
95e3cdcfa1 Rename {r,R}unReactForgetBabelPlugin
ghstack-source-id: e7f00aab61dd749dc6ba11ce7e4d14603bfeeab9
Pull Request resolved: https://github.com/facebook/react-forget/pull/2928
2024-05-02 17:14:24 -07:00
Joe Savona
b844766e6c Update references to Forget to React Compiler
ghstack-source-id: cce73f26b7b3903b8d79b70dbc24cbee09693d81
Pull Request resolved: https://github.com/facebook/react-forget/pull/2927
2024-05-02 14:28:06 -07:00
Joe Savona
48e0c70292 Rename babel plugin
ghstack-source-id: bb66913e2d3c814696311371ed655f3da03d1199
Pull Request resolved: https://github.com/facebook/react-forget/pull/2926
2024-05-02 14:12:33 -07:00
Sebastian Silbermann
f5f2799a8d DevTools: Fix inspecting components with multiple reads of the same Context in React 17 (#28974) 2024-05-02 22:08:41 +02:00
Sebastian Silbermann
14f71db6b6 Devtools: Fix build-for-devtools (#28976) 2024-05-02 21:58:51 +02:00
Sebastian Silbermann
c44e9d1557 Devtools: Streamline getting extension from branch (#28975) 2024-05-02 21:47:18 +02:00
Dmytro Rykun
73bcdfbae5 Introduce a faster version of the addProperties function (#28969)
## Summary

This PR introduces a faster version of the `addProperties` function.
This new function is basically the `diffProperties` with `prevProps` set
to `null`, propagated constants, and all the unreachable code paths
collapsed.

## How did you test this change?

I've tested this change with [the benchmark
app](https://github.com/react-native-community/RNNewArchitectureApp/tree/new-architecture-benchmarks)
and got ~4.4% improvement in the view creation time.
2024-05-02 17:10:13 +01:00
Josh Story
c7b1ae5a9e [Tooling] Update critical artifact list (#28966)
When a React PR is opened CI will report large size changes. But for
critical packages like react-dom it reports always. In React 19 we moved
the build for react-dom the client reconciler from react-dom to
react-dom/client

This change adds react-dom-client artifacts for stable and oss channels
since that is originally what was being tracked. But since
react-dom/client always imports react-dom I left the original react-dom
packages as critical as well. They are small but it would be good to
keep an eye on them
2024-05-02 07:39:10 -07:00
Juan Pinilla
29d3c83f0a ReactDOM: Fix missing form data when the submitter is outside the form (#28056) 2024-05-02 13:06:28 +02:00
Lauren Tan
fddc0151fe Remove duplicated hermes-parser
ghstack-source-id: 79f3319d87909d05731ef821d0ffe86cb01b0432
Pull Request resolved: https://github.com/facebook/react-forget/pull/2920
2024-04-30 21:34:36 -04:00
Lauren Tan
f93529e0be Don't compile node_modules by default
To make a first time setup of the compiler truly config-less, default to
not compiling node_modules unless a user provided `sources` (advanced
option) is provided

ghstack-source-id: b0798052404d772ce6ee471e577699d4b0871d56
Pull Request resolved: https://github.com/facebook/react-forget/pull/2919
2024-04-30 21:34:33 -04:00
Jan Kassens
ebe58ee619 Update babel plugin to use runtime from react/compiler-runtime (#2918)
This uses the compiler runtime from `react/compiler-runtime` by default unless `compilerRuntime` is specifified in the Babel options which then imports the runtime from there. The `useMemoCache` hook is now named `c` in accordance with 4508873393

Unfortunately, I couldn't figure out how to import `react@beta` which already has that import as various react verstions were conflicting. If someone can figure this out it'd be fantastic. As a result, I had to update the default for the test runner to default the `compilerRuntime` option to `react` to preserve the previous behavior to import from `react`. Once upgraded to React 19, we should be able to remove that override.
2024-05-01 13:53:44 -04:00
Sathya Gunsasekaran
85e6e9b469 [hir] Mark MethodCalls as escaping
Treat MethodCalls similar to general CallExpressions and mark them
as escaping in PruneNonEscapingScopes pass.

ghstack-source-id: 3c81bdb17f58fbeef8be24e7cb363172d1867217
Pull Request resolved: https://github.com/facebook/react-forget/pull/2925
2024-05-01 15:58:57 +01:00
Sathya Gunsasekaran
4ce5c56fee [healthcheck] Refactor checks into separate files
Makes it easier to extend later, if we want to add more checks.

ghstack-source-id: 6fb3435555f1b988e1a185bfda8be9418eb622c5
Pull Request resolved: https://github.com/facebook/react-forget/pull/2924
2024-05-01 16:05:11 +01:00
Sathya Gunsasekaran
c464445b91 [healthcheck] Check for incompatible libraries
Add a configurable list of known incompatible libraries.

Check all package.jsons for any uses of known incompatible libraries and
warn if found.

ghstack-source-id: 7329e3792b57458e681780cba3140a14a9b1a60d
Pull Request resolved: https://github.com/facebook/react-forget/pull/2923
2024-05-01 15:34:40 +01:00
Sathya Gunsasekaran
36d775596f [healthcheck] Check for StrictMode
ghstack-source-id: f05222073be785b77346c4e8760bf4d0bb4d658e
Pull Request resolved: https://github.com/facebook/react-forget/pull/2922
2024-05-01 13:35:06 +01:00
Sathya Gunsasekaran
c88d356603 [healthcheck] Add status message
Show compiling status message and not just block
UI.

ghstack-source-id: 67761c5d32216e105c4aa6404dfa07d76ae22583
Pull Request resolved: https://github.com/facebook/react-forget/pull/2921
2024-05-01 13:35:03 +01:00
Lauren Tan
86e0f87a5b Dynamically detect reanimated plugin
Enables the Reanimated flag automatically if we find reanimated in the
user's list of plugins

ghstack-source-id: 20e83374612362a30d6c8cc7a903d9320e8cc23a
Pull Request resolved: https://github.com/facebook/react-forget/pull/2915
2024-04-30 14:24:03 -04:00
Jan Kassens
4508873393 Move useMemoCache hook to react/compiler-runtime (#28954)
Move useMemoCache hook to react/compiler-runtime

For Meta-internal purposes, we keep the export on `react` itself to
reduce churn.
2024-04-30 12:00:22 -04:00
Pieter De Baets
d779eba4b3 [react-native] Add unit test to ReactNativeAttributePayload (#28955)
## Summary

I'm looking at cleaning up some unnecessary manual property flattening
in React Native and wanted to verify this behaviour is working as
expected, where properties from nested objects will always overwrite
properties from the base object.

## How did you test this change?

Unit tests
2024-04-29 19:57:32 -07:00
Sebastian Silbermann
190cc990e0 Import correct prod version of jsx-dev-runtime for react-server (#28939) 2024-04-27 22:25:25 +02:00
Alex Yang
8090457c77 fix: add react-server condition for react/jsx-dev-runtime (#28921) 2024-04-27 21:45:52 +02:00
Ricky
4ddff7355f Add changelog for 18.3.1 (#28932) 2024-04-26 16:03:03 -04:00
Ricky
95e610da13 Add changelog for 18.3 (#28929)
Fixes https://github.com/facebook/react/issues/28924
2024-04-26 12:45:08 -04:00
Ricky
c4083616a2 Create React 19 issue template 2024-04-25 15:32:31 -04:00
Lauren Tan
1181043d80 Disable ValidateNoCapitalizedCalls by default
As discussed earlier, let's disable this validation pass by default for
oss since it still has many false positives

ghstack-source-id: fa3c21dde7cc4c3e4bb91dfa707e64bc7a9e088b
Pull Request resolved: https://github.com/facebook/react-forget/pull/2908
2024-04-25 14:19:21 -04:00
Mofei Zhang
18f4a388a9 [hir-rewrite] Remove breaks to HIR ScopeTerminal after converting to reactiveFunction IR
ghstack-source-id: 67e979e1533b23ced762973e239a4290111e2242
Pull Request resolved: https://github.com/facebook/react-forget/pull/2911
2024-04-29 14:08:30 -04:00
Mofei Zhang
6db5f36aa7 [tests] Add fixtures showing HIR rewrite changes
ghstack-source-id: 5e8b1680e95d112e1e5b2cb8c4e6c414360e0a77
Pull Request resolved: https://github.com/facebook/react-forget/pull/2900
2024-04-29 14:08:29 -04:00
Mofei Zhang
2b2d305199 [hir] Rewrite buildReactiveBlocks -> buildReactiveScopeTerminalsHIR
ghstack-source-id: 1e804dd31d4b5f74070c94c0ea56f55539e46703
Pull Request resolved: https://github.com/facebook/react-forget/pull/2853
2024-04-29 14:08:29 -04:00
Josh Story
94eed63c49 (Land #28798) Move Current Owner (and Cache) to an Async Dispatcher (#28912)
Rebasing and landing https://github.com/facebook/react/pull/28798

This PR was approved already but held back to give time for the sync.
Rebased and landing here without pushing to seb's remote to avoid
possibility of lost updates

---------

Co-authored-by: Sebastian Markbage <sebastian@calyptus.eu>
2024-04-25 10:40:40 -07:00
Andrew Clark
f8a8eac86b Update canary channel label to "beta" (#28905)
During the beta period, canaries will be published as
`19.0.0-beta-<COMMIT_SHA>-<DATE>`. They will also be tagged as `beta`
when published to npm.
2024-04-25 13:14:33 -04:00
Sebastian Silbermann
82d8129e58 Reconciler: Change commitUpdate signature to account for unused updatePayload parameter (#28909) 2024-04-25 19:14:06 +02:00
Andrew Clark
d285b3acba Go back to shared refs instance object (#28911)
It turns out we already made refs writable in #25696, which has been in
canary for over a year. The approach in that PR also has the benefit of
being slightly more perf sensitive because it still uses a shared object
until the fiber is mounted. So let's just go back to that.
2024-04-25 13:03:21 -04:00
Jan Kassens
ed71a3ad29 Support ref cleanup function for imperative handle refs (#28910)
Support ref cleanup function for imperative handle refs
2024-04-25 12:51:41 -04:00
Joe Savona
1c7e34cdb6 Stop reporting errors on mutations of primitives (and associated false positives)
ghstack-source-id: 4dc9b3c51953f8ed2184ebc5e438bc877959b5d0
Pull Request resolved: https://github.com/facebook/react-forget/pull/2903
2024-04-25 09:39:20 -07:00
Joe Savona
0a7f4427f1 Fix invariant on mutable context values
ghstack-source-id: cb105318bf66d876a546b1c52e28286839d30032
Pull Request resolved: https://github.com/facebook/react-forget/pull/2904
2024-04-25 08:00:56 -07:00
Mofei Zhang
59e37b088b [HIR] Detect more cases of invalid block nesting
ghstack-source-id: 9e08fa69807ee1b5e99675f9aefc8acd13c16827
Pull Request resolved: https://github.com/facebook/react-forget/pull/2899
2024-04-26 12:40:36 -04:00
Mofei Zhang
a44559b0a5 [hir] Re-implement mergeOverlappingReactiveScopes (+ bugfix)
ghstack-source-id: 06d49edffe5ae3c31eb6ef642078752c056c617c
Pull Request resolved: https://github.com/facebook/react-forget/pull/2852
2024-04-26 12:40:35 -04:00
Mofei Zhang
f196e1f703 [hir][patch] Fix small bug in duplicate scope merging logic
ghstack-source-id: 98966dd4c2b264329acad077427febc2a34a5edc
Pull Request resolved: https://github.com/facebook/react-forget/pull/2906
2024-04-25 11:07:22 -04:00
Jack Pope
cf5ab8b8b2 Add descriptions of new methods to the react-reconciler readme (#28750)
Add new reconciler methods since last breaking change to the README
based on usage and comments.

---------

Co-authored-by: Josh Story <josh.c.story@gmail.com>
2024-04-25 09:37:55 -04:00
Andrew Clark
c516cefc7d warn -> error for Test Renderer deprecation (#28904)
We use `console.error` for deprecations. `console.warn` is for less
critical issues, like performance anti-patterns.
2024-04-24 14:54:39 -04:00
Josh Story
cb151849e1 [react-dom] move all client code to react-dom/client (#28271)
This PR reorganizes the `react-dom` entrypoint to only pull in code that
is environment agnostic. Previously if you required anything from this
entrypoint in any environment the entire client reconciler was loaded.
In a prior release we added a server rendering stub which you could
alias in server environments to omit this unecessary code. After landing
this change this entrypoint should not load any environment specific
code.

While a few APIs are truly client (browser) only such as createRoot and
hydrateRoot many of the APIs you import from this package are only
useful in the browser but could concievably be imported in shared code
(components running in Fizz or shared components as part of an RSC app).
To avoid making these require opting into the client bundle we are
keeping them in the `react-dom` entrypoint and changing their
implementation so that in environments where they are not particularly
useful they do something benign and expected.

#### Removed APIs
The following APIs are being removed in the next major. Largely they
have all been deprecated already and are part of legacy rendering modes
where concurrent features of React are not available
* `render`
* `hydrate`
* `findDOMNode`
* `unmountComponentAtNode`
* `unstable_createEventHandle`
* `unstable_renderSubtreeIntoContainer`
* `unstable_runWithPrioirty`

#### moved Client APIs
These APIs were available on both `react-dom` (with a warning) and
`react-dom/client`. After this change they are only available on
`react-dom/client`
* `createRoot`
* `hydrateRoot`

#### retained APIs
These APIs still exist on the `react-dom` entrypoint but have normalized
behavior depending on which renderers are currently in scope
* `flushSync`: will execute the function (if provided) inside the
flushSync implemention of FlightServer, Fizz, and Fiber DOM renderers.
* `unstable_batchedUpdates`: This is a noop in concurrent mode because
it is now the only supported behavior because there is no legacy
rendering mode
* `createPortal`: This just produces an object. It can be called from
anywhere but since you will probably not have a handle on a DOM node to
pass to it it will likely warn in environments other than the browser
* preloading APIS such as `preload`: These methods will execute the
preload across all renderers currently in scope. Since we resolve the
Request object on the server using AsyncLocalStorage or the current
function stack in practice only one renderer should act upon the
preload.

In addition to these changes the server rendering stub now just rexports
everything from `react-dom`. In a future minor we will add a warning
when using the stub and in the next major we will remove the stub
altogether
2024-04-24 08:50:32 -07:00
Jan Kassens
b039be627d Unrevert "Support writing to this.refs from userspace" (#28879)
Reverts facebook/react#28877

We found the cause of the regression and should be able to land this
again.
2024-04-24 10:03:09 -04:00
Ricky
6f6e375fce Create short link for jsx warning (#28899)
Short link created in https://github.com/reactjs/react.dev/pull/6772
2024-04-24 09:32:11 -04:00
Sebastian Silbermann
6f18664b82 eslint-plugin-react-hooks: Add support for ESLint v9 (#28773) 2024-04-23 23:29:01 +02:00
Mofei Zhang
f22dd62ab8 [HIR] Followup to pruneUnusedLabelsHIR (#2866)
Followup to https://github.com/facebook/react-forget/pull/2866

ghstack-source-id: 087d5da53787cb7cff6495b6a791326ff8a952b4
Pull Request resolved: https://github.com/facebook/react-forget/pull/2896
2024-04-23 19:58:31 +01:00
Joe Savona
2d569a3353 Add detection of dynamic hooks
ghstack-source-id: 3acfa4fde5dfd65353fa407378057ca937ee3599
Pull Request resolved: https://github.com/facebook/react-forget/pull/2901
2024-04-24 15:15:34 -07:00
Lauren Tan
89f2f7fcef Make DropManualMemoization error message more accurate
This was probably a leftover from a previous time, but since this error
message throws when the dependency list is not an array literal, and not
just when its a rest spread, this PR updates the message to match.

ghstack-source-id: 28f2338212e56a67d3d477cea5abb6e9f3826488
Pull Request resolved: https://github.com/facebook/react-forget/pull/2902
2024-04-25 10:19:29 -04:00
Joe Savona
08b777d6dc [be] Rename NextIterableOf -> AdvanceIterator
ghstack-source-id: 024ec00511483cf342d851fde001c96348a2d3c6
Pull Request resolved: https://github.com/facebook/react-forget/pull/2898
2024-04-24 14:59:09 -07:00
Joe Savona
21b2af8ba0 More precisely model for..of semantics with separate init/test blocks
ghstack-source-id: 827c35466d04f7b7143ddf1adefeff926d4b22cd
Pull Request resolved: https://github.com/facebook/react-forget/pull/2893
2024-04-24 14:59:08 -07:00
Joe Savona
da004f1885 Add GetIterator instruction
ghstack-source-id: 8ed2deed7606c48414de74cb2781a5fde6cd6f59
Pull Request resolved: https://github.com/facebook/react-forget/pull/2892
2024-04-24 14:59:08 -07:00
Joe Savona
6304e98902 Repro for for..of scoping bug
ghstack-source-id: 6147b6e70d24203350664e8ab24691d1ec5c5b2a
Pull Request resolved: https://github.com/facebook/react-forget/pull/2891
2024-04-24 14:59:07 -07:00
Andrew Clark
a94838df1c Remove automatic fetch cache instrumentation (#28896)
This removes the automatic patching of the global `fetch` function in
Server Components environments to dedupe requests using `React.cache`, a
behavior that some RSC framework maintainers have objected to.

We may revisit this decision in the future, but for now it's not worth
the controversy.

Frameworks that have already shipped this behavior, like Next.js, can
reimplement it in userspace.

I considered keeping the implementation in the codebase and disabling it
by setting `enableFetchInstrumentation` to `false` everywhere, but since
that also disables the tests, it doesn't seem worth it because without
test coverage the behavior is likely to drift regardless. We can just
revert this PR later if desired.
2024-04-23 14:14:12 -04:00
Jack Pope
d4e78c42a9 Add ref callback test for cleanup fn vs null call (#28895)
Used this test scenario to clarify how callback refs work when detached
based on the availability of a cleanup function to update documentation
in https://github.com/reactjs/react.dev/pull/6770

Checking it in for additional test coverage and test-based documentation
2024-04-23 12:13:09 -04:00
Sathya Gunsasekaran
f8c0363a92 [healthcheck] Rename package to healthcheck
Make it one word, instead of two words and a hyphen.

ghstack-source-id: ef05e8acbd7cf17e2dd04c362dcee785eda79e82
Pull Request resolved: https://github.com/facebook/react-forget/pull/2895
2024-04-23 12:33:51 +01:00
Sathya Gunsasekaran
e0dbe47ad6 [healthcheck] Only count actionable failures
It's not useful to output count of all failures,
as it's not actionable for the developer.

We'll still capture all failures in case we want
to add a rage option to this script.

ghstack-source-id: 4d5a1dd6a9616e6fd5e1166bb97fa047829b9273
Pull Request resolved: https://github.com/facebook/react-forget/pull/2889
2024-04-23 12:28:43 +01:00
Sathya Gunsasekaran
dff05237c5 [healthcheck] Compile sources
Run the compiler on the globbed soruces.

The logger is used to capture the success and
failure compilation cases at the component level.
(If we were to compile the entire file directly,
we wouldn't get this granularity)

For now, we just log the number of success and
failures. In the future, we can provide a better
report building on this.

ghstack-source-id: 6d2d918190b6ed5d42b795491bbce29a950b9741
Pull Request resolved: https://github.com/facebook/react-forget/pull/2888
2024-04-23 12:28:42 +01:00
Sathya Gunsasekaran
8d234c64eb [hir] Remove hermes parser
Exporting the hermes parser breaks the playground
as the hermes parser can not work in the browser.

No one is using this directly anyway -- snap and
others bundle hermes parser on their own, so,
let's remove it.

ghstack-source-id: d448c346eb137f8ba6ada4ad113e41a90b29baff
Pull Request resolved: https://github.com/facebook/react-forget/pull/2890
2024-04-23 12:28:42 +01:00
Sathya Gunsasekaran
9e15ade177 [healthcheck] Read files from globbed path
ghstack-source-id: a79336c2b1c85ae4d5b941ffbeffb3df10816448
Pull Request resolved: https://github.com/facebook/react-forget/pull/2887
2024-04-23 12:28:41 +01:00
Sathya Gunsasekaran
a6cc5bc874 [healthcheck] Read glob of src path
Use yargs to parse input of glob expression
matching the path of src files to compile.

ghstack-source-id: 6a35e958428cd08ef5c96e0014e072d3faf04064
Pull Request resolved: https://github.com/facebook/react-forget/pull/2886
2024-04-23 12:28:41 +01:00
Sathya Gunsasekaran
706e04722f [healthcheck] Init tsconfig
ghstack-source-id: 52339f8e5cde29d4e132762de2e4e4b31f25f8c1
Pull Request resolved: https://github.com/facebook/react-forget/pull/2885
2024-04-23 12:28:40 +01:00
Sathya Gunsasekaran
6019ef0a43 [healthcheck] New package for checking violations
This package will be used to check for violations
of the rules of react and other heuristics to
determine the health of a codebase.

The health of a codebase gives an expectation of
how easy it will be onboard on to the compiler.

ghstack-source-id: b52fc4e44f704e0544f15066d8905825a256dc4a
Pull Request resolved: https://github.com/facebook/react-forget/pull/2884
2024-04-23 12:28:40 +01:00
Mofei Zhang
10e11c4a88 [HIR] Assert program blocks and scopes are properly nested
ghstack-source-id: e3fe9b5fb8d1314e16e562581f40ca2b2a555b97
Pull Request resolved: https://github.com/facebook/react-forget/pull/2867
2024-04-23 10:18:50 +01:00
Mofei Zhang
40474b8eaf [HIR] Naive implementation of pruneUnusedLabels in HIR
ghstack-source-id: 6d55db3d5c0fd7736a59d48c6afb2de9b5729d16
Pull Request resolved: https://github.com/facebook/react-forget/pull/2866
2024-04-23 10:18:50 +01:00
Mofei Zhang
555100ca83 [HIR] Nonnullable block fallthroughs
ghstack-source-id: 6f24b60056c741e5d4e9836f91bc4fce0f9e8fdd
Pull Request resolved: https://github.com/facebook/react-forget/pull/2865
2024-04-23 10:18:49 +01:00
Mofei Zhang
1efb3cae0a [HIR] Add new unreachable terminal
ghstack-source-id: cffdbbbd78a0aa7e1587a234ce27366ea626ee5e
Pull Request resolved: https://github.com/facebook/react-forget/pull/2864
2024-04-23 10:18:48 +01:00
Mofei Zhang
d87ba1b9df [be] Stabilize block ids
ghstack-source-id: 83aedf9f086ccb4819655eab803f25f944b75c7b
Pull Request resolved: https://github.com/facebook/react-forget/pull/2851
2024-04-23 10:18:48 +01:00
Mofei Zhang
b21ca44efc [repro] bug repro
ghstack-source-id: 64d3266bf39ce5b1b9e9b9470910e029f0e951eb
Pull Request resolved: https://github.com/facebook/react-forget/pull/2849
2024-04-23 10:18:47 +01:00
Sebastian Silbermann
699d03ce1a Cleanup replayFailedUnitOfWorkWithInvokeGuardedCallbackand enableProfilerNestedUpdateScheduledHook (#28891) 2024-04-22 21:35:11 +02:00
Sebastian Markbåge
9f2eebd807 [Fiber/Fizz] Support AsyncIterable as Children and AsyncGenerator Client Components (#28868)
Stacked on #28849, #28854, #28853. Behind a flag.

If you're following along from the side-lines. This is probably not what
you think it is.

It's NOT a way to get updates to a component over time. The
AsyncIterable works like an Iterable already works in React which is how
an Array works. I.e. it's a list of children - not the value of a child
over time.

It also doesn't actually render one component at a time. The way it
works is more like awaiting the entire list to become an array and then
it shows up. Before that it suspends the parent.

To actually get these to display one at a time, you have to opt-in with
`<SuspenseList>` to describe how they should appear. That's really the
interesting part and that not implemented yet.

Additionally, since these are effectively Async Functions and uncached
promises, they're not actually fully "supported" on the client yet for
the same reason rendering plain Promises and Async Functions aren't.
They warn. It's only really useful when paired with RSC that produces
instrumented versions of these. Ideally we'd published instrumented
helpers to help with map/filter style operations that yield new
instrumented AsyncIterables.

The way the implementation works basically just relies on unwrapThenable
and otherwise works like a plain Iterator.

There is one quirk with these that are different than just promises. We
ask for a new iterator each time we rerender. This means that upon retry
we kick off another iteration which itself might kick off new requests
that block iterating further. To solve this and make it actually
efficient enough to use on the client we'd need to stash something like
a buffer of the previous iteration and maybe iterator on the iterable so
that we can continue where we left off or synchronously iterate if we've
seen it before. Similar to our `.value` convention on Promises.

In Fizz, I had to do a special case because when we render an iterator
child we don't actually rerender the parent again like we do in Fiber.
However, it's more efficient to just continue on where we left off by
reusing the entries from the thenable state from before in that case.
2024-04-22 13:25:05 -04:00
Sebastian Markbåge
3b551c8284 Rename the react.element symbol to react.transitional.element (#28813)
We have changed the shape (and the runtime) of React Elements. To help
avoid precompiled or inlined JSX having subtle breakages or deopting
hidden classes, I renamed the symbol so that we can early error if
private implementation details are used or mismatching versions are
used.

Why "transitional"? Well, because this is not the last time we'll change
the shape. This is just a stepping stone to removing the `ref` field on
the elements in the next version so we'll likely have to do it again.
2024-04-22 12:39:56 -04:00
Joe Savona
896d1b0027 [dx] Improve error message from InferReferenceEffects
ghstack-source-id: 06265d9676b671a5b02ca05433a219dd219be4f1
Pull Request resolved: https://github.com/facebook/react-forget/pull/2883
2024-04-22 08:14:35 -07:00
Joe Savona
b11074772b InferReferenceEffects defers function effects until used
ghstack-source-id: 186951086ac1dd8ebd75d6fdfca5c1a41e54a33e
Pull Request resolved: https://github.com/facebook/react-forget/pull/2881
2024-04-22 08:14:34 -07:00
Jack Pope
db913d8e17 Remove warning for ref cleanup function (#28883)
Resources
- RFC: https://github.com/reactjs/rfcs/pull/205
- Warning implemented in https://github.com/facebook/react/pull/22313
- Warning enabled in https://github.com/facebook/react/pull/23145
- Feature added in https://github.com/facebook/react/pull/25686

We have warned to prevent the old behavior since 18.0.0.

The new feature has been on in canary for a while but still triggering
the warning. This PR cleans up the warning for 19
2024-04-22 10:57:31 -04:00
Sebastian Markbåge
5b903cdaa9 [Flight] Support (Async) Generator ServerComponent (#28849)
Stacked on #28853 and #28854.

React supports rendering `Iterable` and will soon support
`AsyncIterable`. As long as it's multi-shot since during an update we
may have to rerender with new inputs an loop over the iterable again.
Therefore the `Iterator` and `AsyncIterator` types are not supported
directly as a child of React - and really it shouldn't pass between
Hooks or components neither for this reason. For parity, that's also the
case when used in Server Components.

However, there is a special case when the component rendered itself is a
generator function. While it returns as a child an `Iterator`, the React
Element itself can act as an `Iterable` because we can re-evaluate the
function to create a new generator whenever we need to.

It's also very convenient to use generator functions over constructing
an `AsyncIterable`. So this is a proposal to special case the
`Generator`/`AsyncGenerator` returned by a (Async) Generator Function.

In Flight this means that when we render a Server Component we can
serialize this value as an `Iterable`/`AsyncIterable` since that's
effectively what rendering it on the server reduces down to. That way if
Fiber can receive the result in any position.

For SuspenseList this would also need another special case because the
children of SuspenseList represent "rows".

`<SuspenseList><Component /></SuspenseList>` currently is a single "row"
even if the component renders multiple children or is an iterator. This
is currently different if Component is a Server Component because it'll
reduce down to an array/AsyncIterable and therefore be treated as one
row per its child. This is different from `<SuspenseList><Component
/><Component /></SuspenseList>` since that has a wrapper array and so
this is always two rows.

It probably makes sense to special case a single-element child in
`SuspenseList` to represent a component that generates rows. That way
you can use an `AsyncGeneratorFunction` to do this.
2024-04-21 13:10:10 -04:00
Sebastian Markbåge
bf426f9b1d [Flight / Flight Reply] Encode Iterator separately from Iterable (#28854)
For [`AsyncIterable`](https://github.com/facebook/react/pull/28847) we
encode `AsyncIterator` as a separate tag.

Previously we encoded `Iterator` as just an Array. This adds a special
encoding for this. Technically this is a breaking change.

This is kind of an edge case that you'd care about the difference but it
becomes more important to treat these correctly for the warnings here
#28853.
2024-04-21 12:52:04 -04:00
Sebastian Markbåge
368202181e Warn for Child Iterator of all types but allow Generator Components (#28853)
This doesn't change production behavior. We always render Iterables to
our best effort in prod even if they're Iterators.

But this does change the DEV warnings which indicates which are valid
patterns to use.

It's a footgun to use an Iterator as a prop when you pass between
components because if an intermediate component rerenders without its
parent, React won't be able to iterate it again to reconcile and any
mappers won't be able to re-apply. This is actually typically not a
problem when passed only to React host components but as a pattern it's
a problem for composability.

We used to warn only for Generators - i.e. Iterators returned from
Generator functions. This adds a warning for Iterators created by other
means too (e.g. Flight or the native Iterator utils). The heuristic is
to check whether the Iterator is the same as the Iterable because that
means it's not possible to get new iterators out of it. This case used
to just yield non-sense like empty sets in DEV but not in prod.

However, a new realization is that when the Component itself is a
Generator Function, it's not actually a problem. That's because the
React Element itself works as an Iterable since we can ask for new
generators by calling the function again. So this adds a special case to
allow the Generator returned from a Generator Function's direct child.
The principle is “don’t pass iterators around” but in this case there is
no iterator floating around because it’s between React and the JS VM.

Also see #28849 for context on AsyncIterables.

Related to this, but Hooks should ideally be banned in these for the
same reason they're banned in Async Functions.
2024-04-21 12:51:45 -04:00
Joe Savona
f39dd1b82f InferReferenceEffects s/reference/referenceAndRecordEffects/
ghstack-source-id: 4d7439e7ada77e65991869593aa6a5d88d5ad600
Pull Request resolved: https://github.com/facebook/react-forget/pull/2882
2024-04-20 12:18:28 -07:00
Andrew Clark
857ee8cdf9 Don't minify symbols in production builds (#28881)
This disables symbol renaming in production builds. The original
variable and function names are preserved. All other forms of
compression applied by Closure (dead code elimination, inlining, etc)
are unchanged — the final program is identical to what we were producing
before, just in a more readable form.

The motivation is to make it easier to debug React issues that only
occur in production — the same reason we decided to start shipping
sourcemaps in #28827 and #28827.

However, because most apps run their own minification step on their npm
dependencies, it's not necessary for us to minify the symbols before
publishing — it'll be handled the app, if desired.

This is the same strategy Meta has used to ship React for years. The
React build itself has unminified symbols, but they get minified as part
of Meta's regular build pipeline.

Even if an app does not minify their npm dependencies, gzip covers most
of the cost of symbol renaming anyway.

This saves us from having to ship sourcemaps, which means even apps that
don't have sourcemaps configured will be able to debug the React build
as easily as they would any other npm dependency.
2024-04-20 11:23:46 -04:00
Joe Savona
c98c02f8e6 InferReferenceEffects propagates context effects with precise locations
ghstack-source-id: f9517b98013613a3a80a78b4a6110006ee8b4de7
Pull Request resolved: https://github.com/facebook/react-forget/pull/2880
2024-04-19 19:03:01 -07:00
Joe Savona
f7a643c7b2 Add ContextMutation variant of FunctionEffect
ghstack-source-id: f3da80ed22acd916db0cecad9f2b80ba4c01c6eb
Pull Request resolved: https://github.com/facebook/react-forget/pull/2879
2024-04-19 17:44:08 -07:00
Joe Savona
12f314fa98 Extend ValueReason w context identifiers
ghstack-source-id: 5157b988b0ff15299d585aa906831c1d640f6a40
Pull Request resolved: https://github.com/facebook/react-forget/pull/2878
2024-04-19 17:44:04 -07:00
Andrew Clark
ea26e38e33 [Experiment] Reuse memo cache after interruption (#28878)
Adds an experimental feature flag to the implementation of useMemoCache,
the internal cache used by the React Compiler (Forget).

When enabled, instead of treating the cache as copy-on-write, like we do
with fibers, we share the same cache instance across all render
attempts, even if the component is interrupted before it commits.

If an update is interrupted, either because it suspended or because of
another update, we can reuse the memoized computations from the previous
attempt. We can do this because the React Compiler performs atomic
writes to the memo cache, i.e. it will not record the inputs to a
memoization without also recording its output.

This gives us a form of "resuming" within components and hooks.

This only works when updating a component that already mounted. It has
no impact during initial render, because the memo cache is stored on the
fiber, and since we have not implemented resuming for fibers, it's
always a fresh memo cache, anyway.

However, this alone is pretty useful — it happens whenever you update
the UI with fresh data after a mutation/action, which is extremely
common in a Suspense-driven (e.g. RSC or Relay) app.

So the impact of this feature is faster data mutations/actions (when the
React Compiler is used).
2024-04-19 19:30:01 -04:00
Joe Savona
9f9f4a9f11 Fix gating mode hoisting check to skip type references
ghstack-source-id: f83e9e28c1cdf31dba718e62db12aaa3a5f9ddab
Pull Request resolved: https://github.com/facebook/react-forget/pull/2876
2024-04-19 15:16:19 -07:00
Joe Savona
07ba52bc1a Repro for false positive gating mode hoisting check
ghstack-source-id: c4f5777b85751e9d5f0323384160adfba55d883f
Pull Request resolved: https://github.com/facebook/react-forget/pull/2875
2024-04-19 15:16:15 -07:00
Sebastian Markbåge
446aa9a632 Build react-reconciler for FB builds (#28880)
Meta uses various tools built on top of the "react-reconciler" package
but that package needs to match the version of the "react" package.

This means that it should be synced at the same time. However, more than
that the feature flags between the "react" package and the
"react-reconciler" package needs to line up. Since FB has custom feature
flags, it can't use the OSS version of react-reconciler.
2024-04-19 18:06:01 -04:00
Andrew Clark
0e0b69321a Run Closure on non-minified prod builds, too (#28827)
In #26446 we started publishing non-minified versions of our production
build artifacts, along with source maps, for easier debugging of React
when running in production mode.

The way it's currently set up is that these builds are generated
*before* Closure compiler has run. Which means it's missing many of the
optimizations that are in the final build, like dead code elimination.

This PR changes the build process to run Closure on the non-minified
production builds, too, by moving the sourcemap generation to later in
the pipeline.

The non-minified builds will still preserve the original symbol names,
and we'll use Prettier to add back whitespace. This is the exact same
approach we've been using for years to generate production builds for
Meta.

The idea is that the only difference between the minified and non-
minified builds is whitespace and symbol mangling. The semantic
structure of the program should be identical.

To implement this, I disabled symbol mangling when running Closure
compiler. Then, in a later step, the symbols are mangled by Terser. This
is when the source maps are generated.
2024-04-19 14:22:38 -04:00
Jan Kassens
f5ce642dee Revert "Support writing to this.refs from userspace" (#28877)
Reverts facebook/react#28867

It broke some tests, reverting until we figure out why to avoid having
too much delay in the sync.
2024-04-19 12:35:06 -04:00
Josh Story
33a32441e9 Remove renderToStaticNodeStream (#28873)
Stacked on #28872 

renderToStaticNodeStream was not originally deprecated when
renderToNodeStream was deprecated because it did not yet have a clear
analog in the modern streaming implementation for SSR. In React 19 we
have already removed renderToNodeStream. This change removes
renderToStaticNodeStream as well because you can replicate it's
semantics using renderToPipeableStream with onAllReady or
renderToReadableStream with await stream.allready.
2024-04-18 21:06:04 -07:00
Josh Story
d329ff9d9e Deprecate renderToStaticNodeStream (#28872)
This commit adds warnings indicating that `renderToStaticNodeStream`
will be removed in an upcoming React release. This API has been legacy,
is not widely used (renderToStaticMarkup is more common) and has
semantically eqiuvalent implementations with renderToReadableStream and
renderToPipeableStream.
2024-04-18 18:33:53 -07:00
Josh Story
561c023708 [Fizz] escape <script> textContent similar to bootstrapScript (#28871)
stacked on #28870 

inline script children have been encoded as HTML for a while now but
this can easily break script parsing so practically if you were
rendering inline scripts you were using dangerouslySetInnerHTML. This is
not great because now there is no escaping at all so you have to be even
more careful. While care should always be taken when rendering untrusted
script content driving users to use dangerous APIs is not the right
approach and in this PR the escaping functionality used for
bootstrapScripts and importMaps is being extended to any inline script.

the approach is to escape 's' or 'S" with the appropriate unicode code
point if it is inside a <script or </script sequence. This has the nice
benefit of minimally escaping the text for readability while still
preserving full js parsing capabilities. As articulated when we
introduced this escaping for prior use cases this is only safe because
we are escaping the entire script content. It would be unsafe if we were
not escaping the entirety of the script because we would no longer be
able to ensure there are no earlier or later <script sequences that put
the parser in unexpected states.
2024-04-18 17:38:43 -07:00
Josh Story
aead514db2 [Fizz] escape <style> textContent as css (#28870)
style text content has historically been escaped as HTML which is
non-sensical and often leads users to using dangerouslySetInnerHTML as a
matter of course. While rendering untrusted style rules is a security
risk React doesn't really provide any special protection here and
forcing users to use a completely unescaped API is if anything worse. So
this PR updates the style escaping rules for Fizz to only escape the
text content to ensure the tag scope cannot be closed early. This is
accomplished by encoding "s" and "S" as hexadecimal unicode
representation "\73 " and "\53 " respectively when found within a
sequence like </style>. We have to be careful to support casing here
just like with the script closing tag regex for bootstrap scripts.
2024-04-18 17:24:27 -07:00
Jan Kassens
4c34a7ffc5 Add missing bundle types for ReactCacheOld (#28860)
Add missing bundle types for ReactCacheOld

These are used at FB and we need to update them for the SecretInternals
update.
2024-04-18 17:26:03 -04:00
Jason Bonta
92f5c3ac7b [Devtools] Rename Forget badge (#28858)
## Summary

The Forget codename needs to be hidden from the UI to avoid confusion.
Going forward, we'll be referring to this set of features as part of the
larger React compiler. We'll be describing the primary feature that
we've built so far as auto-memoization, and this badge helps devs see
which components have been automatically memoized by the compiler.

## How did you test this change?

- force Forget badge on with and without the presence of other badges
- confirm colors/UI in light and dark modes
- force badges on for `ElementBadges`, `InspectableElementBadges`,
`IndexableElementBadges`
- Running yarn start in packages/react-devtools-shell

[demo
video](https://github.com/facebook/react/assets/973058/fa829018-7644-4425-8395-c5cd84691f3c)
2024-04-18 13:55:53 -07:00
Joe Savona
a78da09640 Move global reassignment validation to InferReferenceEffects
ghstack-source-id: fcda8140310d6e1201df35f399020b22b6dccb08
Pull Request resolved: https://github.com/facebook/react-forget/pull/2872
2024-04-18 13:51:47 -07:00
Jan Kassens
1cd77a4ff7 Remove ReactFlightFB bundles (#28864)
Remove ReactFlightFB bundles
2024-04-18 16:41:04 -04:00
Joe Savona
d870c979a1 Add StoreGlobal instruction
ghstack-source-id: a715b1385da6648d867118d2b7486ffdb0ff89f6
Pull Request resolved: https://github.com/facebook/react-forget/pull/2871
2024-04-18 13:21:31 -07:00
Jon Janzen
1f0701c11e Write Diff Train import branch to this repo (#28869)
For fbsource we've historically used a separate repo for imports due to
internal limitations in Diff Train. Those have been lifted so we can now
commit this branch here and then we can import from this repo (and get
rid of the other repo)
2024-04-18 12:08:52 -07:00
Joe Savona
682f5a920a [dx] Update capitalized call error messages
ghstack-source-id: dbc012018ecc73007dfb3e745615605512ab6867
Pull Request resolved: https://github.com/facebook/react-forget/pull/2870
2024-04-18 11:07:10 -07:00
Andrew Clark
ea24427d16 Support writing to this.refs from userspace (#28867)
Previously, the `refs` property of a class component instance was
read-only by user code — only React could write to it, and until/unless
a string ref was used, it pointed to a shared empty object that was
frozen in dev to prevent userspace mutations.

Because string refs are deprecated, we want users to be able to codemod
all their string refs to callback refs. The safest way to do this is to
output a callback ref that assigns to `this.refs`.

So to support this, we need to make `this.refs` writable by userspace.
2024-04-18 13:37:54 -04:00
Joe Savona
a9732d65b4 [dx] Improve error messages for unpreserved memoization
ghstack-source-id: ff5bcaa7ab219035f57dc3dc3396c9a324896d4b
Pull Request resolved: https://github.com/facebook/react-forget/pull/2869
2024-04-18 09:49:27 -07:00
Dmytro Rykun
0061ca6cf4 Add early return to diffProperties (#28842)
## Summary

This PR adds early return to the `diff` function. We don't need to go
through all the entries of `nextProps`, process and deep-diff the values
if `nextProps` is the same object as `prevProps`. Roughly 6% of all
`diffProperties` calls can be skipped.

## How did you test this change?

RNTester.
2024-04-18 17:24:07 +01:00
Joe Savona
da6c4612a3 [dx] Consistently use backticks for quoting input in error messages
ghstack-source-id: 34e5507c08fb883c987c88f415158fac781a5f8e
Pull Request resolved: https://github.com/facebook/react-forget/pull/2863
2024-04-18 09:21:24 -07:00
Joe Savona
e3d0f4bce8 [dx] Update NoRefAccessInRender error messages
ghstack-source-id: a5623aa4f29e15c89d2dfbe855bab9843e240a8a
Pull Request resolved: https://github.com/facebook/react-forget/pull/2862
2024-04-18 09:21:23 -07:00
Joe Savona
8c8bd00c04 [dx] Update error messages for manual memo validation
ghstack-source-id: 1a88145049d7f7acb01748ad6ec4dd1500781766
Pull Request resolved: https://github.com/facebook/react-forget/pull/2861
2024-04-18 09:21:22 -07:00
zhangenming
36e62c6034 delete useless eslint-disable-next-line (#28859)
<!--
  Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.

Before submitting a pull request, please make sure the following is
done:

1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
  2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
  9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
  10. If you haven't already, complete the CLA.

Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->

## Summary

<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->

## How did you test this change?

<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
  If you leave this empty, your PR will very likely be closed.
-->
2024-04-18 11:49:55 -04:00
Joe Savona
c84ff16afb [dx] Update error message for reassigning globals
ghstack-source-id: 48ce4b55a8b4b5a22c8ec29f6787732ab2a67778
Pull Request resolved: https://github.com/facebook/react-forget/pull/2860
2024-04-18 07:46:22 -07:00
Ricky
4fdbac075f Revert "convert circleci workflow devtools_regression_tests to github actions" (#28865)
Reverts facebook/react#27800, tests fail on main
2024-04-18 10:18:53 -04:00
Rob Anderson
496e8733bb convert circleci workflow devtools_regression_tests to github actions (#27800)
## Summary

This pull request converts the CircleCI workflows to GitHub actions
workflows. [Github Actions
Importer](https://github.com/github/gh-actions-importer) was used to
convert the workflows initially, then I edited them manually to correct
errors in translation.

**Issues**
1. facebook/react/devtools_regression_tests  
The scripts that this workflow calls need to be modified.

## How did you test this change?

I tested these changes in a forked repo. You can [view the logs of this
workflow in my fork](https://github.com/robandpdx/react/actions).

https://fburl.com/workplace/f6mz6tmw
2024-04-18 15:04:27 +01:00
Joe Savona
87993f333a [dx] Update suppression error messages
ghstack-source-id: 0c362a349de86a07b4e9e381b942939ce4a24e69
Pull Request resolved: https://github.com/facebook/react-forget/pull/2859
2024-04-17 18:25:40 -07:00
Joe Savona
4666bf8c17 [dx] Improve BuildHIR error messages
ghstack-source-id: cbf62aec4e768249a8c7f44fe6f3852183127415
Pull Request resolved: https://github.com/facebook/react-forget/pull/2858
2024-04-17 18:16:33 -07:00
Joe Savona
fc7467ac0e [dx] Update error messages for config parsing
ghstack-source-id: 693a3526a73f1fbd25f4e59416f8c65a0f8f1235
Pull Request resolved: https://github.com/facebook/react-forget/pull/2857
2024-04-17 18:16:32 -07:00
Ricky
b5e5ce8e0a Update ReactNativeTypes for root options (part 2) (#28857)
Forgot to push a change before mergin
https://github.com/facebook/react/pull/28850
2024-04-17 14:56:43 -04:00
Josh Story
da6ba53b10 [UMD] Remove umd builds (#28735)
In React 19 React will finally stop publishing UMD builds. This is
motivated primarily by the lack of use of UMD format and the added
complexity of maintaining build infra for these releases. Additionally
with ESM becoming more prevalent in browsers and services like esm.sh
which can host React as an ESM module there are other options for doing
script tag based react loading.

This PR removes all the UMD build configs and forks.

There are some fixtures that still have references to UMD builds however
many of them already do not work (for instance they are using legacy
features like ReactDOM.render) and rather than block the removal on
these fixtures being brought up to date we'll just move forward and fix
or removes fixtures as necessary in the future.
2024-04-17 11:15:27 -07:00
Sebastian Silbermann
0c245df1dc Complete the typo fix (#28856) 2024-04-17 20:04:22 +02:00
Andrew Clark
f82051d7ab console test utils fix: match entire string, not just first letter (#28855)
Fixes issue where if the first letter of the expected string appeared
anywhere in actual message, the assertion would pass, leading to false
negatives. We should check the entire expected string.

---------

Co-authored-by: Ricky <rickhanlonii@gmail.com>
2024-04-17 13:04:54 -04:00
Sebastian Markbåge
4ca20fd36b Test top level fragment inside lazy semantics (#28852)
This wasn't clearly articulated and tested why the code structure is
like this but I think the logic is correct - or at least consistent with
the weird semantics.

We place this top-level fragment check inside the recursion so that you
can resolve how many every Lazy or Usable wrappers you want and it still
preserves the same semantics if they weren't there (which they might not
be as a matter of a race condition).

However, we don't actually recurse with the top-level fragment
unwrapping itself because nesting a bunch of keyless fragments isn't the
same as a single fragment/element.
2024-04-17 12:37:51 -04:00
Sebastian Markbåge
c0cf7c696c Promote ASYNC_ITERATOR symbol to React Symbols (#28851)
So that when we end up referring to it in more places, it's only one.

We don't do this same pattern for regular `Symbol.iterator` because we
also support the string `"@@iterator"` for backwards compatibility.
2024-04-17 12:29:08 -04:00
Ricky
657428a9e9 Add ReactNativeTypes for root options (#28850)
Flow should have failed for this but didn't, we need these options
sync'd over in the types too.
2024-04-16 21:18:16 -04:00
Sebastian Markbåge
7909d8eabb [Flight] Encode ReadableStream and AsyncIterables (#28847)
This adds support in Flight for serializing four kinds of streams:

- `ReadableStream` with objects as a model. This is a single shot
iterator so you can read it only once. It can contain any value
including Server Components. Chunks are encoded as is so if you send in
10 typed arrays, you get the same typed arrays out on the other side.
- Binary `ReadableStream` with `type: 'bytes'` option. This supports the
BYOB protocol. In this mode, the receiving side just gets `Uint8Array`s
and they can be split across any single byte boundary into arbitrary
chunks.
- `AsyncIterable` where the `AsyncIterator` function is different than
the `AsyncIterable` itself. In this case we assume that this might be a
multi-shot iterable and so we buffer its value and you can iterate it
multiple times on the other side. We support the `return` value as a
value in the single completion slot, but you can't pass values in
`next()`. If you want single-shot, return the AsyncIterator instead.
- `AsyncIterator`. These gets serialized as a single-shot as it's just
an iterator.

`AsyncIterable`/`AsyncIterator` yield Promises that are instrumented
with our `.status`/`.value` convention so that they can be synchronously
looped over if available. They are also lazily parsed upon read.

We can't do this with `ReadableStream` because we use the native
implementation of `ReadableStream` which owns the promises.

The format is a leading row that indicates which type of stream it is.
Then a new row with the same ID is emitted for every chunk. Followed by
either an error or close row.

`AsyncIterable`s can also be returned as children of Server Components
and then they're conceptually the same as fragment arrays/iterables.
They can't actually be used as children in Fizz/Fiber but there's a
separate plan for that. Only `AsyncIterable` not `AsyncIterator` will be
valid as children - just like sync `Iterable` is already supported but
single-shot `Iterator` is not. Notably, neither of these streams
represent updates over time to a value. They represent multiple values
in a list.

When the server stream is aborted we also close the underlying stream.
However, closing a stream on the client, doesn't close the underlying
stream.

A couple of possible follow ups I'm not planning on doing right now:

- [ ] Free memory by releasing the buffer if an Iterator has been
exhausted. Single shots could be optimized further to release individual
items as you go.
- [ ] We could clean up the underlying stream if the only pending data
that's still flowing is from streams and all the streams have cleaned
up. It's not very reliable though. It's better to do cancellation for
the whole stream - e.g. at the framework level.
- [ ] Implement smarter Binary Stream chunk handling. Currently we wait
until we've received a whole row for binary chunks and copy them into
consecutive memory. We need this to preserve semantics when passing
typed arrays. However, for binary streams we don't need that. We can
just send whatever pieces we have so far.
2024-04-16 12:20:07 -04:00
Andrew Clark
13eb61d056 Move enableUseDeferredValueInitialArg to canary (#28818)
Per team discussion, this upgrades the `initialValue` argument for
`useDeferredValue` from experimental to canary.

- Original implementation PR:
https://github.com/facebook/react/pull/27500
- API documentation PR: https://github.com/reactjs/react.dev/pull/6747

I left it disabled at Meta for now in case there's old code somewhere
that is still passing an `options` object as the second argument.
2024-04-16 12:12:32 -04:00
Jan Kassens
8afa144bdc Enable flag disableClientCache (#28846)
Enable flag disableClientCache

Forcing a `__VARIANT__` in the mock file so we keep testing this until
fully removing it.
2024-04-16 10:59:36 -04:00
Sathya Gunsasekaran
6bf64df538 [babel] Add a sources option
This allows the plugin to be configured to run on an allowlist, rather
than compiling all files helping with an incremental rollout plan.

The sources option takes both an array of path strings or a function
to be flexible.

For now I've left this be optional but we can make it required.

ghstack-source-id: 282a33dc8d08d47f699894692e0fcc813dff5b77
Pull Request resolved: https://github.com/facebook/react-forget/pull/2855
2024-04-16 14:41:16 +01:00
Sathya Gunsasekaran
5bceb3cb3b [.gitignore] Add bundle script
No need to commit this in the repo, but keeping this locally helps with
building the feedback repo.

ghstack-source-id: 28f08992b1ab856567a5338af07e12ac820a7298
Pull Request resolved: https://github.com/facebook/react-forget/pull/2854
2024-04-16 14:41:13 +01:00
Sebastian Silbermann
734956ace6 Devtools: Add support for useFormStatus (#28413) 2024-04-16 10:28:16 +02:00
Sebastian Silbermann
8f212cc789 Ensure sizebot doesn't swallow large diffs (#28845) 2024-04-16 09:56:25 +02:00
Sebastian Markbåge
17e920c00d [Flight Reply] Encode Typed Arrays and Blobs (#28819)
With the enableBinaryFlight flag on we should encode typed arrays and
blobs in the Reply direction too for parity.

It's already possible to pass Blobs inside FormData but you should be
able to pass them inside objects too.

We encode typed arrays as blobs and then unwrap them automatically to
the right typed array type.

Unlike the other protocol, I encode the type as a reference tag instead
of row tag. Therefore I need to rename the tags to avoid conflicts with
other tags in references. We are running out of characters though.
2024-04-15 22:32:08 -04:00
Ricky
fd35655fae Delete AUTHORS.md (#28844)
This hasn't been updated in a long time, and it getting really large. 

You can view the authors locally via the git history with:

``
git shortlog -se | perl -spe 's/^\s+\d+\s+//'
``
2024-04-15 15:23:30 -04:00
Ricky
0347fcd007 Add on(Caught|Uncaught|Recoverable) opts to RN (#28836)
## Overview

There's currently a bug in RN now that we no longer re-throw errors. The
`showErrorDialog` function in React Native only logs the errors as soft
errors, and never a fatal. RN was depending on the global handler for
the fatal error handling and logging.

Instead of fixing this in `ReactFiberErrorDialog`, we can implement the
new root options in RN to handle caught/uncaught/recoverable in the
respective functions, and delete ReactFiberErrorDialog. I'll follow up
with a RN PR to implement these options and fix the error handling.
2024-04-15 12:03:28 -04:00
Kenta Iwasaki
c113503ad1 Flush direct streams in Bun (#28837)
<!--
  Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.

Before submitting a pull request, please make sure the following is
done:

1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
  2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
  9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
  10. If you haven't already, complete the CLA.

Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->

## Summary

<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->

The ReadableStreamController for [direct
streams](https://bun.sh/docs/api/streams#direct-readablestream) in Bun
supports a flush() method to flush all buffered items to its underlying
sink.

Without manually calling flush(), all buffered items are only flushed to
the underlying sink when the stream is closed. This behavior causes the
shell rendered against Suspense boundaries never to be flushed to the
underlying sink.

## How did you test this change?

<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
  If you leave this empty, your PR will very likely be closed.
-->

A lot of changes to the test runner will need to be made in order to
support the Bun runtime. A separate test was manually run in order to
ensure that the changes made are correct.

The test works by sanity-checking that the shell rendered against
Suspense boundaries are emitted first in the stream.

This test was written and run on Bun v1.1.3.

```ts
import { Suspense } from "react";
import { renderToReadableStream } from "react-dom/server";

if (!import.meta.resolveSync("react-dom/server").endsWith("server.bun.js")) {
  throw new Error("react-dom/server is not the correct version:\n  " + import.meta.resolveSync("react-dom/server"));
}

const A = async () => {
  await new Promise(resolve => setImmediate(resolve));
  return <div>hi</div>;
};

const B = async () => {
  return (
    <Suspense fallback={<div>loading</div>}>
      <A />
    </Suspense>
  );
};

const stream = await renderToReadableStream(<B />);

let text = "";
let count = 0;
for await (const chunk of stream) {
  text += new TextDecoder().decode(chunk);
  count++;
}

if (
  text !==
  `<!--$?--><template id="B:0"></template><div>loading</div><!--/$--><div hidden id="S:0"><div>hi</div></div><script>$RC=function(b,c,e){c=document.getElementById(c);c.parentNode.removeChild(c);var a=document.getElementById(b);if(a){b=a.previousSibling;if(e)b.data="$!",a.setAttribute("data-dgst",e);else{e=b.parentNode;a=b.nextSibling;var f=0;do{if(a&&8===a.nodeType){var d=a.data;if("/$"===d)if(0===f)break;else f--;else"$"!==d&&"$?"!==d&&"$!"!==d||f++}d=a.nextSibling;e.removeChild(a);a=d}while(a);for(;c.firstChild;)e.insertBefore(c.firstChild,a);b.data="$"}b._reactRetry&&b._reactRetry()}};$RC("B:0","S:0")</script>`
) {
  throw new Error("unexpected output");
}
if (count !== 2) {
  throw new Error("expected 2 chunks from react ssr stream");
}
```
2024-04-15 11:25:08 -04:00
Sathya Gunsasekaran
b28bb19e44 [codegen] Don't drop directives in simple arrow fn
The compiler has an optimisation where it transforms a simple arrow
function with only a return statement to a implicit arrow function.

In the case, there's a directive in this simple arrow function, the
directive gets dropped.

Instead of dropping the directive, the compiler should perform this
optimisation only if there are no directives.

ghstack-source-id: 514cd2440025986a2d6d950694a7339d779b09f2
Pull Request resolved: https://github.com/facebook/react-forget/pull/2848
2024-04-15 15:52:45 +01:00
Ruslan Lesiutin
2d128ff041 React DevTools 5.0.2 -> 5.1.0 (#28840)
Full list of changes:
* Look for a ReactMemoCacheSentinel on state
([gsathya](https://github.com/gsathya) in
[#28831](https://github.com/facebook/react/pull/28831))
* Use use() in the Cache if available
([sebmarkbage](https://github.com/sebmarkbage) in
[#28793](https://github.com/facebook/react/pull/28793))
* feat[devtools-fusebox]: support theme option
([hoxyq](https://github.com/hoxyq) in
[#28832](https://github.com/facebook/react/pull/28832))
* feat[devtools]: add package for fusebox integration
([hoxyq](https://github.com/hoxyq) in
[#28553](https://github.com/facebook/react/pull/28553))
* feat[devtools]: add method for connecting backend with custom
messaging protocol ([hoxyq](https://github.com/hoxyq) in
[#28552](https://github.com/facebook/react/pull/28552))
* Rename SECRET INTERNALS to
`__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE`
([sebmarkbage](https://github.com/sebmarkbage) in
[#28789](https://github.com/facebook/react/pull/28789))
* Flatten ReactSharedInternals
([sebmarkbage](https://github.com/sebmarkbage) in
[#28783](https://github.com/facebook/react/pull/28783))
* feat[devtools]: ship source maps for content scripts and ignore list
installHook script ([hoxyq](https://github.com/hoxyq) in
[#28730](https://github.com/facebook/react/pull/28730))
* Track Owner for Server Components in DEV
([sebmarkbage](https://github.com/sebmarkbage) in
[#28753](https://github.com/facebook/react/pull/28753))
* Move ReactDOMLegacy implementation into RootFB
([sebmarkbage](https://github.com/sebmarkbage) in
[#28656](https://github.com/facebook/react/pull/28656))
* Reland #28672: Remove IndeterminateComponent
([gnoff](https://github.com/gnoff) in
[#28681](https://github.com/facebook/react/pull/28681))
* Remove reference to deleted <Cache> in un-linted file
([josephsavona](https://github.com/josephsavona) in
[#28715](https://github.com/facebook/react/pull/28715))
* [be] Remove unshipped experimental <Cache> element type
([josephsavona](https://github.com/josephsavona) in
[#28698](https://github.com/facebook/react/pull/28698))
* Revert "Remove module pattern function component support"
([rickhanlonii](https://github.com/rickhanlonii) in
[#28670](https://github.com/facebook/react/pull/28670))
* Remove module pattern function component support
([gnoff](https://github.com/gnoff) in
[#27742](https://github.com/facebook/react/pull/27742))
* [RTR] Enable warning flag ([jackpope](https://github.com/jackpope) in
[#28419](https://github.com/facebook/react/pull/28419))
* Update error messages ([rickhanlonii](https://github.com/rickhanlonii)
in [#28652](https://github.com/facebook/react/pull/28652))
* fix[devtools/ci]: split profiling cache test for different react
versions and toEqual checker ([hoxyq](https://github.com/hoxyq) in
[#28628](https://github.com/facebook/react/pull/28628))
* Guard against legacy context not being supported in DevTools fixture
([eps1lon](https://github.com/eps1lon) in
[#28596](https://github.com/facebook/react/pull/28596))
* Use `declare const` instead of `declare var`
([kassens](https://github.com/kassens) in
[#28599](https://github.com/facebook/react/pull/28599))
* Update isConcurrent RTR option usage
([jackpope](https://github.com/jackpope) in
[#28546](https://github.com/facebook/react/pull/28546))
* Disable legacy context ([kassens](https://github.com/kassens) in
[#27991](https://github.com/facebook/react/pull/27991))
* Remove invokeGuardedCallback and replay trick
([sebmarkbage](https://github.com/sebmarkbage) in
[#28515](https://github.com/facebook/react/pull/28515))
* Remove remaining usages of ReactTestUtils in tests unrelated to
`react-dom/test-util` ([eps1lon](https://github.com/eps1lon) in
[#28534](https://github.com/facebook/react/pull/28534))
* fix[devtools/e2e]: fixed source inspection in e2e tests
([hoxyq](https://github.com/hoxyq) in
[#28518](https://github.com/facebook/react/pull/28518))
* Devtools: Display actual pending state when inspecting `useTransition`
([eps1lon](https://github.com/eps1lon) in
[#28499](https://github.com/facebook/react/pull/28499))
2024-04-15 14:56:57 +01:00
Sathya Gunasekaran
d486051de7 [Devtools] Look for a ReactMemoCacheSentinel on state (#28831)
The useMemoCache polyfill doesn't have access to the fiber, and it
simply uses state, which does not work with the existing devtools 
badge for the compiler.

With this PR, devtools will look on the very first hook's state for the
memo cache sentinel and display the Forget badge if present.

The polyfill will add this sentinel to it's state (the cache array).
2024-04-15 13:05:05 +01:00
Sebastian Markbåge
1683cb186c Use use() in the Cache if available (#28793)
This is a follow up to
https://github.com/facebook/react/pull/28789#discussion_r1557232202

Revert to use the old readContext detection if not to support older
React.

I haven't actually tested this. Just opening as a suggestion.
2024-04-15 13:03:29 +01:00
Sebastian Silbermann
9defcd56bc Remove redundant props assign (#28829) 2024-04-12 21:55:51 +02:00
Sebastian Markbåge
ed40236036 Fix mistaken "react-server" condition (#28835)
This is a Fizz server.
2024-04-12 15:53:41 -04:00
Josh Story
ba1a9797ee Remove flight-browser fixture (#28828)
The flight-browser fixtures doesn't make sense. It also uses UMD builds
which are being removed so we'd have to make it use esm.sh or something
and really it just won't work because it needs to be built by webpack to
not error. We could potentially shim the webpack globals but really the
right thing is to publish the esm version of react-server and use esm.sh
to load a browser only esm demo of react-server. This change removes the
flight-browser fixture
2024-04-12 09:48:40 -07:00
Ruslan Lesiutin
7625f0d501 feat[devtools-fusebox]: support theme option (#28832)
Support propagating theme from Chrome DevTools frontend, the field is
optional.

Next step, which is out of scope of this project and general improvement
for React DevTools: teach RDT to listen to theme changes and if the
theme preference is set to `auto` in settings, update the theme
accordingly with the browser devtools.
2024-04-12 17:26:37 +01:00
Sathya Gunsasekaran
441024318c [polyfill] Add sentinel to array
To make the polyfill work well with devtools, we add this sentinel.

Devtools will look for this sentinel before adding the Forget badge.

ghstack-source-id: d246040f67da48fa46f818753c8bf26dff56f390
Pull Request resolved: https://github.com/facebook/react-forget/pull/2847
2024-04-12 15:32:12 +01:00
Ruslan Lesiutin
96c5846610 feat[devtools]: add package for fusebox integration (#28553)
## Summary

Stacked on https://github.com/facebook/react/pull/28552. Review only the
[last commit at the
top](c69952f1bf).

These changes add new package `react-devtools-fusebox`, which is the
entrypoint for the RDT Frontend, which will be used in Chrome DevTools
panel. The main differences from other frontend shells (extension,
standalone) are:
1. This package builds scripts in ESM format, this is required by Chrome
DevTools, see webpack config:

c69952f1bf/packages/react-devtools-fusebox/webpack.config.frontend.js (L50-L52)
2. The build includes styles in a separate `.css` file, which is
required for Chrome DevTools: styles are loaded lazily once panel is
mounted.
2024-04-12 15:29:35 +01:00
Ruslan Lesiutin
d012a32f84 feat[devtools]: add method for connecting backend with custom messaging protocol (#28552)
## Summary

RDT backend will now expose method `connectWithCustomMessagingProtocol`,
which will be similar to the classic `connectToDevTools` one, but with
few differences:
1. It delegates the communication management between frontend and
backend to the owner (whos injecting RDT backend). Unlike the
`connectToDevTools`, which is relying on websocket connection and
receives host and port as an arguments.
2. It returns a callback, which can be used for unsubscribing the
current backend instance from the global DevTools hook.


This is a prerequisite for any non-browser RDT integration, which is not
designed to be based on websocket.
2024-04-12 15:17:07 +01:00
Josh Story
c8a035036d [Fizz] hoistables should never flush before the preamble (#28802)
Hoistables should never flush before the preamble however there is a
surprisingly easy way to trigger this to happen by suspending in the
shell of the app. This change modifies the flushing behavior to not emit
any hoistables before the preamble has written. It accomplishes this by
aborting the flush early if there are any pending root tasks remaining.
It's unfortunate we need this extra condition but it's essential that we
don't emit anything before the preamble and at the moment I don't see a
way to do that without introducing a new condition.

There is a test that began to fail with this update. It turns out that
in node the root can be blocked during a resume even for a component
inside a Suspense boundary if that boundary was part of the prerender.
This means that with the current heuristic in this PR boundaries cannot
be flushed during resume until the root is unblocked. This is not ideal
but this is already how Edge works because the root blocks the stream in
that case. This just makes Node deopt in a similar way to edge. We
should improve this but we ought to do so in a way that works for edge
too and it needs to be more comprehensive.
2024-04-11 15:13:04 -07:00
Sebastian Silbermann
4f5c812a3c DevTools: Rely on sourcemaps to compute hook name of built-in hooks in newer versions (#28593) 2024-04-11 22:00:54 +02:00
Jack Pope
4354159623 Backwards compatibility for string refs on WWW (#28826)
Seeing errors with undefined string ref values when trying to sync
https://github.com/facebook/react/pull/28473

Added a test that reproduces the failing pattern.

@acdlite pushed
a786481ae5
with fix

---------

Co-authored-by: Jack Pope <jackpope@meta.com>
Co-authored-by: Andrew Clark <git@andrewclark.io>
2024-04-11 15:30:37 -04:00
Sebastian Silbermann
adb7173938 Fix nightly release job for real (#28825) 2024-04-11 18:28:10 +02:00
Sebastian Silbermann
a2582074b8 Fix nightly release job (#28824) 2024-04-11 18:08:13 +02:00
Ricky
608edcc90a [tests] add assertConsole<method>Dev helpers (#28732)
## Overview
**Internal React repo tests only**

Depends on https://github.com/facebook/react/pull/28710

Adds three new assertions:
- `assertConsoleLogDev`
- `assertConsoleWarnDev`
- `assertConsoleErrorDev`

These will replace this pattern:

```js
await expect(async () => {
  await expect(async () => {
    await act(() => {
      root.render(<Fail />)
    });
  }).toThrow();
}).toWarnDev('Warning');
```

With this:

```js
await expect(async () => {
  await act(() => {
    root.render(<Fail />)
  });
}).toThrow();

assertConsoleWarnDev('Warning');
```

It works similar to our other `assertLog` matchers which clear the log
and assert on it, failing the tests if the log is not asserted before
the test ends.

## Diffs

There are a few improvements I also added including better log diffs and
more logging.

When there's a failure, the output will look something like:

<img width="655" alt="Screenshot 2024-04-03 at 11 50 08 AM"
src="https://github.com/facebook/react/assets/2440089/0c4bf1b2-5f63-4204-8af3-09e0c2d752ad">


Check out the test suite for snapshots of all the failures we may log.
2024-04-11 08:19:46 -04:00
Andrew Clark
da69b6af96 ReactDOM.requestFormReset (#28809)
Based on:

- #28808
- #28804 

---

This adds a React DOM method called requestFormReset that schedules a
form reset to occur when the current transition completes.

Internally, it's the same method that's called automatically whenever a
form action is submitted. It only affects uncontrolled form inputs. See
https://github.com/facebook/react/pull/28804 for details.

The reason for the public API is so UI libraries can implement their own
action-based APIs and maintain the form-resetting behavior, something
like this:

```js
function onSubmit(event) {
  // Disable default form submission behavior
  event.preventDefault();
  const form = event.target;
  startTransition(async () => {
    // Request the form to reset once the action
    // has completed
    requestFormReset(form);

    // Call the user-provided action prop
    await action(new FormData(form));
  })
}
```
2024-04-10 16:56:55 -04:00
Andrew Clark
374b5d26c2 Scaffolding for requestFormReset API (#28808)
Based on:

- #28804 

---

This sets adds a new ReactDOM export called requestFormReset, including
setting up the export and creating a method on the internal ReactDOM
dispatcher. It does not yet add any implementation.

Doing this in its own commit for review purposes.

The API itself will be explained in the next PR.
2024-04-10 16:55:15 -04:00
Andrew Clark
41950d14a5 Automatically reset forms after action finishes (#28804)
This updates the behavior of form actions to automatically reset the
form's uncontrolled inputs after the action finishes.

This is a frequent feature request for people using actions and it
aligns the behavior of client-side form submissions more closely with
MPA form submissions.

It has no impact on controlled form inputs. It's the same as if you
called `form.reset()` manually, except React handles the timing of when
the reset happens, which is tricky/impossible to get exactly right in
userspace.

The reset shouldn't happen until the UI has updated with the result of
the action. So, resetting inside the action is too early.

Resetting in `useEffect` is better, but it's later than ideal because
any effects that run before it will observe the state of the form before
it's been reset.

It needs to happen in the mutation phase of the transition. More
specifically, after all the DOM mutations caused by the transition have
been applied. That way the `defaultValue` of the inputs are updated
before the values are reset. The idea is that the `defaultValue`
represents the current, canonical value sent by the server.

Note: this change has no effect on form submissions that aren't
triggered by an action.
2024-04-10 16:54:24 -04:00
Josh Story
dc6a7e01e1 [Float] Don't preload images inside <noscript> (#28815)
`<noscript>` scopes should be considered inert from the perspective of
Fizz since we assume they'll only be used in rare and adverse
circumstances. When we added preload support for img tags we did not
include the noscript scope check in the opt-out for preloading. This
change adds it in

fixes: #27910
2024-04-10 12:15:04 -07:00
Mofei Zhang
d2018c51e1 [playground] Editing input shouldn't toggle diff view
ghstack-source-id: f9e80fc0c928e1ce1ec698d4d952e11402054843
Pull Request resolved: https://github.com/facebook/react-forget/pull/2833
2024-04-10 14:53:38 -04:00
Lauren Tan
c836a0158b Fix usage of renamed React internals
ghstack-source-id: d646c84815c324bd490584d34488a73f5e39d589
Pull Request resolved: https://github.com/facebook/react-forget/pull/2845
2024-04-11 11:10:42 -04:00
Lauren Tan
6e65445911 Fix playground e2e test for real
ghstack-source-id: 1b52cb312a5f26e8e058bfe4a29b586ffaeb25d7
Pull Request resolved: https://github.com/facebook/react-forget/pull/2843
2024-04-11 12:13:59 -04:00
Lauren Tan
ec05176fb3 Make string values for config case-insensitive
Fixes a tiny inconsistency with compiler options where one was all
uppercase and one all lowercase by normalizing to lowercase regardless
of the casing of the user's config.

ghstack-source-id: fe60a3259de89a1b3fdd7475950e16e96cc57f6b
Pull Request resolved: https://github.com/facebook/react-forget/pull/2832
2024-04-11 10:45:35 -04:00
Lauren Tan
9cdd6b243e Fix playground e2e test
ghstack-source-id: 3904ff9beea21ffbb79a6a15fcfbb72f04a2d170
Pull Request resolved: https://github.com/facebook/react-forget/pull/2842
2024-04-10 18:17:38 -04:00
Lauren Tan
200b0f2333 Add aria-label to playground tabs
ghstack-source-id: 1c23bbbb55c1582a03674897b57c5061b895be83
Pull Request resolved: https://github.com/facebook/react-forget/pull/2841
2024-04-10 18:17:37 -04:00
Lauren Tan
f990948a1f Small style tweaks to playground vertical tabs
ghstack-source-id: 56e72c56782274bdac92fdd131d2c9b33eb19052
Pull Request resolved: https://github.com/facebook/react-forget/pull/2840
2024-04-10 18:17:36 -04:00
Lauren Tan
ed3bad0c2f Add aria-label to playground header buttons
ghstack-source-id: 4fdf672f02144e550d50b17f396a5d6d82dab5bd
Pull Request resolved: https://github.com/facebook/react-forget/pull/2839
2024-04-10 18:17:36 -04:00
Lauren Tan
7083231f6d Add link to GitHub repo in playground header
ghstack-source-id: 9fd0c2f8d70f8782602b0e4f8535b6be3ed89782
Pull Request resolved: https://github.com/facebook/react-forget/pull/2838
2024-04-10 18:17:35 -04:00
Lauren Tan
b6c01f7f78 Remove playground wipe button
It was never clear to me what the difference between Wipe and Reset was.
Let's just get rid of one and reset to something more useful instead of
fibonacci.

ghstack-source-id: 4f88a1c1da2d0fd9e1f26e4859c12db0fc961af2
Pull Request resolved: https://github.com/facebook/react-forget/pull/2837
2024-04-10 17:46:35 -04:00
Lauren Tan
9ae3e74c3e Rename references to "forget" in playground
ghstack-source-id: fc2d58e4d5195c3ec157f65b3e1c94de39ff2070
Pull Request resolved: https://github.com/facebook/react-forget/pull/2836
2024-04-10 17:46:35 -04:00
Lauren Tan
594f335201 Show alert when url copied
ghstack-source-id: e8dba44d246d53f60a0858710cedf75c77c6c7e4
Pull Request resolved: https://github.com/facebook/react-forget/pull/2835
2024-04-10 17:46:34 -04:00
Lauren Tan
2f66d37cfd Use lz-string for playground state uri param
This compresses more efficiently than the base64 encoding we were
previously using, which makes sharing URLs a little less unwieldy and
takes up less space in local storage. Using
some real code as an example, lz-string compresses to 8040 bytes,
whereas the original base64 encoding we were using compresses to 16504
bytes

ghstack-source-id: b8f1089889b94b07d6f419606b798ffddb8863ba
Pull Request resolved: https://github.com/facebook/react-forget/pull/2834
2024-04-10 17:46:33 -04:00
Ricky
3f947b1b46 [tests] Assert scheduler log empty in internalAct (#28737)
We should force `assertLog` to be called before each `act` block to
ensure the queue is empty.

Requires fixing tests:
- https://github.com/facebook/react/pull/28745
- https://github.com/facebook/react/pull/28758
- https://github.com/facebook/react/pull/28759
- https://github.com/facebook/react/pull/28760
- https://github.com/facebook/react/pull/28761
- https://github.com/facebook/react/pull/28762
- https://github.com/facebook/react/pull/28763
- https://github.com/facebook/react/pull/28812
2024-04-10 14:13:46 -04:00
Ricky
870e4045ab [tests] add assertLog for legacy mode tests (#28814)
A few more tests for https://github.com/facebook/react/pull/28737
2024-04-10 14:01:59 -04:00
Josh Story
a8a83f7a59 Removes the react-interactions project which is unused (#28799)
Removes the react-interactions project which is unused

This project uses `unstable_createEventHandle` which is being removed
from React in version 19
2024-04-10 09:18:33 -07:00
Ricky
bf09089f64 Remove Scheduler.log from ReactSuspenseFuzz-test (#28812)
These test don't `assertLog` or `waitFor` so we don't need to
`Scheduler.log`. Ideally we would, but since they're fuzzers it's a bit
difficult to know what the expected log is from the helper.

Since this doesn't regress current test behavior, we can improve them
after this to unblock https://github.com/facebook/react/pull/28737
2024-04-10 11:40:22 -04:00
Ricky
84cb3b4cb2 Hardcode disableIEWorkarounds for www (#28811)
This has landed and is true everywhere, but let's keep the flag until it
lands in the stable release.
2024-04-10 11:14:33 -04:00
Ricky
2243b40aba [tests] assertLog before act in useEffectEvent (#28763)
Fixes tests blocking https://github.com/facebook/react/pull/28737
2024-04-10 10:34:34 -04:00
Ricky
dfc64c6e31 [tests] assertLog before act in ReactUse (#28762)
Fixes tests blocking https://github.com/facebook/react/pull/28737
2024-04-10 10:34:27 -04:00
Ricky
b02199d322 [tests] assertLog before act in ReactErrorBoundaries (#28761)
Fixes tests blocking https://github.com/facebook/react/pull/28737
2024-04-10 10:34:19 -04:00
Ricky
6e1e2f2198 [tests] assertLog before act in ReactUpdates (#28760)
Fixes tests blocking https://github.com/facebook/react/pull/28737
2024-04-10 10:34:11 -04:00
Ricky
88df5242d6 [tests] assertLog before act in ReactDOMServerSelectiveHydration (#28759)
Fixes tests blocking https://github.com/facebook/react/pull/28737
2024-04-10 10:34:02 -04:00
Ricky
e36ee763fa [tests] assertLog before act in ReactCompositeComponentState (#28758)
Fixes tests blocking https://github.com/facebook/react/pull/28737
2024-04-10 10:33:51 -04:00
Ricky
42eff4bc78 [tests] Fix assertions not flushed before act (#28745)
Fixes some easy cases blocking
https://github.com/facebook/react/pull/28737, I'll follow up with more
complex/interesting cases in other PRs.
2024-04-10 10:33:41 -04:00
Andrew Clark
ed3c65caf0 Warn if outdated JSX transform is detected (#28781)
We want to warn if we detect that an app is using an outdated JSX
transform. We can't just warn if `createElement` is called because we
still support `createElement` when it's called manually. We only want to
warn if `createElement` is output by the compiler.

The heuristic is to check for a `__self` prop, which is an optional,
internal prop that older transforms used to pass to `createElement` for
better debugging in development mode.

If `__self` is present, we `console.warn` once with advice to upgrade to
the modern JSX transform. Subsequent elements will not warn.

There's a special case we have to account for: when a static "key" prop
is defined _after_ a spread, the modern JSX transform outputs
`createElement` instead of `jsx`. (This is because with `jsx`, a spread
key always takes precedence over a static key, regardless of the order,
whereas `createElement` respects the order.) To avoid a false positive
warning, we skip the warning whenever a `key` prop is present.
2024-04-09 17:13:19 -04:00
Andrew Clark
3f9e237a2f Fix: Suspend while recovering from hydration error (#28800)
Fixes a bug that happens when an error occurs during hydration, React
switches to client rendering, and then the client render suspends. It
works correctly if there's a Suspense boundary on the stack, but not if
it happens in the shell of the app.

Prior to this fix, the app would crash with an "Unknown root exit
status" error.

I left a TODO comment for how we might refactor this code to be less
confusing in the future.
2024-04-09 17:11:46 -04:00
Joseph Savona
64c8d2d45d Attempt to fix diff syncing for Meta (#28801)
#28796 broke Meta's PR syncing tool, hoping this fixes it
2024-04-09 14:05:18 -07:00
Joseph Savona
7f5d25e23e Fix cloneElement using string ref w no owner (#28797)
Fix for an issue introduced in #28473 where cloneElement() with a string
ref fails due to lack of an owner. We should use the current owner in
this case.

---------

Co-authored-by: Rick Hanlon <rickhanlonii@fb.com>
2024-04-09 13:43:49 -07:00
Josh Story
bf40b02442 [Fizz] Stop publishing external-runtime to stable channel (#28796)
The external runtime is not vetted for stability yet. We should stop
publishing it with our stable build
2024-04-09 11:57:58 -07:00
Josh Story
67ff96e12d [Tests][Fizz] Test script runtime even when external runtime is available (#28794)
Previously if the external runtime was enabled Fizz tests would use it
exclusively. However now that this flag is enabled for OSS and Meta
builds this means we were no longer testing the inline script runtime.
This changes the test flags to produce some runs where we test the
inline script runtime and others where we test the external runtime

the external runtime will be tested if the flag is enabled and
* Meta Builds: variant is true
* OSS Builds: experiemental is true

this gives us decent coverage. long term we should probably bring
variant to OSS builds since we will eventually want to test both modes
even when the external runtime is stable.
2024-04-09 11:50:02 -07:00
Josh Story
7f93cb41c8 [DOM] Infer react-server entries bundles if not explicitly configured (#28795)
When packaging we want to infer that a bundle exists for a
`react-server` file even if it isn't explicitly configured. This is
useful in particular for the react-server entrypoints that error on
import that were recently added to `react-dom`

This change also cleans up a wayward comment left behind in a prior PR
2024-04-09 10:39:25 -07:00
Sebastian Markbåge
f613165357 Rename SECRET INTERNALS to __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE (#28789)
Follow up to #28783 and #28786.

Since we've changed the implementations of these we can rename them to
something a bit more descriptive while we're at it, since anyone
depending on them will need to upgrade their code anyway.

"react" with no condition:
`__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE`
"react" with "react-server" condition:
`__SERVER_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE`
"react-dom":
`__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE`
2024-04-09 12:20:22 -04:00
Sebastian Silbermann
50895bc161 CI: Run with Node.js 18.20 (#28774) 2024-04-09 16:02:22 +02:00
Jan Kassens
cffbcac8e3 Run stale job hourly (#28769) 2024-04-09 09:21:20 -04:00
Ricky
9644d206e8 Soften useFormState warning (#28788)
It's not deprecated, it's really just renamed. Let's make the warning
less scary.
2024-04-08 23:22:17 -04:00
Sebastian Markbåge
c771016e19 Rename The Secret Export of Server Internals (#28786)
We have a different set of dispatchers that Flight uses. This also
includes the `jsx-runtime` which must also be aliased to use the right
version.

To ensure the right versions are used together we rename the export of
the SharedInternals from 'react' and alias it in relevant bundles.
2024-04-08 22:34:59 -04:00
Joe Savona
57d746e3c4 HIR terminal for representing reactive scopes
ghstack-source-id: f7d5b68bc5217555b6060ade3bfa253bb15660ad
Pull Request resolved: https://github.com/facebook/react-forget/pull/2830
2024-04-08 17:43:41 -07:00
Joe Savona
0c6fe1ba35 Cleanup range printing
ghstack-source-id: 42c49348a93921fc03ec5defcf68a364952a3f88
Pull Request resolved: https://github.com/facebook/react-forget/pull/2829
2024-04-08 17:31:35 -07:00
Joe Savona
f707cb5ff3 HIR-based MergeOverlappingScopes
ghstack-source-id: 93ac68114683044d7e6332fe7898b0dcc5dc6685
Pull Request resolved: https://github.com/facebook/react-forget/pull/2828
2024-04-08 17:09:07 -07:00
Sebastian Markbåge
d50323eb84 Flatten ReactSharedInternals (#28783)
This is similar to #28771 but for isomorphic. We need a make over for
these dispatchers anyway so this is the first step. Also helps flush out
some internals usage that will break anyway.

It flattens the inner mutable objects onto the ReactSharedInternals.
2024-04-08 19:23:23 -04:00
Josh Story
acef2f8111 [DOM] Fix package.json files for #28784 (#28785)
Missed some files for the react-server disallow client change in #28784
2024-04-08 15:49:18 -07:00
Josh Story
556a087de3 [DOM] disallow client entrypoints with react-server condition (#28784)
`react-server` precludes loading code that expects to be run in a client
context. This includes react-dom/client react-dom/server
react-dom/unstable_testing react-dom/profiling and react-dom/static

This update makes importing any of these client only entrypoints an
error
2024-04-08 15:37:06 -07:00
Josh Story
f62cf8c620 [Float] treat props.async in Float consistent with the rest of react-dom (#26760)
Treat async (boolean prop) consistently with Float. Previously float
checked if `props.async === true` (or not true) but the rest of
react-dom considers anything truthy that isn't a function or symbol as
`true`. This PR normalizes the Float behavior.
2024-04-08 14:26:58 -07:00
Hugo Sales
dfd3d5af83 Add support for transition{run,start,cancel} events (#27345) 2024-04-08 23:23:04 +02:00
Josh Story
1f8327f834 [Fiber] Use real event priority for hydration scheduling (#28765)
Stacked on #28751 

Historically explicit hydration scheduling used the reconciler's update
priority to schedule the hydration. There was a lingering todo to switch
to using event priority in the absence of an explicit update priority.
This change updates the hydration priority by referring to the event
priority if no update priority is set
2024-04-08 14:05:17 -07:00
Josh Story
97c90ed883 [DOM] Shrink ReactDOMCurrentDispatcher method names (#28770)
Stacked on #28771 

ReactDOMCurrentDispatcher has longer property names for various methods.
These methods are only ever called internally and don't need to be
represented with as many characters. This change shortens the names and
aligns them with the hint codes we use in Flight. This alignment is
passive since not all dispatcher methods will exist as flight
instructions but where they can line up it seems reasonable to make them
do so
2024-04-08 13:54:30 -07:00
Joe Savona
f4fbeb8803 HIR-based AlignReactiveScopesToBlockScopes
ghstack-source-id: ae560a93f24fdbdb4ed73cdd889feb084bb172e5
Pull Request resolved: https://github.com/facebook/react-forget/pull/2822
2024-04-08 13:48:22 -07:00
Josh Story
9007fdc8f1 [DOM] Shrink ReactDOMSharedInternals source representation (#28771)
Stacked on #28751 

ReactDOMSharedInternals uses properties of considerable length to model
mutuable state. These properties are not mangled during minification and
contribute a not insigificant amount to the uncompressed bundle size and
to a lesser degree compressed bundle size.

This change rewrites the DOMInternals in a way that shortens property
names so we can have smaller builds.
It also treats the entire object as a mutable container rather than
having different mutable sub objects.

The same treatment should be given to ReactSharedInternals
2024-04-08 13:39:39 -07:00
Sebastian Markbåge
14f50ad155 [Flight] Allow lazily resolving outlined models (#28780)
We used to assume that outlined models are emitted before the reference
(which was true before Blobs). However, it still wasn't safe to assume
that all the data will be available because an "import" (client
reference) can be async and therefore if it's directly a child of an
outlined model, it won't be able to update in place.

This is a similar problem as the one hit by @unstubbable in #28669 with
elements, but a little different since these don't follow the same way
of wrapping.

I don't love the structuring of this code which now needs to pass a
first class mapper instead of just being known code. It also shares the
host path which is just an identity function. It wouldn't necessarily
pass my own review but I don't have a better one for now. I'd really
prefer if this was done at a "row" level but that ends up creating even
more code.

Add test for Blob in FormData and async modules in Maps.
2024-04-08 15:40:11 -04:00
Josh Story
01bb3c5632 [TestUtils] Build limited test-utils (#28782)
We landed a flag to disable test utils in many builds but we need to
fork the entrypoint to make it work with tests properly. This also
removes test-utils implementations from builds that do not support it.
Currently in OSS builds the only thing in test-utils is a reexport of
`act`
2024-04-08 12:27:20 -07:00
Ruslan Lesiutin
6de7733e73 feat[devtools]: ship source maps for content scripts and ignore list installHook script (#28730)
## Summary

1. RDT browser extension's content scripts will now ship source maps
(without source in prod, to save some bundle size).
2. `installHook` content script will be ignore listed via `ignoreList`
field in the corresponding source map.
3. Previously, source map for backend file used `x_google_ignoreList`
naming, now `ignoreList`.

## How did you test this change?

1. `ignoreList-test.js`
2. Tested manually that I don't see `installHook` in stack traces when
`console.error` is called.
2024-04-08 18:10:09 +01:00
Josh Story
4c12339ce3 [DOM] move flushSync out of the reconciler (#28500)
This PR moves `flushSync` out of the reconciler. there is still an
internal implementation that is used when these semantics are needed for
React methods such as `unmount` on roots.

This new isomorphic `flushSync` is only used in builds that no longer
support legacy mode.

Additionally all the internal uses of flushSync in the reconciler have
been replaced with more direct methods. There is a new
`updateContainerSync` method which updates a container but forces it to
the Sync lane and flushes passive effects if necessary. This combined
with flushSyncWork can be used to replace flushSync for all instances of
internal usage.

We still maintain the original flushSync implementation as
`flushSyncFromReconciler` because it will be used as the flushSync
implementation for FB builds. This is because it has special legacy mode
handling that the new isomorphic implementation does not need to
consider. It will be removed from production OSS builds by closure
though
2024-04-08 09:03:20 -07:00
Josh Story
8e1462e8c4 [Fiber] Move updatePriority tracking to renderers (#28751)
Currently updatePriority is tracked in the reconciler. `flushSync` is
going to be implemented reconciler agnostic soon and we need to move the
tracking of this state to the renderer and out of reconciler. This
change implements new renderer bin dings for getCurrentUpdatePriority
and setCurrentUpdatePriority.

I was originally going to have the getter also do the event priority
defaulting using window.event so we eliminate getCur rentEventPriority
but this makes all the callsites where we store the true current
updatePriority on the stack harder to work with so for now they remain
separate.

I also moved runWithPriority to the renderer since it really belongs
whereever the state is being managed and it is only currently exposed in
the DOM renderer.

Additionally the current update priority is not stored on
ReactDOMSharedInternals. While not particularly meaningful in this
change it opens the door to implementing `flushSync` outside of the
reconciler
2024-04-08 08:53:17 -07:00
Lauren Tan
5bf2323880 Fix missing playground snapshot
ghstack-source-id: f2c8a542b525ab8c91beaad6327c128a96d122f6
Pull Request resolved: https://github.com/facebook/react-forget/pull/2827
2024-04-08 11:23:29 -04:00
Andrew Clark
0b3b8a6a35 jsx: Remove unnecessary hasOwnProperty check (#28775)
Follow up to #28768.

The modern JSX runtime (`jsx`) does not need to check if each prop is a
direct property with `hasOwnProperty` because the compiler always passes
a plain object.

I'll leave the check in the old JSX runtime (`createElement`) since that
one can be called manually with any kind of object, and if there were
old user code that relied on this for some reason, it would be using
that runtime.
2024-04-08 11:12:40 -04:00
Sebastian Silbermann
e63918d2cd Add Promise as a child test to Flight fixture (#28778) 2024-04-08 17:06:17 +02:00
Lauren Tan
9a6cb24150 [ez] Fix comments to be star blocks
ghstack-source-id: 6a9e844a3ba548d3bf2b7373dc023ad42c2e719b
Pull Request resolved: https://github.com/facebook/react-forget/pull/2826
2024-04-08 10:55:55 -04:00
Lauren Tan
0d0f7c2ad1 Update ValidatePreservedManualMemoization severity to
CannotPreserveMemoization

We do need to fix the error location to point to the "callsite" rather
than the definition of the useMemo callback, but that aside, even if the
error message were perfect, it's not meant to be actionable to the user.

So let's change the severity to CannotPreserveMemoization. This
preserves the validation, but the eslint plugin won't report it.

ghstack-source-id: 722c88922884de05e89030a7b001bd93e0a2a114
Pull Request resolved: https://github.com/facebook/react-forget/pull/2825
2024-04-08 10:55:54 -04:00
Lauren Tan
1f35f7e320 Add new ErrorSeverity.CannotPreserveMemoization
This adds a new category of error where the compiler cannot preserve
memoization exactly how as it was originally authored. We're adding a
new category here because it's not an actionable error, and allows us to
more specifically control whether it's reportable or not.

ghstack-source-id: 9693cd42ca64b980248c6202091bdd4c827e1cd4
Pull Request resolved: https://github.com/facebook/react-forget/pull/2824
2024-04-08 11:00:41 -04:00
Surav Shrestha
f86afca090 docs: fix typo in fixtures/nesting/README.md (#27507) 2024-04-06 19:11:19 +02:00
Sebastian Markbåge
2acfb7b609 [Flight] Support FormData from Server to Client (#28754)
We currently support FormData for Replies mainly for Form Actions. This
supports it in the other direction too which lets you return it from an
action as the response. Mainly for parity.

We don't really recommend that you just pass the original form data back
because the action is supposed to be able to clear fields and such but
you could potentially at least use this as the format and could clear
some fields.

We could potentially optimize this with a temporary reference if the
same object was passed to a reply in case you use it as a round trip to
avoid serializing it back again. That way the action has the ability to
override it to clear fields but if it doesn't you get back the same as
you sent.

#28755 adds support for Blobs when the `enableBinaryFlight` is enabled
which allows them to be used inside FormData too.
2024-04-05 14:32:18 -04:00
Andrew Clark
d1547defe3 Fast JSX: Don't clone props object (#28768)
(Unless "key" is spread onto the element.)

Historically, the JSX runtime clones the props object that is passed in.
We've done this for two reasons.

One reason is that there are certain prop names that are reserved by
React, like `key` and (before React 19) `ref`. These are not actual
props and are not observable by the target component; React uses them
internally but removes them from the props object before passing them to
userspace.

The second reason is that the classic JSX runtime, `createElement`, is
both a compiler target _and_ a public API that can be called manually.
Therefore, we can't assume that the props object that is passed into
`createElement` won't be mutated by userspace code after it is passed
in.

However, the new JSX runtime, `jsx`, is not a public API — it's solely a
compiler target, and the compiler _will_ always pass a fresh, inline
object. So the only reason to clone the props is if a reserved prop name
is used.

In React 19, `ref` is no longer a reserved prop name, and `key` will
only appear in the props object if it is spread onto the element.
(Because if `key` is statically defined, the compiler will pass it as a
separate argument to the `jsx` function.) So the only remaining reason
to clone the props object is if `key` is spread onto the element, which
is a rare case, and also triggers a warning in development.

In a future release, we will not remove a spread key from the props
object. (But we'll still warn.) We'll always pass the object straight
through.

The expected impact is much faster JSX element creation, which in many
apps is a significant slice of the overall runtime cost of rendering.
2024-04-05 13:25:24 -04:00
Andrew Clark
bfd8da807c Make class prop resolution faster (#28766)
`delete` causes an object (in V8, and maybe other engines) to deopt to a
dictionary instead of a class. Instead of `assign` + `delete`, manually
iterate over all the properties, like the JSX runtime does.

To avoid copying the object twice I moved the `ref` prop removal to come
before handling default props. If we already cloned the props to remove
`ref`, then we can skip cloning again to handle default props.
2024-04-05 13:06:39 -04:00
Sebastian Markbåge
cbb6f2b546 [Flight] Support Blobs from Server to Client (#28755)
We currently support Blobs when passing from Client to Server so this
adds it in the other direction for parity - when `enableFlightBinary` is
enabled.

We intentionally only support the `Blob` type to pass-through, not
subtype `File`. That's because passing additional meta data like
filename might be an accidental leak. You can still pass a `File`
through but it'll appear as a `Blob` on the other side. It's also not
possible to create a faithful File subclass in all environments without
it actually being backed by a file.

This implementation isn't great but at least it works. It creates a few
indirections. This is because we need to be able to asynchronously emit
the buffers but we have to "block" the parent object from resolving
while it's loading.

Ideally, we should be able to create the Blob on the client early and
then stream in it lazily. Because the Blob API doesn't guarantee that
the data is available synchronously. Unfortunately, the native APIs
doesn't have this. We could implement custom versions of all the data
read APIs but then the blobs still wouldn't work with native APIs. So we
just have to wait until Blob accepts a stream in the constructor.

We should be able to stream each chunk early in the protocol though even
though we can't unblock the parent until they've all loaded. I didn't do
this yet mostly because of code structure and I'm lazy.
2024-04-05 12:49:25 -04:00
Sebastian Markbåge
f33a6b69c6 Track Owner for Server Components in DEV (#28753)
This implements the concept of a DEV-only "owner" for Server Components.
The owner concept isn't really super useful. We barely use it anymore,
but we do have it as a concept in DevTools in a couple of cases so this
adds it for parity. However, this is mainly interesting because it could
be used to wire up future owner-based stacks.

I do this by outlining the DebugInfo for a Server Component
(ReactComponentInfo). Then I just rely on Flight deduping to refer to
that. I refer to the same thing by referential equality so that we can
associate a Server Component parent in DebugInfo with an owner.

If you suspend and replay a Server Component, we have to restore the
same owner. To do that, I did a little ugly hack and stashed it on the
thenable state object. Felt unnecessarily complicated to add a stateful
wrapper for this one dev-only case.

The owner could really be anything since it could be coming from a
different implementation. Because this is the first time we have an
owner other than Fiber, I have to fix up a bunch of places that assumes
Fiber. I mainly did the `typeof owner.tag === 'number'` to assume it's a
Fiber for now.

This also doesn't actually add it to DevTools / RN Inspector yet. I just
ignore them there for now.

Because Server Components can be async the owner isn't tracked after an
await. We need per-component AsyncLocalStorage for that. This can be
done in a follow up.
2024-04-05 12:48:52 -04:00
Andrew Clark
e3ebcd54b9 Move string ref coercion to JSX runtime (#28473)
Based on:

- #28464

---

This moves the entire string ref implementation out Fiber and into the
JSX runtime. The string is converted to a callback ref during element
creation. This is a subtle change in behavior, because it will have
already been converted to a callback ref if you access element.prop.ref
or element.ref. But this is only for Meta, because string refs are
disabled entirely in open source. And if it leads to an issue in
practice, the solution is to switch to a different ref type, which Meta
is going to do regardless.
2024-04-05 10:53:11 -04:00
Jan Kassens
76bb13cd26 First attempt at making the linter work with advanced TypeScript syntax
First attempt at making the linter work with advanced TypeScript syntax 

Falls back to the babel parser for some advanced syntax like string template 
syntax. 

This is pretty hacky as it doesn't take in any parsing options that are 
configured for the outer ESLint parser, not sure how that could be handled.
2024-04-04 18:31:31 -04:00
Sebastian Markbåge
fd0da3eef1 Remove _owner field from JSX elements in prod if string refs are disabled (#28739)
In prod, the `_owner` field is only used for string refs so if we have
string refs disabled, we don't need this field. In fact, that's one of
the big benefits of deprecating them.
2024-04-04 11:20:15 -04:00
Lauren Tan
9a9e03b915 Add playground e2e to ci
This only runs on main since it takes a while to run (about ~4 mins)
2024-04-03 18:12:30 -04:00
Lauren Tan
07a3404c5f Add basic playwright e2e test to playground 2024-04-03 18:12:29 -04:00
Lauren Tan
ee24e3fc16 Dogfood uMC polyfill in playground 2024-04-03 18:12:29 -04:00
Joe Savona
f33e63838d Update copyrights to reference Meta instead of Facebook
Thanks @zpao!!! This was mostly his work, i just fixed up the last bit.
2024-04-03 08:43:36 -07:00
Joe Savona
0846daaa54 Commit message script captures non-ghstack messages
Tested by manually passing in the commit message from 
1028504dc8 
and verifying that it grabbed the description from GH.
2024-04-02 20:55:41 -07:00
Joe Savona
852301828b Remove reactive_ir crate 2024-04-02 21:03:25 -07:00
Joe Savona
8a43e954a2 s/Forget/React Compiler/ in Rust port 2024-04-02 16:49:31 -07:00
Joe Savona
5ec3f02ee5 Rename docs/architecture.md to design_goals per contents 2024-04-02 16:05:11 -07:00
Joe Savona
90dd2084e2 ValidateNoCapitalizedCalls checks capitalized methods 2024-04-03 08:02:40 -07:00
Joe Savona
4c1aae4f3b [be] Cleanup removed directories from prettier script 2024-04-03 08:02:39 -07:00
Joe Savona
94156d7014 Repro for unmemoized value due to assignment to a context variable 2024-04-03 08:02:38 -07:00
Joe Savona
284f776420 [be] Clean up already fixed "bug" fixtures
These are already fixed, renamed to clarify that they are just repros of 
previous bugs
2024-04-02 12:43:44 -07:00
Joe Savona
a53f208f5e Fixture for conditional use()
This is mostly to ensure we don't flag this as a conditional hook, but since i'm 
here i made it a proper test.
2024-04-02 11:36:11 -07:00
Joe Savona
8b9edafa95 Support use operator
Implements support for use: 

* Teaches InferReactivePlaces to treat use() result as reactive 

* Teaches FlattenScopesWithHooks to also flatten scopes with use() 

Handles both `use()` and `React.use()`.
2024-04-02 11:28:01 -07:00
Lauren Tan
e61f983264 Fix yarn dev 2024-04-02 12:18:12 -04:00
Lauren Tan
5485ed0855 Standardize on banner2 plugin 2024-04-02 12:13:41 -04:00
Lauren Tan
f01f74d9e1 Also build react-forget-runtime with rollup
This adds rollup to the runtime and adds a new plugin to add the license banner 
+ inject the `"use no memo"` directive. We need to inject it there as rollup 
currently strips out unknown directives during bundling.
2024-04-02 12:13:40 -04:00
Lauren Tan
b4eeae6f5d Share rollup packages across workspace 2024-04-02 12:13:39 -04:00
Lauren Tan
91826d4924 Configure rollup to strip comments and whitespace
For now this configures rollup to strip out comments in DEV builds and 
whitespace. Unfortunately there's no easy way to do this in just terser alone or 
other minifiers/manglers, so I had to add prettier as well to re-format the 
minified code. This does make the build a little bit slower: 

``` before: yarn build  118.96s user 12.38s system 185% cpu 1:10.81 total after: 
 yarn build  121.55s user 12.90s system 183% cpu 1:13.17 total ``` 

Eventually I would like to have a similar setup to React's rollup config where 
we can have DEV and prod builds. After the repo merge we could probably share or 
reuse bits of React's rollup config.
2024-04-02 12:13:38 -04:00
Joe Savona
f0806c43fe Remove flag for enableEarlyReturnInReactiveScopes (always enable) 2024-04-01 15:37:03 -07:00
Joe Savona
ef285e0702 Remove memoizeJsxElements flag 2024-04-01 15:34:42 -07:00
Joe Savona
b00300c4cf Remove disableAllMemoization flag 2024-04-01 15:18:59 -07:00
Joe Savona
69f03dc4c4 Remove unused environment flags
These flags aren't used anymore
2024-04-01 15:16:31 -07:00
Sathya Gunasekaran
cd3f56bb18 [eslint] Enforce generic array type syntax
Fix and enforce generic array type syntax
2024-04-02 15:31:25 +01:00
Sathya Gunasekaran
1de6c5aea2 [eslint] Don't lint generated files
drive by fix multi line comments in .eslintrc
2024-04-02 12:03:26 +01:00
Lauren Tan
ba661c80f3 Remove comments in builds 2024-04-02 10:32:24 -04:00
Lauren Tan
4e6aa42773 Upgrade to typescript 5.4.3
This PR makes all packages share the same typescript version and updates us to 
latest versions of typescript, ts-node, typescript-eslint/eslint-plugin and 
typescript-eslint/parser. 

I also noticed that the tsconfig we were extending (node18-strictest) was 
deprecated, so I switched us over to one that's more up to date. 

Also had to make a couple of small changes to the playground so that continues 
to build correctly.
2024-04-02 10:32:23 -04:00
Lauren Tan
5516cbb708 [ez] Fix unneeded path in copyright script 2024-04-02 10:32:22 -04:00
Lauren Tan
0af58d6c9e [ez] Clean up unused test filters 2024-04-02 10:32:21 -04:00
Sathya Gunasekaran
d5b6e584fb [hir] Add support for directives
Previously, we would drop directives inside a component or hook but this is 
problematic with reanimated which uses `'worklet'` to mark components from 
compilation. 

This PR adds a directive to HIRFunction and ReactiveFunction and codegens the 
directive add the end. No processing is done on the directives themselves. 

Babel seems to store the directives on a BlockStatement, rather than on the 
Function but I've stored it on the Function types because we only support 
compiling functions and the spec defines directives as occuring in the initial 
statement list of a function: > A Directive Prologue is the longest sequence of 
ExpressionStatements > occurring as the initial StatementListItems or 
ModuleItems of a > FunctionBody, a ScriptBody, or a ModuleBody and where each > 
ExpressionStatement in the sequence consists entirely of a > StringLiteral token 
followed by a semicolon.
2024-04-02 11:25:10 +01:00
Joe Savona
1c3313707f ReactiveTerminal: add loc and id to all variants 2024-04-01 15:03:47 -07:00
Joe Savona
832285fed2 Only create mutable ranges for phis *mutated* later
This PR was the result of a long chain of ~yak-shaving~ debugging kicked off as 
a result of fixing up invariants. Where this started was that i noticed some 
cases of loops where the first instance we saw of a reactive scope was after its 
starting instruction. Eg instruction N would have an operand with scope 
Start:End, where Start was _before_  N. One of the cases involved a phi with a 
backedge. Then i noticed that we assign scopes differently for phis with and 
without backedges: 

``` 

[1] let x0 = init; 

[2] if (x0 < limit) { 

[3] x1 += increment; 

} 

x2 = phi(x0, x1); 

[4] x2; 

``` 

The phi isn't mutated _or_ reassigned after its creation, so we don't assign a 
mutable range to the phi or any of its operands. We also don't create a scope 
for `x`. 

But change the `if` to a `while` and now the phi moves - now there's a backedge: 

``` 

[1] let x0 = init; 

[2] while (x0 < limit) { 

x2 = phi(x0, x2); // now this is "mutated" later!!! 

[3] x2 += increment; 

} 

[4] x2; 

``` 

What was happening here is that x2 has a mutable range which is "after" the phi 
instruction, so it would appear that the phi was actually being mutated later. 
Ie, this was treated equivalently to the original "if" version, but with a 
mutation: 

``` 

let x = []; 

if (cond) { 

x = {}; 

} 

mutate(x); // later mutation of the phi 

``` 

But these latter two cases are different! We only need to (should) create a 
mutable range for a phi _if its value is actually mutated_. If it's just being 
reassigned, well then it shouldn't matter if there are back edges or not. 

So this PR implements that intuition: only create a mutable range for a phi if 
it is actually _mutated_ later, ie don't assign a mutable range if it is only 
_reassigned_ later. Concretely in InferMutableRanges: 

* InferMutableLifetimes no longer has to initialize a range for phis during the 
first pass (inferMutableRangesForStores=false). We wait to see if the phi is 
mutated during the main fixpoint iteration of InferMutableRanges 

* The main fixpoint iteration in InferMutableRanges already aliases phi operands 
if the phi is later mutated, which will extend the end of the mutable range of 
all the operands accordingly. 

* Finally, InferMutableLifetimes's second run 
(inferMutableRangesForStores=true), we ensure that any phis mutated later have a 
valid mutable range, specifically setting the `start` of the range since the 
fixpoint only updates the `end` value.
2024-04-01 14:21:48 -07:00
Lauren Tan
8d2cd323a4 Include package.json when bundling for oss
This was causing issues for an external partner's build pipeline which expected 
each package to have its own package.json.
2024-03-29 12:19:04 -04:00
Mofei Zhang
5656d5f078 [config] Change compiler defaults to prepare for oss 2024-03-29 09:31:09 -04:00
Lauren Tan
9d20aa2819 Don't compile node_modules on playground 2024-03-28 10:46:56 -04:00
Lauren Tan
7748ce8f3f Update compiler naming in remaining error text 2024-03-28 10:40:05 -04:00
Lauren Tan
df5ff9753a Fix jest script 2024-03-28 10:40:05 -04:00
Lauren Tan
f4229cdb7f Rename error type 2024-03-28 10:40:04 -04:00
Lauren Tan
638f8e3ddc Remove [ReactForget] prefix from error messages 2024-03-28 10:40:03 -04:00
Mofei Zhang
5166869204 [patch] Fix control flow bug in PropagateScopeDeps
A dependency D from either an instruction or scope is poisoned if there may be a 
(non-linear) jump instruction between it and the start of its immediate parent 
scope. Poisoned dependencies are added as conditional dependencies to their 
parent scope. 

(done: reduce false positives in scopes that begin after return/throw) (done: 
fix bugs in recording and joining exhaustive conditional deps) (done: flesh out 
commit message, clean up PR, add more fixtures) 

--- \## Bug details: 

Take a simple example: ```js target: {   instrA;   if (...) {     instrB;     
break target;   } else {     instrC;   }   instrD;   // ... } instrE; // ... ``` 

This diagram shows how we represent this program in the reactive IR. - Blocks 
are represented as a list of nodes. - Green nodes show instructions and value 
blocks (simplified as a single instruction). - Pink nodes show terminals, which 
transfer control to a subtree of nodes. <img width="450" alt="image" 
src="https://github.com/facebook/react-forget/assets/34200447/930789f2-39cd-4ea8-b12a-530042807b46"> 

Prior to this PR, PropagateReactiveScopeDeps was incorrect because it assumed 
that a block's instructions are evaluated unconditionally (which is how HIR 
basic blocks work). E.g. if a reactive scope enclosed `block 1`, we assume that 
`instrA` and `instrD` both will evaluate unconditionally. 

This failed to account for `jump` instructions like break, continue, return, and 
throw. This may result in invalid hoisting of PropertyLoads (i.e. Forget output 
may throw when source does not throw). Note that other terminals (e.g. if and 
loops) are not affected as they are self contained subtrees that evaluate 
sequentially. 

With the changes in this PR, we mark `block 1` as poisoned upon encountering the 
`break` instruction. While `block 1` is active and poisoned, it will determine 
how visited dependencies are added. 

Here, added solid lines show unconditional dependencies, dashed lines show 
conditionally accessed dependencies: - dependencies from `instrB, instrC` are 
conditional because they are within conditional subtrees - dependencies from 
`instrD` are conditional because it is within a poisoned block within its parent 
scope. 

<img width="450" alt="image" 
src="https://github.com/facebook/react-forget/assets/34200447/81980f68-7e65-4bd7-ba94-3f0c26550e5c"> 

--- Recapping an offline discussion with @josephsavona: this pass would really 
benefit from operating on HIR. The minimal work needed for this pass to run on 
HIR is to rewrite and reorder  `AlignReactiveScopesToBlockScopes` to operate on 
HIR. 

The following diagram shows what HIR blocks look like for the same code. 
Evaluating hoistable PropertyLoad dependencies for a scope enclosing 
`instr{A-D}` is much simpler:  just evaluate whether the PropertyLoad evaluates 
for every path between `bb0` and `bb4`. <img width="250" alt="image" 
src="https://github.com/facebook/react-forget/assets/34200447/44b38939-defb-4b29-878d-4445ec6ccc06"> 

---
2024-03-27 20:26:18 -04:00
Mofei Zhang
b98b569017 [DeriveMinimalDeps] Account for conditional / poisoned accesses within
conditionals 

This change is needed for #2752. To minimize renaming `error.fixture` -> 
`fixture` files, I'm reordering this PR to earlier in the stack. 

Prior to the fix in #2752, we only expected unconditional accesses within 
`depsInCurrentConditional`, which records instructions directly within a 
conditional block (not including nested conditional blocks).
2024-03-27 20:26:18 -04:00
Mofei Zhang
2782fa2664 [rhir] Preserve block labels + target blockIds for break/continue terminals
RFC: we can either retain break/continue target ids (instead of pruning them in 
`buildReactiveFunction`) or re-implement the same logic in `propagateScopeDeps` 
(ignore implicit breaks; match unlabeled break / continues to their closest loop 
/ while parent terminal). 

If we go ahead with this approach, I'll clean up this PR (add relevant types and 
comments)
2024-03-27 20:26:17 -04:00
Mofei Zhang
877c490fa3 [visitors] Visit loop body before update 2024-03-27 20:26:16 -04:00
Mofei Zhang
1515cb3218 [be][sprout] Add ErrorBoundary to test exceptions in sequentialRenders 2024-03-27 20:26:15 -04:00
Mofei Zhang
4d37a599e3 [be][tests] Move PropagateScopeDep fixtures to separate directory 2024-03-27 20:26:14 -04:00
Mofei Zhang
a37f242f59 [be][DeriveMinimalDeps] Check current assumptions for conditional control flow 2024-03-27 20:26:13 -04:00
Sathya Gunasekaran
fd3ae0ca0a [type] Conservatively type return values from reanimated
Typing them as mixed read only seems safe because the fields are primitives but 
lets be conservative and type as Poly.
2024-03-28 12:32:25 +00:00
Sathya Gunasekaran
0a3d1c3d31 Add type defs for reanimated
The reanimated babel plugin specifically looks for args to their hooks that are 
callbacks, then it workletizes the body of that callback so it can run on the 
main thread. 

But, forget extracts that callback into a temporary variable and then replaces 
the previously inlined callback as an identifier, so that breaks reanimated's 
babel plugin. so what happens is some of the previously workletized functions no 
longer do after forget runs, which throws a runtime error about a non-worklet 
function running on the main thread. 

Reanimated expects this: ``` const animatedGProps = useAnimatedProp(function () 
{ ... }) ``` 

But forget does this: ``` const t0 =function () { ... } const animatedGProps = 
useAnimatedProp(t0) ``` 

With the type definitions, Forget no longer assumes the args to reanimated APIs 
escape so Forget does not memoize and they stay as is.
2024-03-28 12:26:22 +00:00
Sathya Gunasekaran
a6c6855dd1 [be] Remove unused properties arg from addHook
This is never used by any caller.
2024-03-28 12:26:22 +00:00
Joe Savona
91f7bc8be7 Ensure valid mutable ranges for all scopes; fix ranges for context vars
Our current validation fails to detect some invalid cases of mutable ranges — 
namely, ranges that are fully or partially uninitialized, with start or 
start+end still set to zero. 

This PR fixes these cases, starting by validating that the ranges for _all_ 
reactive scopes are valid: start >= 1, and end <= (last instr id + 1). This 
exposed the invalid cases, which are also fixed here: 

* During AnalyzeFunctions, we need to reset identifier ranges and scopes when 
exiting an inner function. This has to happen *after* the effects have been 
translated to the function deps/context operands, in order for 
InferRefenceEffects to continue working on the outer function. Previously I did 
this at the start of InferReactiveScopeVariables, but that's insufficient bc the 
incorrect ranges could also influence InferMutableRanges. AnalyzeFunctions is 
the point at which we compute the ranges for identifiers in the inner function, 
so it's the most ideal place to clean those up so they don't influence the outer 
function. 

* In InferMutableLifetimes, we need to ensure that context variable identifiers 
end up with a mutable range starting where they are declared, and ending with 
their last assignment. We now track declarations and extend their mutable range 
to account for each reassignment.
2024-03-26 13:29:24 -07:00
Joe Savona
fbd68eef7f Fix for invalid mutable range in phi with backedge
Fixes the repro added in 947832009997bf9149e88e583c46cc39f6a6136c - previously 
when computing mutable ranges of phis, we didn't check that all operands had 
been visited. This meant that a backedge could allow a phi's mutable range to 
start at 0. Then in PropagateScopeDeps, we might see reject dependencies of a 
scope since they appeared to start after a scope — only because the scope's 
start was incorrectly too early. 

The fix here is to initially set phi.id mutable ranges based on only on operands 
that are already visited. Then, during/after the fixpoint iteration of 
InferMutableRanges, we start account for all operands since we know they've been 
visited at least once and have a real range.
2024-03-25 21:36:33 -07:00
Joe Savona
48ecaf92d5 Reset scopes from AnalyzeFunctions in InferReactiveScopeVariables
Updates InferReactiveScopeVariables to first prune scopes attached during 
AnalyzeFunctions. This ensures that after this pass the only scopes that exist 
on identifiers in the outer program are those that the pass explicitly inferred, 
and not accidentally leftover.
2024-03-25 15:31:23 -07:00
Joe Savona
29ab0f835e Repro for scopes from AnalyzeFunction influencing outer compilation
We share identifiers between outer functions and inner function expressions, 
which means that scopes inferred during AnalyzeFunctions can be retained on 
Identifiers in the outer function. If InferReactiveScopeVariables doesn't happen 
to visit an identifier we currently retain that scope information and use it 
when constructing scopes, even if there doesn't technically need to be a scope 
for that value. 

Fixed in the next PR.
2024-03-25 15:31:22 -07:00
Lauren Tan
e6ce5e4922 [eslint] Update devDep to match types 2024-03-25 15:00:34 -04:00
Lauren Tan
15abd38911 Cleanup unused eslint-browser and js-fuzzer packages
These were never used, so let's clean them up
2024-03-25 15:00:31 -04:00
Sathya Gunasekaran
70302048a0 Update yarn.lock 2024-03-25 16:41:13 +00:00
Joe Savona
2ce112dbf2 Repro for missing dep with while/if using externally declared "index" variable
What happens here is that the phi node for `i` has its mutable range set to 
start at 0, because it has a back edge and we haven't initialized the mutable 
ranges of all its operands yet when we iterate the operands and set range.start 
= min(start of operand starts). 

Then the corresponding scope has its range set to start at 0 too. When 
PropagateScopeDeps runs it sees that `b` is from instruction 1, which is after 
the start of the scope (0), so it thinks `b` isn't a valid dependency. 

The fix is in InferMutableRanges, where we need to make sure that phis ignore 
their operand's ranges until those ranges are initialized.
2024-03-22 21:49:39 -07:00
Sathya Gunasekaran
db7e7c7fae Support method call when validating useEffect
Treat MethodCall similarly to CallExpression when validation useEffect
2024-03-22 23:42:27 +00:00
Lauren Tan
1f45b13fda Update yarn.lock 2024-03-22 18:59:44 -04:00
Joe Savona
200d2c379d Fix for loops with value block index initial value 2024-03-22 14:51:36 -07:00
Joe Savona
f4ff1a28e7 Fix for destructuring with partial context variables 2024-03-22 14:32:50 -07:00
Joe Savona
73fa712108 Repro for destructuring with partial context variables 2024-03-22 14:32:47 -07:00
Jan Kassens
d7d82e7bee Correct eslint-plugin-react-compiler dependencies
Correct eslint-plugin-react-compiler dependencies 

- The eslint plugin doesn't actually depend on the babel plugin as it compiles 
in the dependencies. - `zod` and `zod-validation-error` were missing, but 
required to run the plugin. - Update to the `hermes-parser` dependency just to 
keep it updated.
2024-03-22 17:03:30 -04:00
Joe Savona
4575c8a5f6 Detect hoisting where the reference is a reassignment
Our logic to detect hoisting relies on Babel's `isReferencedIdentifier()` to 
determine whether a reference to an identifier is a reference or a declaration. 
The idea is that we want to find references to variables that may be hoistable, 
before the declaration — the definition of hoisting. But due to the bug in 
isReferencedIdentifier, we skipped over reassignments of hoisted variables. The 
hack here checks if an identifier is a direct child of an AssignmentExpression, 
ensuring we visit reassignments.
2024-03-22 13:49:50 -07:00
Joe Savona
fb0f7e5c39 Repro for closures reassigning hoisted let variables
Adds examples for closures that reassign hoisted let bindings. We currently 
compile these as context variables without hoisting, so we hit an invariant in 
InferReferenceEffects when the variable is referenced before being declared.
2024-03-22 11:53:29 -07:00
Joe Savona
e7d0c81d13 Add todo for for loops with context iterator variable
Detect the previous case — for loops where the iterator is a context variables — 
and throw a todo rather than hitting the invariant.
2024-03-22 11:46:28 -07:00
Joe Savona
bc516409a6 Repro of for loop with context variable iterator variable
`let` bindings of context variables are lowered to a DeclareContext + 
StoreContext, which breaks codegen for `for` loops which expect that all 
statements of the init block will lower to variable declarations. The two 
instructions produce a variable declaration and a reassignment.
2024-03-22 09:15:29 -07:00
Joe Savona
7dc08d79be PropagateScopeDeps treats switch w only default as unconditional
If we have a switch with only a default case, then that code will be executed 
unconditionally. PropagateScopeDeps can take advantage of this to record 
dependencies in these cases as unconditional, which avoids the issue seen in the 
previous PR.
2024-03-22 09:02:56 -07:00
Joe Savona
3229302222 Repro for "expect trees to be >=2 elements long" invariant
These cases have a switch with only a default, so we create a "conditional" 
child dependency tree and no other trees which hits the invariant.
2024-03-22 08:53:30 -07:00
Mofei Zhang
ee93b62f51 [ez] Patch BuildHIR to match unlabeled breaks to switch and loops 2024-03-22 16:34:09 -04:00
Mofei Zhang
f6472522da [repro] Fixture for incorrect lowering for break (label within loop)
--- 

unlabeled breaks must goto the nearest iteration or switch boundary (not labeled 
block) 

--- 

Sprout fails with the following: 

``` 

FAIL: bug-unlabeled-break-within-label-loop 

>> Unexpected error during test: 

Found differences in evaluator results 

Non-forget (expected): 

(kind: ok) ["0 @A","0 @B","0 @C","1 @A"] 

Forget: 

(kind: ok) ["0 @A","0 @B","0 @C","1 @A","1 @C"] 

```
2024-03-22 16:14:35 -04:00
Mofei Zhang
e047db8d03 [repro] Repro for control flow bug in PropagateScopeDependency
--- 

Thanks to @josephsavona for finding this bug. This is another example of why we 
really want hir-everywhere. 

Forget output currently nullthrows because we believe `obj.a` is run 
unconditionally in source (missing the break/returns out of this scope)
2024-03-21 17:44:13 -04:00
Joe Savona
3a6a2e7e4a Repro for "context variables are always mutable" error w callbacks
I haven't debugged to understand exactly why this pattern fails, but there are a 
few instances of this internally. It's especially weird because 

```javascript 

// @enableAssumeHooksFollowRulesOfReact 
@enableTransitivelyFreezeFunctionExpressions 

function Component(props) { 

const [_state, setState] = useState(); 

const a = () => { 

return b(); 

}; 

const b = () => { 

return ( 

<> 

<div onClick={() => onClick(true)} /> 

<div onClick={() => onClick(false)} /> // <---- only repros if there's a second 
call! 

</> 

); 

}; 

const onClick = (value) => { 

setState(value); 

}; 

return <div>{a()}</div>; 

} 

``` 

Here, if `b()` only had one nested function expression that called `onClick` it 
would work. Also, if we disable `@enableTransitivelyFreezeFunctionExpressions` 
then it works. 

But the combination of multiple calls plus that mode causes "context variables 
are always mutable". I'm guessing we're freezing `onClick` twice and the second 
time reports an error since it calls `setState`.
2024-03-21 21:40:02 -07:00
Joe Savona
f18a01bf59 Add todo for destructuring to context variables
We don't have `DestructureContext` yet, this adds a todo for the cases where we 
should use one.
2024-03-21 17:12:09 -07:00
Joe Savona
e8073ff16c Repro for destructure declaration of context variable
We don't have a `DestructureContext` equivalent of `StoreContext`, so variables 
that are declared via destructuring and later reassigned trigger the invariant 
that all mentions of a variable must be consistently local or context. The next 
PR adds a todo for this case.
2024-03-21 17:12:09 -07:00
Joe Savona
231f29f6f3 Fix using context variable as JSX element tag
Updates the lowering for JSX element tag names to check if the identifier is a 
context or local variable and use the appropriate instruction kind.
2024-03-21 17:12:08 -07:00
Joe Savona
21bb8f9e75 Repro for inconsistent context/local variable reference
We currently assume JSX element tags are always locals, and need to check which 
load kind to use. Fixed in the next PR.
2024-03-21 17:12:08 -07:00
Joe Savona
0e72e2a00e Repro for nested method calls causing promoted property load invariant 2024-03-21 14:11:07 -07:00
Joe Savona
1b6afa1879 Repro for uninitialized value due to shadowed function name 2024-03-21 14:11:06 -07:00
Joe Savona
a6b3adbe68 Repro for unsupported recursive function expressions 2024-03-21 14:11:06 -07:00
Joe Savona
d31e10b406 Fix for type aliases in function inferring as function deps 2024-03-21 14:11:05 -07:00
Joe Savona
4bc676d3db Repro for undefined "hoisted" variable from type alias
We inadvertently think the type annotation on the function expression param is 
an identifier and create a LoadLocal for it, which fails. This happens to trip 
up on the InferReferenceEffects initialization check, which we had assumed would 
only fire for invalid hoisting cases (hence the specific error message).
2024-03-21 14:11:04 -07:00
Joe Savona
241b35463c Fix PruneMaybeThrows to update phi operand predecessor ids
When PruneMaybeThrows removes maybe-throw terminals, it's possible that the 
block in question reassigned a value s.t. it appears as a later phi operand. 
That phi has to be rewritten to reflect the updated predecessor block. 

Here we track these rewrites (transitively) and rewrite phi operands 
accordingly.
2024-03-21 14:11:04 -07:00
Joe Savona
1cf8d9bc8b Repro for missing predecessor with try/catch
Found when running the compiler on a large swath of internal code. 
PruneMaybeThrows rewrites terminals, but the logic to update subsequent phis was 
incorrectly dropping phis rather than rewriting them. Fixed in the next PR.
2024-03-20 14:07:28 -07:00
Mofei Zhang
14d54869ed [patch][codegen] don't reuse babel nodes in codegen for dependencies
--- 

Reusing optionalMemberExpression nodes recently led to a bug when compiling 
Forget playground. 

```js 

// the two a?.b's here should be different nodes! 

if (a?.b !== $[0]) { 

// ... 

$[0] = a?.b; 

} 

``` 

Forget playground uses `babel-plugin-react-forget` and `next/babel`. Reusing the 
same node in two positions in the AST lead to invalid mutations: 

- the first `a?.b` is visited and transpiled to `a === void 0 ? ...`, which (1) 
inserts nodes between the original node and its parent and (2) mutates `a?.b` in 
place to a non-optional call 

- the second `a?.b` in source gets updated to `a.b` and does not get visited 
again 

```js 

// Source in `EditorImpl.tsx` 

compilerOutput.kind === "err" ? compilerOutput.error.details : [] 

// Forget transformed: 

if ($[2] !== compilerOutput.kind || $[3] !== compilerOutput.error?.details) { 

t4 = compilerOutput.kind === "err" ? compilerOutput.error.details : []; 

$[2] = compilerOutput.kind; 

// this is good! 

$[3] = compilerOutput.error?.details; 

$[4] = t4; 

} else { 

t4 = $[4]; 

} 

// After next/babel 

if ($[2] !== compilerOutput.kind || $[3] !== ((_compilerOutput$error = 
compilerOutput.error) === null || _compilerOutput$error === void 0 ? void 0 : 
_compilerOutput$error.details)) { 

t4 = compilerOutput.kind === "err" ? compilerOutput.error.details : []; 

$[2] = compilerOutput.kind; 

// Oh no!! 

$[3] = _compilerOutput$error.details; 

$[4] = t4; 

} else { 

t4 = $[4]; 

} 

```
2024-03-20 19:29:02 -04:00
Mofei Zhang
4daa4eceb7 [validation] More detailed error diagnostics for validatePreserveExistingMemo
--- 

This should make it easier to grep through error diagnostics to understand state 
of the codebase: 

- no matching dependences -> likely that source is ignoring eslint failures 

- differences in ref.current access -> non-backwards compatible refs 

- subpath -> should be fixable in the compiler (unless source is ignore eslint 
failures)
2024-03-20 16:05:24 -04:00
Mofei Zhang
63bb781781 [validation] Reduce false positives in validatePreserveExistingMemo
--- 

Previously (in #2663), we check that inferred dependencies exactly match source 
dependencies. As @validatePreserveExistingMemoizationGuarantees checks that 
Forget output does not invalidate *any more* than source, we now allow more 
specific inferred dependencies when neither the inferred dep or matching source 
dependency reads into a ref (based on `.current` access). 

See added comments for more details
2024-03-20 16:05:23 -04:00
Mofei Zhang
b35779c6a4 [logger] Add CompilerDiagnostic category, thread Logger into environment 2024-03-20 13:48:18 -04:00
Joe Savona
346f6c13a8 Todo for fbt with multiple fbt:enum
I need to do more debugging to figure out exactly why the example earlier fails 
— but whatever it is, it's clearly a matter of the fbt plugin relying on some 
specifics of source locations. 

Here we just detect multiple instances of `<fbt:enum>` within a given `<fbt>` 
tag and throw a todo.
2024-03-19 16:39:05 -07:00
Joe Savona
f2b0b656b2 More accurate source locations for JSX opening/closing tags
<img width="553" alt="Screenshot 2024-03-19 at 4 41 15 PM" 
src="https://github.com/facebook/react-forget/assets/6425824/e87ee704-6c67-4e10-824b-71e97e7e19f5"> 

Slightly improves source locations for JSX elements so that the opening and 
closing tag have distinct locations that match up with source. The identifier 
itself within the closing tag still has the wrong location, but at least this is 
an improvement. 

Doesn't fix the fbt thing but it was worth a try.
2024-03-19 16:26:32 -07:00
Joe Savona
501481ccea Repro for fbt:enum source location issue
Fbt enums appear to rely on source locations and something that we're doing 
(maybe destructuring?) isn't preserving locations such that the fbt plugin 
breaks.
2024-03-19 15:21:55 -07:00
Joe Savona
7257c9b4f8 Fix for <fbt> with local import
Fbt violates the JSX spec by using a lowercase function as a tagname, even 
though lowercase names are reserved for builtins. Here we detect cases where 
there is an `<fbt>` tag where `fbt` is a local identifier and throw a todo.
2024-03-19 14:51:35 -07:00
Joe Savona
93e0815b26 Remove unnecessary scopes for value blocks
The example earlier in the stack had unreachable code in the output because 
there was an unnecessary memoization block around an assignment. This was a 
holdover from before we moved the logic to expand mutable ranges for phis from 
LeaveSSA to InferMutableRanges. We were conservatively assigning a mutable range 
to all variables with a phi, even those that didn't strictly need one. 

Removing the range extension logic in LeaveSSA fixed the issue, but uncovered 
the fact that AlignReactiveScopesToBlockScopes was missing a case to handle 
optionals. 

## Test Plan 

Synced internally and ran a snapshot/comparison of compilation before/after 
(P1197734337 for those curious). The majority of components get fewer memo slots 
thanks to not needing to memoize non-allocating value block expressions like 
ternaries/optionals. In a few cases, the fact that we're no longer assigning a 
mutable range for value blocks (unless there is actually a mutation!) means we 
get more fine-grained memoization and increase the number of memoization blocks. 
So overall this appears to be correct, improve memoization, and reduce code 
size.
2024-03-19 13:28:47 -07:00
Joe Savona
c03f1c809a Extract shared-runtime helper for validating memoization
Extracts a helper from the repro earlier in the stack into a helper in 
shared-runtime. This makes it easy to verify that memoization is actually 
working.
2024-03-19 13:28:47 -07:00
Joe Savona
57fcf7d305 Repro for nonproblematic unreachable code
This case is specific to early return inside an inlined IIFE (which can often 
occur as a result of dropping manual memoization). When we inline IIFEs, as a 
reminder we wrap the body in a labeled block and convert returns to assignment 
of a temporary + break out of the label. 

Those reassignments themselves are getting a reactive scope assigned since the 
reassigned value has a mutable range. They don't really need a mutable range or 
scope, though. And then the presence of the `break` statements means that we can 
sometimes exit out of the scope before reaching the end - leading to unreachable 
code. 

This can only occur though where _all the values are already memoized_. So the 
code works just fine and even memoizes just fine - it's just that we have some 
extraneous scopes and there is technically unreachable code. I'll fix in a 
follow-up, adding a repro here.
2024-03-19 13:28:46 -07:00
Sathya Gunasekaran
b5a7fe4e1c [hir] Improve error message for mutating state 2024-03-19 17:06:17 +00:00
Sathya Gunasekaran
9358aeab34 [scripts] Add script to pull commit message from PR description 2024-03-19 11:23:43 +00:00
Mofei Zhang
cea84a41bc validatePreserveExistingMemoizationGuarantees ensures compiler preserves subset
of dependencies from source 

--- 

`validatePreserveExistingMemoizationGuarantees` previously checked 

- manual memoization dependencies and declarations (the returned value) do not 
"lose" memoization due to inferred mutations 

``` 

function useFoo() { 

const y = {}; 

// bail out because we infer that y cannot be a dependency of x as its 
mutableRange 

// extends beyond 

const x = useMemo(() => maybeMutate(y), [y]); 

// similarly, bail out if we find that x or y are mutated here 

return x; 

} 

``` 

- manual memoization deps and decls do not get deopted due to hook calls 

``` 

function useBar() { 

const x = getArray(); 

useHook(); 

mutate(x); 

return useCallback(() => [x], [x]); 

} 

``` 

This PR updates `validatePreserveExistingMemoizationGuarantees` with the 
following correctness conditions: 

*major change* All inferred dependencies of reactive scopes between 
`StartMemoize` and `StopMemoize` instructions (e.g. scopes containing manual 
memoization code) must either: 

1. be produced from earlier within the same manual memoization block 

2. exactly match an element of depslist from source 

This assumes that the source codebase mostly follows the `exhaustive-deps` lint 
rule, which ensures that deps lists are (1) simple expressions composing of 
reads from named identifiers + property loads and (2) exactly match deps usages 
in the useMemo/useCallback itself. 

--- 

Validated that this does not change source by running internally on ~50k files 
(no validation on `main`, no validation on this PR, and validation on this PR).
2024-03-18 14:50:15 -04:00
Mofei Zhang
81695f62c2 [refactor] Refactor Memoize to two instructions: Start and Finish
--- 

Previously, we always emitted `Memoize dep` instructions after the function 
expression literal and depslist instructions 

```js 

// source 

useManualMemo(() => {...}, [arg]) 

// lowered 

$0 = FunctionExpression(...) 

$1 = LoadLocal (arg) 

$2 = ArrayExpression [$1] 

$3 = Memoize (arg) 

$4 = Call / LoadLocal 

$5 = Memoize $4 

``` 

Now, we insert `Memoize dep` before the corresponding function expression 
literal: 

```js 

// lowered 

$0 = StartMemoize (arg)      <---- this moved up! 

$1 = FunctionExpression(...) 

$2 = LoadLocal (arg) 

$3 = ArrayExpression [$2] 

$4 = Call / LoadLocal 

$5 = FinishMemoize $4 

``` 

Design considerations: 

- #2663 needs to understand which lowered instructions belong to a manual 
memoization block, so we need to emit `StartMemoize` instructions before the 
`useMemo/useCallback` function argument, which contains relevant memoized 
instructions 

- we choose to insert StartMemoize instructions to (1) avoid unsafe instruction 
reordering of source and (2) to ensure that Forget output does not change when 
enabling validation 

This PR only renames `Memoize` -> `Start/FinishMemoize` and hoists 
`StartMemoize` as described. The latter may help with stricter validation for 
`useCallback`s, although testing is left to the next PR. 

#2663 contains all validation changes
2024-03-18 12:09:37 -04:00
Mofei Zhang
37452089fb [ez] CompilerError: optional description and suggestions
--- 

Many compiler errors have neither descriptions nor suggestions (e.g. most `todo` 
or `invariant` errors), so let's make those optional
2024-03-18 12:09:36 -04:00
Jan Kassens
8c8e318bd4 Remove private header from playground
Remove private header from playground 

Before we miss removing this from the public release, I think we can remove this 
header now already. We're still behind a secret URL + password.
2024-03-18 16:53:47 -04:00
Joe Savona
d5e2d9f8d5 Handle fbt:param with only leading or trailing whitespace
Fixes T180504437. We expected `<fbt:param>` to always have no surrounding 
whitespace or have both leading and trailing whitespace, it can have one but not 
the other, though such cases are rare in practice.
2024-03-13 14:54:39 -07:00
Joe Savona
bf64a826a4 Repro for fbt with unexpected child count 2024-03-13 14:54:35 -07:00
Joe Savona
d5eca2ed85 Repro for scope with no declarations (already fixed on this stack)
Repro from T180504728 which reproduced internally and on playground, neither of 
which have #2687 yet. That PR (earlier in this stack) already fixes the issue, 
so i'm just adding the repro to help prevent regressions.
2024-03-15 08:26:43 -07:00
Joe Savona
ec3d36b865 Convert unhandled value block terminal to todo
While i'm here, we know that there are a variety of cases that are not supported 
yet around combining value blocks with other syntax constructs. Since we're 
aware of these cases and detect them, we can make this a todo instead of an 
invariant.
2024-03-13 22:25:18 -07:00
Joe Savona
1301e68ae6 Add todo for label as value block terminal
We need to revisit the conversion from value blocks into ReactiveFunction. Or 
just revisit ReactiveFunction altogether (see my post about what this would look 
like). For now, makes this case a todo.
2024-03-13 22:20:59 -07:00
Joe Savona
d70b2c2c4e Repro for unexpected label as value block terminal
This was one of the last invariants firing internally, this PR adds a minimal 
repro and the next PR makes it a todo.
2024-03-13 22:20:56 -07:00
Joe Savona
459679f91e Support type alias syntax
"Support" in the sense of dropping these on the floor and compiling, rather than 
bailing out with a todo. 

We already don't make any guarantees about which type annotations we'll preserve 
through to the output, so it seems fine for now to just drop type aliases.
2024-03-13 22:17:19 -07:00
Joe Savona
1b5ae0638e Fix block scoping of declarations with early return
I addressed some of the cases that lead to this invariant but there were still 
more. In this case, we have scopes like this: 

``` 

scope @1 declarations=[t$0] { 

let t$0 = ArrayExpression [] 

if (...) { 

return null; 

} 

} 

scope @2 deps=[t$0] declarations=[t$1] { 

let t$1 = Jsx children=[t$0] ... 

} 

``` 

Because scope 1 has an early return, PropagateEarlyReturns wraps its contents in 
a label and converts the returns to breaks: 

``` 

scope @1 declarations=[t$0] earlyReturn={t$2} { 

let t$2 

bb0: { 

let t$0 = ArrayExpression [] 

if (...) { 

t$2 = null; 

break bb0; 

} 

} 

} 

scope @2 deps=[t$0] declarations=[t$1] { 

let t$1 = Jsx children=[t$0] ... 

} 

``` 

But then MergeReactiveScopesThatInvalidateTogether smushes them together: 

``` 

scope @1 declarations=[t$1] earlyReturn={t$2} { 

let t$2 

bb0: { 

let t$0 = ArrayExpression [] // <--- Oops! We're inside a block now 

if (...) { 

t$2 = null; 

break bb0; 

} 

} 

let t$1 = Jsx children=[t$0] ... 

} 

``` 

Note that the `t$0` binding is now created inside the labeled block, so it's no 
longer accessible to the Jsx instruction which follows the labeled block. This 
isn't an issue with promoting temporaries or propagating outputs, but a simple 
issue of the labeled block (used for early return) introducing a new block 
scope. The solution here is to simply reorder the passes so that we transform 
for early returns after other optimizations. This means the jsx element will 
basically move inside the labeled block, solving the scoping issue: 

``` 

scope @1 declarations=[t$1] earlyReturn={t$2} { 

let t$2 

bb0: { 

let t$0 = ArrayExpression [] // ok, same block scope as its use 

if (...) { 

t$2 = null; 

break bb0; 

} 

let t$1 = Jsx children=[t$0] // note this moved inside the labeled block 

} 

} 

```
2024-03-13 21:29:58 -07:00
Joe Savona
f7ca10a0c7 Repro for "no value for temporary"
I addressed some of the cases that lead to this invariant but there were still 
more. In this case, we have scopes like this: 

``` 

scope @1 declarations=[t$0] { 

let t$0 = ArrayExpression [] 

if (...) { 

return null; 

} 

} 

scope @2 deps=[t$0] declarations=[t$1] { 

let t$1 = Jsx children=[t$0] ... 

} 

``` 

Because scope 1 has an early return, PropagateEarlyReturns wraps its contents in 
a label and converts the returns to breaks: 

``` 

scope @1 declarations=[t$0] earlyReturn={t$2} { 

let t$2 

bb0: { 

let t$0 = ArrayExpression [] 

if (...) { 

t$2 = null; 

break bb0; 

} 

} 

} 

scope @2 deps=[t$0] declarations=[t$1] { 

let t$1 = Jsx children=[t$0] ... 

} 

``` 

But then MergeReactiveScopesThatInvalidateTogether smushes them together: 

``` 

scope @1 declarations=[t$1] earlyReturn={t$2} { 

let t$2 

bb0: { 

let t$0 = ArrayExpression [] // <--- Oops! We're inside a block now 

if (...) { 

t$2 = null; 

break bb0; 

} 

} 

let t$1 = Jsx children=[t$0] ... 

} 

``` 

Note that the `t$0` binding is now created inside the labeled block, so it's no 
longer accessible to the Jsx instruction which follows the labeled block. This 
isn't an issue with promoting temporaries or propagating outputs, but a simple 
issue of the labeled block (used for early return) introducing a new block 
scope. The solution (in the next PR) is to simply reorder the passes so that we 
transform for early returns after other optimizations. This means the jsx 
element will basically move inside the labeled block, solving the scoping issue: 

``` 

scope @1 declarations=[t$1] earlyReturn={t$2} { 

let t$2 

bb0: { 

let t$0 = ArrayExpression [] // ok, same block scope as its use 

if (...) { 

t$2 = null; 

break bb0; 

} 

let t$1 = Jsx children=[t$0] // note this moved inside the labeled block 

} 

} 

```
2024-03-13 21:29:54 -07:00
Jan Kassens
72d374e978 [linter] rename ReactForgetDiagnostics to ReactCompilerRule
[linter] rename ReactForgetDiagnostics to ReactCompilerRule
2024-03-14 11:53:32 -04:00
Joe Savona
ca8e0d4527 Support multiple declarations in for init
This was an oversight in codegen. The entire pipeline supports multiple values 
in a for initializer, but codegen was dropping all but the first initializer.
2024-03-13 15:45:50 -07:00
Joe Savona
fe29f46de8 Repro for multiple declarations in for initializer codegen issue 2024-03-13 15:31:51 -07:00
Joe Savona
2940440dfb Fix for mutable ranges ended early with interleaving
Fixes T180504437. In MergeOverlappingReactiveScopes we track the active scopes 
and mark them as "ended" when reaching the first instruction after their mutable 
range. However, in cases of interleaving that will be merged, we could 
previously mark a scope as complete when it's original range was completed, even 
though the range would get extended post-merge. The fix here detects 
interleaving earlier, and eagerly updates the mutable ranges of the merged 
scopes to ensure that neither is "ended" earlier than it should. 

The repro here fails without this change.
2024-03-13 13:52:29 -07:00
Joe Savona
79e3fc0acb Fix for method call not memoizing in same scope as outer call
Fixes T175282980. InferReactiveScopeVariables had logic to force assigning a 
scope to MethodCall property lookups with the idea of forcing the method call 
lookup to be in the same scope as the method call itself. But this doesn't work 
if we never assign a scope to the method call! That can happen if we're able to 
infer that the method call produces a primitive and doesn't need memoization. 

This PR changes things so that: 

* InferReactiveScopeVariables no longer assumes that MethodCall property values 
need a scope 

* We run a separate pass that ensures that _if_ a MethodCall has a scope, that 
it's property is in the scope, and that otherwise its property doesn't get a 
scope. This is similar to the existing passes that force a single scope for 
related instructions like ObjectMethod+ObjectExpression and fbt operands/calls.
2024-03-13 13:52:29 -07:00
Joe Savona
dc7ed065c0 Fix missing declaration invariant for "for" within try/catch
Fixes T180509722. What happened is that the logic in LeaveSSA to find 
declarations within for initializers wasn't working with try/catch because the 
initializer block gets broken up with a maybe-throw after every instruction that 
can throw. These maybe-throws can then get turned into gotos by 
PruneMaybeThrows, so LeaveSSA has to handle both cases. 

The new logic scans from the start of the init block until reaching the end, and 
creates declarations for all StoreLocals. Note that we don't yet support 
maybe-throw in value blocks — that's already a todo — so the change here simply 
allows us to compile farther until reaching that other todo. But i've 
double-checked the HIR and it looks correct for this case, so it should just 
work once we fix that todo. I've also added a comment to help us remember (and 
of course, we'd have to add a snap fixture too)
2024-03-13 13:52:28 -07:00
Jan Kassens
b666bd1637 [lint] do not report issues when a matching flow suppression is present
Based on implementation of a similar case in D54776832.
2024-03-13 11:40:35 -04:00
Lauren Tan
56d96ba203 [forget-runtime] Remove invariant dep
Reduces a dep needed to be installed by users of the polyfill
2024-03-11 16:31:46 -04:00
Mofei Zhang
e09c631aff [be][snap] Make testfilter file recognize glob patterns, remove @skip 2024-03-11 13:55:24 -04:00
Mofei Zhang
c84b817972 [be][snap] Warm up workers when in watch + filter mode 2024-03-11 13:55:22 -04:00
Mofei Zhang
2fc910d580 [optim] NewExpressions do not mutate the caller (class type)
``` 

function Foo() { 

const MyClass = getClass(); 

// following line is not expected to change MyClass 

return new MyClass(); 

} 

```
2024-03-11 13:03:45 -04:00
Mofei Zhang
df84152780 [hir] Attach fnType to HIRFunction
--- 

(This came out of running a sync and observing hundreds of bailouts due from 
this validation) 

Reading `fnType` from environment overgeneralizes, as inner functions are 
usually not the type of the outer react function. 

``` 

// Component type 

function Component() { 

// not Component type 

const helper = () => {...}; 

} 

``` 

Let's attach fnType to `HIRFunction` and use that for our inference + 
validations
2024-03-08 17:06:37 -05:00
Joe Savona
16852386a5 Support more cases of reassignment within value blocks
Fixes T175283039 — it's totally fine to have a StoreLocal as an instruction in a 
value block, so long as its a reassignment.
2024-03-08 13:59:22 -08:00
Joe Savona
5f3ed3f724 Fixture for reassignment within value block
Fixture from T175283039, a reassignment within an expression can sometimes 
generate a StoreLocal within a value block. Depending on the case this can end 
up as the last instruction of the block, which then hits an invariant.
2024-03-08 13:59:21 -08:00
Joe Savona
052f5fe973 Todo for "unreachable" possibly-hoisted functions
Adds a todo in HIRBuilder, before we prune unreachable code we check if there 
were any function expressions. Realistically that's only going to occur for 
hoisted functions, so this lets us target a todo rather than hit an invariant.
2024-03-08 13:59:20 -08:00
Joe Savona
7605e89a50 Repro for "unreachable" hoisted function
For T175282529. We already detect hoisted functions and have a specific todo for 
them, but in this case the function is in unreachable code that gets pruned 
during BuildHIR. The later check for hoisted functions doesn't find it.
2024-03-08 13:59:20 -08:00
Joe Savona
fe91bcefd2 Fix promotion of catch bindings w/in function expressions
One of our visitors wasn't visiting TryTerminal's handlerBinding, which meant 
that we missed renaming those identifiers in RenameVariables. I also updated the 
printers to print this binding.
2024-03-07 09:21:33 -08:00
Joe Savona
449aa70f99 Fix promotion of locals referenced outside of scopes 2024-03-06 16:19:28 -08:00
Joe Savona
854a810f05 Turn value block within try/catch into todo (from invariant)
Within a try/catch, every instruction is followed by a maybe-throw terminal. 
This currently breaks the logic in BuildReactiveFunction which tries to 
reassemble the value block, since it isn't expecting the maybe-throw. 
Conceptually the logic should just ignore it — we could even flatten away 
maybe-throw terminals before this pass — but for now since this pattern is rare 
we can just make it a todo.
2024-03-06 15:28:36 -08:00
Joe Savona
52875a72fe Invariant if codegen tries to emit a temporary as an identifier
For T181507827 — adds an invariant in codegen when emitting identifiers to 
ensure that we only create babel Identifier nodes for nodes that the compiler 
has explicitly promoted to valid, named identifiers. This means that we'll fail 
for unnamed temporaries (previously caught), as well as promoted temporaries 
that somehow didn't get renamed by RenameVariables (newly caught).
2024-03-06 21:50:03 -08:00
Joe Savona
ebbada309d MemoizeFbtOperandsInSameScope operates on HIR
Moves this pass to operate against HIRFunction instead of ReactiveFunction, no 
logic changes.
2024-03-06 14:02:18 -08:00
Joe Savona
e7fcc4e6a8 Rename promoteTemporary helpers
Renames the helpers for promoting temporaries to named identifiers, per feedback 
earlier in the stack.
2024-03-06 11:16:44 -08:00
Joe Savona
b7026ede39 Avoid generating conflicts w global names
Adds a visitor to collect all the globals that are referenced within the 
function, and then uses this list to avoid synthesizing variables with 
conflicting names. This is used in both RenameVariables (for promoted 
temporaries) and Codegen (for `$` and change variables only, so far, but this 
can be extended in follow-ups).
2024-03-06 11:07:11 -08:00
Joe Savona
8faed2af4c Avoid conflicting names for reactive scope codegen
This is a key part of avoiding generating conflicting names in our output. To 
start, RenameVariables now returns a Set of the unique identifier names that 
exist in the function. Codegen uses this to avoid generating duplicate names for 
change variables and for the `$` useMemoCache variable. Rather than always emit 
`$` or `c_N`, codegen checks that this name would not conflict and appends an 
incrementing suffix until it finds a unique name. 

Note that it's still possible for us to generate conflicts with global 
variables, both during RenameVariable and Codegen. The next step will be to 
avoid conflicts with globals.
2024-03-06 11:07:11 -08:00
Joe Savona
2ae0f36543 Promote and rename within nested functions
Another title for this PR could be "Yet another reason for HIR-everywhere" 

ReactiveFunctionVisitor doesn't traverse into HIRFunctions from 
FunctionExpression and ObjectMethod values. This means that 
PromoteUsedTemporaries and RenameVariables also weren't traversing into such 
functions, and those values weren't getting promoted and renamed correctly. 

This PR updates ReactiveFunctionVisitor with a method that can optionally be 
invoked to traverse an HIRFunction and call the appropriate visitor methods. 
PromoteUsedTemporaries and RenameVariables invoke this to ensure they visit all 
places, even in nested HIRFunctions.
2024-03-06 11:07:10 -08:00
Joe Savona
99b9da4b00 Move remaining promotion of temporaries out of codegen
I realized that codegen still had a fallback for generating identifier nodes for 
unnamed temporaries. This PR updates codegen to throw if it needs to generate an 
identifier for a temporary, and updates earlier passes to promote temporaries to 
named values in all the cases that were missed: 

* BuildHIR needs to promote temporaries for temporaries in destructuring 
bindings and catch clause bindings 

* PromoteUsedTemporaries has to promote temporaries for destructured function 
parameters or function params that are context variables.
2024-03-06 11:07:09 -08:00
Joe Savona
31e128a441 Use opaque type for Identifier.name for improved correctness
Uses an enum for Identifier.name to distinguish originally named identifiers vs 
promoted temporaries. An opaque type for the named identifier variant makes it 
hard to accidentally create that type.
2024-03-06 11:07:08 -08:00
Joe Savona
329bb555a0 [RFC] Stabilize naming of promoted temporaries
When the compiler promotes temporary values to named variables, we currently 
eagerly assign a name using the temporary's IdentifierId. This means that we're 
sort of stuck with this name later in compilation, and RenameVariables can't be 
100% sure whether a 't0' variable is a temporary or not. As a result, the names 
of these promoted temporaries is influenced by how many temporaries we happened 
to create during compilation (and what the next available identifier id was), 
making them fluctuate more as we iterate on the compiler. 

This is an RFC for showing how we can stabilize these names. The key elements: 

* Distinguish promoted temporaries from other named identifiers. Here we use a 
hack, naming them starting with '#t' or '#T', since '#' isn't a valid identifier 
starting point. This lets us keep all of our logic that looks for non-null 
identifiers names to distinguish named/unnamed, while also distinguishing real 
names from generated names (if this was Rust, we'd use an Enum and have a 
"isNamed()" method on it that was true for real/temporary names and false 
otherwise) 

* In RenameVariables, detect generated names and fall back to generating the 
next available `tN`-style name (or `TN` for JSX tags). 

* To reduce thrash overall, RenameVariables no longer keeps a global "next id" 
value that uses to distinguish all conflicting identifiers, instead we restart 
at 0 whenever we find a conflict, and keep bumping until we find a free name. 
Thus if both `foo` and `bar` had conflicts, we previously would end up with 
`foo$0` and `bar$1` as the deduped names, but now will end up with `foo$0` and 
`bar$0`. 

## RFC 

I'm open to feedback on the approach. Two main questions: 

* How to annotate promoted temporaries. The most type-safe option is to change 
`Identifier.name` to be a union of `{kind: 'named', value: string} | `{kind: 
'promoted', value: string} | `{kind: 'temporary'}` though TS then wouldn't allow 
`identifier.name.value` (even as nullable) since it doesn't exist on one of the 
variants. Maybe we could type the temporary one as `{kind: 'temporary', value?: 
null}` so the value has to be null but you can always access that property? 

* ?? Other concerns about the approach? We could keep the global 
auto-incrementing id rather than attempting to reset to 0 for each conflict.
2024-03-06 11:07:08 -08:00
Joe Savona
d1d6310f25 Conditional assignment of unmemoized value doesnt invalidate later scopes
The previous implementation used IdentifierId, but since this pass operates 
after LeaveSSA the identifier ids are no longer distinct for different SSA 
instances. Instead we use the Identifier instance, which preserves SSA 
information (even ever LeaveSSA) and allows distinguishing between variables 
whose value always changes vs variables that may be reassigned such that they 
don't always invalidate. 

In the future when we use HIR everywhere, this pass should use the HIR CFG to 
understand that phi nodes whose operands all will always invalidate can also be 
treated as always invalidating. 

## Test Plan 

Synced to www, 91 files have output changes 
(https://fburl.com/everpaste/3e3hjpjs). I spot checked these and confirmed that 
they are all from cases where there was already missing memoization of earlier 
values, where we now can prune later reactive scopes that depend on the 
un-memoized values.
2024-03-06 11:07:07 -08:00
Joe Savona
4af3ff1e75 Fixture for transitive invalidation of effect dep
This fixture shows that this optimization partially improves transitive checking 
for validateMemoizedEffectDependencies.
2024-03-06 09:28:03 -08:00
Joe Savona
f4455ef51a Prune scopes whose deps always invalidate
Implements the optimization described in the previous PR: if we know that a 
scope's dependency will _always_ invalidate (it is not memoized and it is 
guaranteed to be a new object if the instruction executes, such as an array or 
object literal), then we can prune that scope. The invalidation is transitive: 
we track always-invalidating types from within scopes, and if their scope gets 
invalidated we prune downstream scope that depend on them. 

## Test Plan 

Tested via #2639 - see https://fburl.com/everpaste/3e3hjpjs. 91 files change 
output due to reactive scopes which would always invalidate due to always 
invalidating dependencies.
2024-03-06 09:28:02 -08:00
Joe Savona
48e08c42be Fixtures where unmemoized value invalidates later scopes
These fixtures demonstrate how currently, even if the dependency of a scope 
doesn't get memoized (ie the scope gets pruned), we don't remove later scopes 
that depend on that value. Those later scopes will always invalidate, so we 
might as well remove them.
2024-03-06 09:28:01 -08:00
Michael Vitousek
22ea72d4e9 Allow global mutation within useEffect (#2646)
Summary: Currently Forget bails on mutations to globals within any callback function. However, callbacks passed to useEffect should not bail and are not subject to the rules of react in the same way.

We allow this by instead of immediately raising errors when we see illegal writes, storing the error as part of the function. When the function is called, or passed to a position that could call it during rendering, we bail as before; but if it's passed to `useEffect`, we don't raise the errors.
2024-03-05 11:54:29 -08:00
Jordan Brown
f5d99baf8e Add config option to ignore 'use no forget' directives
This will let us test without taking into account the existing 'use no forget' 
directives to better understand what validations we may need to build. 

There are two other files that look for this directive that do not seem to take 
compiler options: 

1. 
408617ec8a/react/scripts/babel/transform-forget.js (L25) 

2. 
408617ec8a/packages/babel-plugin-react-forget/scripts/jest/makeTransform.ts (L119) 

Do I need to do anything for those?
2024-03-04 19:00:48 -05:00
Sathya Gunasekaran
7f994241b0 Infer type of ref argument
Extend type inference to infer second argument of component as a ref type
2024-02-29 14:47:52 -08:00
Sathya Gunasekaran
ad8f19675c Infer type of React function
Infer if a function is a component or hook when we're deciding to compile a 
function and store that in the environment. 

This is used in passes like InferReferenceEffects rather than having to re-parse 
the name in each pass.
2024-02-29 14:47:51 -08:00
Sathya Gunasekaran
19e44a4a69 Allow ref argument to be mutated
Previously, Forget would throw if _any_ of the arguments to a component are 
modified. This isn't quite right as a ref argument can be modified. 

This PR assumes the second argument of a component to be a ref and allows it to 
be mutable. 

A future PR will add types to this argument so the validateRefAccessDuringRender 
can catch if ref is mutated in render. This PR contains a todo test for this.
2024-02-29 14:47:49 -08:00
Mofei Zhang
9d25ced018 [instrument] Optional globalGating means no global gating 2024-02-29 10:04:37 -08:00
Mofei Zhang
01454d1414 [other] Make global gating (e.g. DEV) check configurable 2024-02-29 09:44:28 -08:00
Joe Savona
6dbec55791 Support prefixed useState due to pre-bundling 2024-02-28 13:12:56 -08:00
Joe Savona
9d5a01359f Move creation of scopes for primitives into a separate pass
Rather than force scopes to be created for primitives within
InferReactiveScopeVariables, here we move the creation of scopes for these
instructions to a later pass. Later in the pipeline we have more context, such
as whether e.g. a primitive or propertyload is being accessed within a scope or
not, and whether it therefore needs its own scope or not.
2024-02-27 12:01:32 -08:00
Joe Savona
45216dc3d9 Track next scope id on Environment
Currently we allocate all reactive scopes during a single pass, 
InferReactiveScopeVariables, using a local incrementing number to assign 
ScopeIds. This means we can't easily create additional scopes later since we 
don't know the next available scope id. 

Here we add `Environment.nextScopeId` and use that to synthesize scope ids.
2024-02-27 11:39:22 -08:00
Sathya Gunasekaran
130b809ac2 Allow property loads from hook
Looking up certain properties on a hook is a common pattern for logging. 

It's non-ideal but it's not a bug to do this. 

This updates Forget to not error on this pattern.
2024-02-28 16:19:43 -08:00
Sathya Gunasekaran
8879a2dd49 Remove sprout from package.json 2024-02-28 15:22:13 -08:00
Mofei Zhang
a06ded902a [other] Change instrumentation to use an optional gating identifier; record
filepath 

Internal rollout currently has a good number of test failures. 
`enableEmitInstrumentForget` can help developers understand which functions / 
files they should look at: 

``` 

// input 

function Foo() { 

userCode(); 

// ... 

} 

// output 

function Foo() { 

if (__DEV__ && inE2eTestMode) { 

logRender("Foo", "/path/to/filename.js"); 

} 

const $ = useMemoCache(...); 

userCode(); 

} 

```
2024-02-27 22:06:36 -08:00
Joe Savona
5acfc13575 Repro for invariant on value block in try/catch
The code for value block handling assumes a small set of terminal kinds, but 
try/catch causes the entire body to get wrapped in MaybeThrow terminals. We need 
to skip over these and delegate to the inner content.
2024-02-26 16:21:56 -08:00
Joe Savona
84ed1f2a37 Update scope declaration invariant to support error grouping
This invariant interpolated values into the `reason` which prevent our internal 
tooling from grouping related errors. This PR updates to make the reason static 
and interpolate the description.
2024-02-26 15:08:42 -08:00
Joe Savona
d81da8e2f5 Fix JSXMemberExpression dependency calculation
Fixes T173101142 — we previously computed incorrect function expression 
dependencies for JSXMemberExpressions. This PR applies similar logic to 
JSXMemberExpression as we use for MemberExpression. 

## Test Plan 

Synced internally, only one file changes output. I manually investigated to 
confirm — the change is that a function expression's dependencies are more 
precise and correct. See https://fburl.com/everpaste/4dqewxqv
2024-02-26 15:04:44 -08:00
Mofei Zhang
915a0b8389 [be] Consolidate sprout -> snap
--- 

No changes to snap or sprout's functionality. 

Tweaks to consolidate sprout into snap while keeping its simple interface and 
most developer patterns. 

- to keep `filter` mode fast, we do not run sprout in filter mode 

- sprout is run in non-filter mode for both test and update 

~~Small qol improvement: `--watch` will start you in `filter` mode~~ 

### Cost of this change 

`performance.now()` is quite noisy due to background processes and ThreadPool 
logic (especially with asymmetric task distribution), so I used 
`process.cpuUsage` which reports time spent in user-space. This was much less 
noisy (1-4% standard dev / mean) 

Running all tests becomes slower by ~50%. Initial runs are slower because they 
load in Forget's `require` chains. 

- 23.9s previous initial run 

- 34.6s current initial run 

- 11.5s previous subsequent runs 

- 15.4s current subsequent runs 

Running filtered tests remains very fast (~100ms on the average case) 

--- 

Additional modes or commands could be added as needed (e.g. run tests in filter 
mode, with sprout output)
2024-02-27 06:54:30 -08:00
Joe Savona
5261281985 move reassignment-related range adjustments to
InferReactiveScopeVariables
2024-02-23 16:58:10 -08:00
Joe Savona
d3589367b0 Handle scopes with reassignment (by expanding scopes to avoid it) 2024-02-23 14:41:27 -08:00
Joe Savona
a815d481c0 Support scopes with multiple declarations
Expands support for reactive scopes that have multiple declarations. We
return an array and destructure after, preserving the reactivity.
2024-02-23 14:41:26 -08:00
Joe Savona
ca1e92ad91 Compile partial reassignments into a single derived scope 2024-02-23 14:41:25 -08:00
Joe Savona
d6406d8360 Validate hook calls in object methods
Adds some test cases for hook calls in object methods. Initially we didn't catch 
these because InferTypes doesn't actually visit ObjectMethod bodies. Once we fix 
that we correctly reject these examples.
2024-02-16 11:27:52 -08:00
Joe Savona
9c419dfd80 Disallow calling hooks in functions
> Don’t call Hooks inside loops, conditions, or nested functions 

Per https://react.dev/warnings/invalid-hook-call-warning#breaking-rules-of-hooks 
it is invalid to call hooks inside function expressions. We now validate this by 
default, i'll verify internally before landing. 

Note the validation is somewhat more conservative and we only disallow known 
hook calls here, this seems like a reasonable tradeoff but i'm open to 
suggestions. We could reuse the same known/potential hook mechanism here but it 
would take some more refactoring.
2024-02-16 11:00:56 -08:00
Joe Savona
a4234bcfbf infer mode only compiles component/hook decls
Forks the logic from Program.ts, letting us customize what
gets compiled.
2024-02-17 17:37:00 -08:00
Joe Savona
ca3d16c5ef Enable hook syntax support
Updates the compiler to understand Flow hook syntax. Like component syntax, in 
infer mode hooks are compiled by default unless opted out. 

Looking ahead, i can imagine splitting up our compilation modes as follows: 

* Annotations: opt-in explicitly 

* Declarations: annotations + component/hook declarations 

* Infer: annotations, component/hook declarations, + component/hook-like 
functions 

This also suggest an alternative annotation strategy: "use react" (or "use 
component" / "use hook") as a general way to tell the compiler that a function 
is intended for React. Then opting out of memoization could do "use 
react(nomemo)".
2024-02-17 17:31:47 -08:00
Joe Savona
1e3f9130f3 Rewrite reactive scopes to computed with lambda
Updates LowerReactiveScopes to rewrite to a ReactiveFunctionValue
(ReactiveFunction-based) instead of a FunctionExpression (HIR-based). This lets
us include terminals and even nested reactive scopes in the result.
2024-02-16 14:59:01 -08:00
Joe Savona
191064b55c Add ReactiveFunctionValue variant
Per the previous PR, we don't have a way to rewrite an arbitrary subset of a
ReactiveFunction into a function expression, since FunctionExpression's contents
is still in HIR.

While long-term our plan is to move to HIR everywhere, this PR adds a stopgap of
adding a ReactiveFunctionValue variant of ReactiveValue. As a reminder,
ReactiveValue is a union of (HIR) InstructionValue | SequenceExpression |
LogicalExpression | ConditionalExpression.

For now i did a first stab at the visitors and transforms with the idea that:

* By default, visitors/transforms _don't_ look into these function expressions,
since we didn't previously traverse into (HIR-based) FunctionExpression either

* But there is a visitor/transform method that you can override if you need to.
2024-02-16 14:59:00 -08:00
Joe Savona
03874a29b3 Repro for needing function body rewriting in ReactiveFunction phase
Adds an example demonstrating why we need the ability to rewrite parts of a
ReactiveFunction into a function expression. Here, the reactive scope needs to
contain an `if` terminal, but we can't put a ReactiveIfTerminal inside a
function expression, since that expects HIR.

There are two main paths forward:

* Use HIR everywhere. I wrote this up and we're all agreed, it's just a bunch of
work.

* Add an alternative FunctionExpression variant to ReactiveFunction

For now i'm going to take the second route.
2024-02-16 14:58:59 -08:00
Sathya Gunasekaran
770ca4ab3e [Babel] Add support for "use memo"
We want to start moving away from "Forget", so this PR adds support "use memo" 
and "use no memo" 

I've left "use forget" and "use no forget" directives unchanged for now, as we 
need to migrate existing users first and then come back and delete support for 
these directives.
2024-02-19 17:48:49 +00:00
Jan Kassens
530f2c293d Add test fixture showing invalid location
Add test fixture showing invalid location 

The error should be on the node mutating the output, not in the read location.
2024-02-15 19:00:21 -05:00
Jan Kassens
ac89712750 Add code frame to snap errors
Add code frame to snap errors 

This should make it easier (possible) to see if errors point at the right lines. 

No idea why I had to add 1 to the column, you'd think it's all babel-standard 
(whatever it is) and there wouldn't be off by one errors, but I'm not quite in 
the mood to debug babel issues more then necessary right now...
2024-02-15 18:42:21 -05:00
Lauren Tan
d3bca420c0 Opt out react-forget-runtime from being Forgotten
This caused a build error when Forget was used in an Expo app as the 
react-forget-runtime package was itself being compiled with Forget. This broke 
Metro as metro serializes modules to iifes, but the import syntax that was 
injected by the useMemoCachePolyfill flag was left behind 

In practice I don't think the runtime package needs to ever be compiled by 
Forget, so this PR opts out the whole file. This would also prevent builds from 
breaking if someone decided to use the "all" compilation mode. 

Test plan: Ran the expo app and verified that it now builds with no errors
2024-02-15 17:44:44 -05:00
Lauren Tan
5142affc26 Allow module level "use no forget"
Currently we only allow adding the directive to function bodies, but there may 
be cases where we want to always opt out an entire module from being compiled by 
Forget
2024-02-15 17:44:44 -05:00
Joe Savona
21c5631570 [be] Prune implicit breaks when flattening unused labels
A labeled block will generally end with an implicit break out of the label. 
However, if there are no _explicit_ breaks to the label, we'll end up with a 
ReactiveFunction along the lines of: 

``` 

bb1: { 

...instructions with no explicit `break bb1`... 

(implicit) break; 

} 

``` 

The `PruneUnusedLabels` pass removes such unused labels, inlining the content of 
label terminal into the surrounding block. However, we weren't pruning the 
`break`! This wasn't a problem in practice since codegen, and future passes, 
would just ignore this. But it's more correct to go and find these unnecessary 
implicit breaks and prune them, which this PR does. 

Again, this shouldn't have any impact other than producing cleaner 
ReactiveFunction data during debugging.
2024-02-14 15:36:40 -08:00
Joe Savona
ad5a272393 Skip functions using same rules as regular pipeline 2024-02-14 15:23:37 -08:00
Joe Savona
09d122a20e [be] Remove @enableMergeConsecutiveScopes flag, feature is stable
Continuing on my quest to clean up our feature flags, the logic for merging 
consecutive feature flags is stable. Let's remove 
`@enableMergeConsecutiveScopes` since this is enabled everywhere.
2024-02-14 15:13:00 -08:00
Jordan Brown
a927040b7a Add an optional validation to bail out on capitalized function calls
Some components stop being components over time and are used as regular 
functions instead, but they may have lingering hook calls. Those hook calls make 
it so the capitalized function calling them do not error (they appear to be a 
function to existing eslint rules), but they are nonetheless unsafe to memoize. 
This diff adds a conservative option to bail out on all capitalized function 
calls. 

There are a handful of known-non-component capitalized functions, like 
`Boolean`, `String`, and `Number`. This diff also adds the ability to supply 
capitalized function names that should not be considered in this analysis. 

I added three tests: 

1. Ensure an error occurs in the obvious case 

2. Ensure an error occurs when the value is aliased simply 

3. Ensure the allowlist works 

This is my first commit so please go hard on me. I was unsure about where this 
code should live, so please nitpick.
2024-02-14 13:23:32 -05:00
Mike Vitousek
8a3def923b Fixed sprout test failure 2024-02-09 16:48:13 -08:00
Mike Vitousek
11a5254b9c add output 2024-02-09 15:24:51 -08:00
Mike Vitousek
966932c525 fix again :( 2024-02-09 15:17:55 -08:00
Mike Vitousek
c10d711309 Further test fix 2024-02-09 15:09:26 -08:00
Mike Vitousek
8c7685ed40 Fix breakages and add fixture entrypoint 2024-02-09 15:00:05 -08:00
Mike Vitousek
48024b76bb Fix lints 2024-02-09 14:50:06 -08:00
Mike Vitousek
19476aa5f6 Option to bail on Flow react-rule suppressions 2024-02-09 14:21:07 -08:00
Joe Savona
24ed13e1f0 Update error message and comments 2024-02-13 16:45:18 -08:00
Joe Savona
fea7b5ac0d Move useMemoCache outside of hook guards
The hook guards are incompatible with using a forget-runtime. Specifically, 
forget-runtime needs to make a call to `useState()` or some other hook to attach 
data to the fiber, but all the builtin hooks are overridden to disallow calling 
them outside of explicit boundaries. We'd either have to wrap the useMemoCache 
call in a push/pop to allow it to call other hooks, or as in this PR, just move 
it outside the enforcement.
2024-02-13 16:45:18 -08:00
Joe Savona
6924786973 [be] Change validate functions to not return unnecessary Result
These validations needs to be able to transitively check for violations within 
function expressions, without immediately erroring. So the inner "-Impl" helpers 
return a Result. But the outer, exported validate functions don't need to return 
a Result, especially since TS has no Rust-style enforcement that return values 
are actually used. Unwrapping within the validation means the caller can't 
forget to do so and inadvertently silence the errors.
2024-02-13 16:45:17 -08:00
Joe Savona
19b4abed69 [be] Remove unused validateRefAccessDuringRenderFunctionExpressions flag
I had split this up from the main validation since function validation was less 
precise; now that previous PRs fix the false positives we can remove this extra 
flag.
2024-02-13 16:45:17 -08:00
Joe Savona
bde30f0284 [be] Remove ValidateFrozenLambdas
This pass doesn't really make sense in light of 
`@enableTransitivelyFreezeFunctionExpressions`. The original idea of 
ValidateFrozenLambdas was that trying to pass a "mutable" lambda to a frozen 
value was invalid. But since then we've realized that the better heuristic is 
that freezing a lambda is transitive.
2024-02-13 16:45:16 -08:00
Joe Savona
5f1b8fd57f Improve validateNoRefAccessInRender
Rewrites the validation to not rely on the mutable range of functions to 
determine whether they are called or not, since the range can be extended for 
other reasons (they happen to reference a mutable value that is mutated later, 
even though the function isn't called during render). 

Instead we use the same approach as validateNoSetStateInRender, explicitly 
tracking references to function expressions that access refs, and checking if 
those function expressions appear to be called. This can have false negatives, 
as with the setState validation, but catches lots of obviously incorrect code 
without false positives.
2024-02-13 16:45:15 -08:00
Joe Savona
6dae958eab [be] Extract computeUnconditionalBlocks() helper 2024-02-13 16:45:15 -08:00
Joe Savona
f7f05501e0 Support destructuring assignment of context variables
Fixes T178003134. Previously we did not check whether values reassigned during a 
destructuring assignment were context variables. This would either miscompile, 
or as of my fix earlier in #2579, would fail validation. Specifically, this 
happened on AssignmentEpression with an object/array pattern lvalue, where the 
pattern contained an identifier that is a context variable. 

This is now fixed: we track whether the outermost assignment is a normal 
assignment or destructuring, and force destructuring to a temporary whenever the 
identifier is a context variable. We apply the same logic to variable 
declarations that are destructuring to a context variable.
2024-02-13 16:45:14 -08:00
Mofei Zhang
a107ba81ed [BE] Remove navigator; edit sprout fixtures
--- 

I recall adding the navigator override because some React library file had done 
an unconditional access, but this doesn't seem to be the case anymore. 
Regardless, newer versions of nodejs comes with a global `navigator` [see 
thread](https://github.com/nodejs/node/issues/39540) that error on writes
2024-02-12 19:12:01 -05:00
Mofei Zhang
5dd55e881b [lower][patch] Recognize UpdateExpression as lval assignment
--- 

Oops, I broke this in #2552. We never handled UpdateExpressions to context 
variables previously, so this PR also adds a todo bailout
2024-02-09 16:18:37 -05:00
Joe Savona
d4cdaf2523 Fix assignment expression with context variables
Fixes the one case discovered in the previous PR; for AssignmentExpression we 
correctly lowered the store instruction to a local/context, but then always used 
a `LoadLocal` to read the result back. 

The load instruction appears like it might be dangling - i think what was 
happening is that DCE cleaned up the unused LoadLocal whereas it leaves the 
LoadContext alone. But this works for now, we can always clean up the extra 
instruction later since this case isn't too common.
2024-02-09 14:25:33 -08:00
Joe Savona
6da1912eed Validate that all variable references are consistently local/context
Validates that all references to a variable (pre-SSA) are consistently "local" 
references or "context" references. Ie, if a variable is declared as 
DeclareContext, any accesses must be eg LoadContext or StoreContext, not 
LoadLocal/StoreLocal. This will help with the issue from #2577 (assuming that we 
know a variable _is_ a context variable) but also provides a more precise 
bailout for an existing case with destructuring assignment to a context 
variable.
2024-02-09 14:09:55 -08:00
Sathya Gunasekaran
da8ca6e954 [types] Check propType when checking if two types are equal 2024-02-08 16:19:55 +00:00
Sathya Gunasekaran
3c431f9afe [types] Check the shapeId as part of the object type equals check 2024-02-08 16:19:55 +00:00
Joe Savona
f0cffef0a2 Fix hook pattern matching for custom hooks 2024-02-07 17:34:10 -08:00
Joe Savona
af6e837b60 Fix destructuring with mixed local/scope declarations
Fixes T176436488. The logic for rewriting Destructure instructions was correct, 
but the visitor implementation was accidentally dropping subsequent Destructure 
instructions within a block after encountering one that needed a rewrite. 
Switching to use the transform infra (added after this pass was written) fixes 
it.
2024-02-05 22:29:53 -08:00
Joe Savona
0277bc4d54 Separate snap flags for watch/update/filter
This is not that big a deal but a constant papercut, i often want to jump 
directly to watch mode with a filter applied. I know @poteto likes to (or at 
least used to) run watch with update enabled. Now instead of passing a mode, you 
can pass `--watch`, `--filter`, and `--update` independently.
2024-02-05 15:55:08 -08:00
Mofei Zhang
707d9643dd [logger] Log todo when encountering "use no forget"
--- 

This change simply logs on every function we encounter with a `use no forget` 
directive. A few nuances -- `compilationMode: "infer"` only compiles functions 
we infer to be 'react functions'. 

```js 

// `add` would not be compiled, as it has no jsx, no hook calls, 

// and is not named as a component or hook 

function add(a, b) { 

return a + b; 

} 

``` 

With this PR, we would report todos for functions that Forget wouldn't 
ordinarily try to compile. 

```js 

// Todo: Skipped due to "use no forget" directive. 

function add(a, b) { 

"use no forget"; 

return a + b; 

} 

``` 

This seems fine to me as (1) it's a bit nonsensical to have a `use no forget` 
direction on a non-react function, and (2) we're goalling on getting `use no 
forget`s down to 0.
2024-02-05 16:05:09 -05:00
Joe Savona
195b5e5a24 Fix sprout 2024-02-05 21:51:18 -08:00
Joe Savona
b79a5289fc Resolve type aliases
The goal of this PR is to move towards a uniform representation for all type
declarations, whether they are named type aliases, function declarations, or
inline annotations. We now assign every non-primitive type declaration (named or
anonymous) a unique DeclarationId. In the next PR, we'll also re-map inline
annotations back to this declaration id when encountering them.

This PR is extremely gross and my intent is to refactor a bunch of things in the
HIR to allow this to be less gross. Challenges:

* Babel name resolution requires using scopes but i really want to just work
with plain nodes, since NodePath and TypeScript do _not_ get along. So here, i
find all identifiers and store a mapping of identifier -> scope, so that i can
later look them up if necessary.

* HIR doesn't have a notion of a declaration id, and in general we don't want
to extend HIR. So i end up with a whole bunch of side table information and
indirection. For example, a function doesn't know it's own declaration id.
So we have to look it up. Function params don't track their Forest type, so
we have to look them up on the function declaration. Etc.
2024-02-05 21:51:13 -08:00
Joe Savona
292a247cc4 Add type to DeclareLocal 2024-02-05 21:51:11 -08:00
Joe Savona
76aaf32c55 HIR StoreLocal.type uses babel type 2024-02-05 21:51:09 -08:00
Joe Savona
9ec9331def Emit function param and return type annotations 2024-02-05 21:51:09 -08:00
Sathya Gunasekaran
29cb62ad6c Add type information for jsx/runtime 2024-02-02 10:56:44 -05:00
Lauren Tan
7bce302421 Make Other mutation validation message more generic
The current error message "This mutates a global or a variable after it 

was passed to React" no longer makes sense since we now have more 

specific error messages for different kinds of Effect.Mutate or 

Effect.Stores. This replaces the fallthrough "Other" case with a 

more generic message. It's not perfect, but it's a little more accurate 

than what is currently emitted 

The proper fix might be to treat functions as mutable objects and allow 

the mutation, or special case `Function.displayName`. For now though 

this PR just updates the message in the meantime so it's less 

confusing.
2024-02-01 11:02:30 -05:00
Lauren Tan
a0aa66ca8f Add test for function property mutation
There were no previous test paths that tested it, so I'm adding the example from 
https://github.com/facebookexternal/forget-feedback/issues/32 as a fixture
2024-02-01 11:02:30 -05:00
Joe Savona
d55420c430 Allow prefixed hooks for compiling bundled code
We're doing some internal benchmarking using a lightweight bundler that @pieterv 
wrote for experimentation purposes. It's designed to fully preserve Flow type 
annotations so we can experiment with type-driven compilation and test out what 
benefits we might get from "cross-module" compilation more easily (ie by just 
bundling together a few modules so we can see them all as one). 

However, the bundler renames local variables and imports, so that a reference to 
`useMemo()` might end up as `React$useMemo()` or similar. This PR adds a flag to 
tell the compiler that builtin hooks might be prefixed and resolve them 
appropriately.
2024-01-30 22:11:17 -05:00
Mofei Zhang
d0bb1fed61 [be] Explicit todo diagnostics for hoisting
--- 

Currently, we error on non-hoisted identifiers in EnterSSA with a somewhat 
cryptic message. This PR changes `BuildHIR` hoisting logic to find ALL hoistable 
bindings, then error when we try to lower hoisting for unsupported declaration 
types. 

Two benefits to this refactoring: 

- Dedups "unhandled identifier declaration" logic (previous to #2552 and this 
PR, we did this check in three places). 

- More explicit todo diagnostic messages when we cannot hoist a declaration
2024-01-31 10:59:25 -05:00
Mofei Zhang
69733c5ad8 [hoisting][patch] use Babel identifier apis in BuildHIR hoisting logic
--- 

Three functional changes: 

- Instead of visiting all identifier references, explicitly traverse only 
function decls/exprs. This avoids bugs like accidentally hoisting inline 
references 

```js 

// input 

const x = identity(y); 

const y = 2; 

// lowered HIR before this PR (simplified) 

[0] DeclareContext HoistedConst y$0 

[1] LoadContext y$0 

[2] StoreLocal Const x$5 = identity([1]) 

``` 

- Rely on `isReferencedIdentifier()` instead of manually checking member 
properties / assignments, which is error prone 

```js 

// added fixture hoisting-repro-variable-used-in-assignment 

const callbk = () => { 

// before this PR, we skip hoisting x because it's part of a declaration 

const copy = x; 

return copy; 

}; 

const x = 2; 

return callbk(); 

``` 

- Visit lvalues after rvalues. This allows for recursive self-references (e.g. 
factorial) 

From the Babel side, this change relies heavily on babel's scope binding 
resolution logic. My understanding is: 

- Babel guarantees node objects are uniqued (`node1 === node2` <--> node1 and 
node2 are the same node in the ast) 

- Each binding has exactly one `bindingIdentifier` (`binding.identifier`, 
`getBindingIdentifier`, etc) which is identifier node @ its declaration site 

```js 

// x is a binding identifier 

const x = 2; 

// foo is a binding identifier 

function foo() { 

} 

// param is a binding identifier 

(param) => {...} 

// this bar is a binding identifier 

let bar; 

// but not this bar 

bar = 2; 

```
2024-01-31 10:59:25 -05:00
Mofei Zhang
c3e9cba0bb [patch] PruneHoistedContexts should traverse lambdas 2024-01-29 17:45:44 -05:00
Mofei Zhang
74d8a18637 [optim] All nested properties in refs are ref values
Forget currently removes memoization of callbacks that have `mutate` effects on 
`ref` inner properties. @gsathya pointed out that our existing compiler behavior 
is to (1) NOT extend mutable ranges for functions that mutate `ref.current` and 
(2) extend mutable ranges for functions that mutate `ref.current.inner`. 

```js 

// input 

function Component() { 

const ref = useRef({ text: null }); 

const handleChange = useCallback((e) => { 

ref.current.text = e.target.value; 

}); 

return <input onChange={handleChange} />; 

} 

// output 

function Component() { 

const ref = useRef({ text: null }); 

// now unmemoized! 

const handleChange = (e) => { 

ref.current.text = e.target.value; 

};
2024-01-29 17:04:52 -05:00
Mofei Zhang
788182f709 [patch] Patch edge case: RenameVariables should visit lvalues
--- 

This is likely a rare edge case, but it does produce a parse error. 

RenameVariables visits all identifier references to ensure we don't end up 
producing conflicting variable declarations, using a stack of block scopes to 
check "in scope variables". 

This pass is currently built to be conservative -- we explicitly rename shadowed 
variables, and visit all rvalue references. The issue is for this IR: 

``` 

{ 

1.  decl t0; 

2.  scope 0 { 

3.    reassign t0 = ... 

4.    read(t0) 

5.  } 

6.  let t0 = ... 

7.  read(t0); 

} 

``` 

We currently visit t0 only on line 4 and 7 (and never rename t0). Instead we 
should visit lvalues (declaration sites) which occur earlier than rvalues 
(visiting lines 1 and 6 will show conflicting declarations)
2024-01-26 15:29:09 -05:00
Lauren Tan
4ec05660a2 [be] Update to node 20 actions
Node.js 16 which various github actions (v3) were using is no longer supported 
by github and was spewing a bunch of warnings
2024-01-30 15:46:19 -05:00
Joe Savona
bc145f6f1f Support customizable eslint suppressions
The compiler bails out of compiling code that contains suppressions of the 
official React ESLint rules. However, some apps may use additional rules that 
they want to trigger bailouts for, or use the official rules under a different 
name (we do this at Meta). This PR adds a compiler flag to specify a custom set 
of line rule names, suppression of which should trigger a bailout.
2024-01-29 16:58:49 -05:00
Lauren Tan
742a6f09bf [ez] Get rid of caniuse warning 2024-01-30 14:54:04 -05:00
Lauren Tan
6b49c4f2d5 [eslint-plugin] Cleanup package.json
Removes some unused packages and unused package.json fields
2024-01-25 10:29:32 -05:00
Joe Savona
c92ad38375 Fix block scoping issues from MergeConsecutiveBlocks
Fixes the issues from the previous PR. It's a simple fix — we don't merge 
consecutive blocks if the successor block is some terminal's fallthrough.
2024-01-23 11:41:00 -08:00
Joe Savona
b2f44c103b Fixtures demonstrating incorrect block scoping due to MergeConsecutiveBlocks
Fixtures from T173102122 and T173101739 demonstrating cases where 
MergeConsecutiveBlocks can move code out of its correct block scope, changing 
behavior or breaking the program, in cases where a control flow structure (such 
as switch) only has one non-returning control flow path. In these cases, the 
non-returning path gets merged with the fallthrough, effectively lifting that 
code out of the control flow structure and moving it into the outer scope. This 
can create dead code or just invalid code (with references to variables that are 
not in scope). 

Sprout fails on both of these fixtures: 

<img width="812" alt="Screenshot 2024-01-23 at 11 25 36 AM" 
src="https://github.com/facebook/react-forget/assets/6425824/d397ea22-3fa3-436e-b655-09a45781274b">
2024-01-23 11:40:56 -08:00
Joe Savona
f9f084087f Fixture for reactively-controlled context variables
Mofei considered this case, it works thanks to the handling for function 
expressions earlier in the stack.
2024-01-23 08:59:21 -08:00
Joe Savona
a023a2da72 Fixtures for control values that become reactive due to interleaving
See the previous PR, interleaved mutation can cause values that were not 
reactive to become reactive. I swear I had a case where this was observable, but 
I came up with it before reordering the PRs in this stack. I think my repro 
relied on an immutable reference to a mutable value, which is now handled in 
InferReactivePlaces. So here i'm just adding fixtures, and allowing this case 
since it's unobservable.
2024-01-23 08:59:20 -08:00
Joe Savona
4d84bee172 Propagate reactive scope dependencies transitively
During PruneNonReactiveDependencies, we sometimes need to promote a value from 
non-reactive to reactive if it ended up being grouped in the same reactive scope 
as some other reactive value. This generally happens due to interleaving 
mutations. 

In this case all downstream usage of the promoted value need to also be 
considered reactive. Fully propagating the reactivity requires re-running 
InferReactivePlaces, to account for things like control reactivity. We can't yet 
reuse that pass here though, because we haven't unified the pipeline on HIR yet. 

For now, we propagate the reactivity through local variables and downstream 
reactive scopes. See test fixtures for some examples that now correctly 
propagate reactivity and some that need the full reactivity inference to run 
correctly. The latter cases are handled in the next PR.
2024-01-22 15:35:56 -08:00
Joe Savona
57163f0a52 InferReactivePlaces account for immutable aliases of mutably aliased values
I found this by adding logic to reject inputs where reactivity gets newly 
propagated in PruneNonReactiveDependencies. It's possible to create a readonly 
alias to a mutable value such that we don't know the value is reactive yet when 
the alias is created. Thus we need to do a fixpoint iteration even if there are 
no loops in order to be able to revisit such aliases and reflow the reactivity 
forward. Example: 

```javascript 

const x = []; 

const y = x; 

const z = [y]; // y isn't reactive yet when we first visit this, so z is 
initially non-reactive 

y.push(props.value); // then we realize y is reactive. we need a fixpoint to 
propagate this back to z 

const a = [z]; // need an indirection to get past the partial propagation in 
PruneNonReactiveDependencies 

let b = 0; 

if (a[0][0]) { 

b = 1; 

} 

return [b]; 

``` 

Existing fixtures don't change because the basic reactivity propagation in 
PruneNonReactiveDependencies is enough to make common cases work. I confirmed 
that the new fixture does not work on previous PR in the stack.
2024-01-22 15:35:55 -08:00
Joe Savona
0894e35d94 More fixtures for reactivity and mutable aliasing (property load case) 2024-01-19 19:12:58 -08:00
Joe Savona
8be56418d3 InferReactivePlaces accounts for mutable aliasing
Fixes T175227223. When inferring reactivity, mutation of a value with a reactive 
input marks the mutable value as reactive. However, we also need to account for 
aliases: 

```javascript 

const x = []; 

const y = x; 

y.push(props.value); 

``` 

Previously we would have only considered `y` reactive here, but `x` also becomes 
reactive. 

The implementation extracts out a helper from InferReactiveScopeVariables that 
builds a `DisjointSet<Identifier>` of disjoint sets of mutably aliased values. 
InferReactivePlaces then treats all instances of each mutable alias group as 
equivalent for reactivity purposes.
2024-01-19 16:03:58 -08:00
Joe Savona
a272cf9b0c Reactive control fixtures use multipass evaluation
Updates all of the reactive control dependency fixtures to use multipass 
evaluation in sprout.
2024-01-19 11:04:18 -08:00
Joe Savona
c8323f3b42 Mutation within a reactively controlled block propagates reactivity
In InferReactivePlaces, we already account for reactively controlled values: 
where a value is never assigned a non-reactive value, but _which_ value is 
assigned is based on a reactive condition (the test conditions of an if, switch, 
loop, etc). 

This PR extends that reactively-controlled inference to mutation that is 
conditioned upon a reactive value. From the test case: 

```javascript 

let x = []; 

if (props.cond) { 

// This mutation has no reactive inputs. 

// *But* the mutation conditionally occurs based on props.cond which is reactive 

x.push(1); 

} 

let y = false; 

if (x[0]) { // therefore the value observed here is reactive 

y = true; 

} 

// so the value of y here is reactive via the reactive control dependency x[0] 

return [y]; 

```
2024-01-19 10:03:51 -08:00
Mofei Zhang
34c89458f2 [entrypoint] Allow ref params to component functions 2024-01-19 14:59:52 -05:00
Mofei Zhang
4aa60d32b9 [patch][dce] Patch dce to have separate mark and sweep phases
--- 

Previously, our logic was something like: 

```js 

fixed-point-loop { 

foreach instruction { 

mark referenced identifiers 

// assume that usages are always visited before declarations 

if (instruction is decl) { 

prune(instruction); 

} 

} 

foreach instruction { 

if not referenced { 

delete(instruction); 

} 

} 

``` 

This contained a bug, as not all usages of a variable are guaranteed to be 
visited before its declaration. 

```js 

// input 

let x = 0; 

while(x < 10) { 

x += 2; 

} 

return x; 

// hir 

entry: 

x$0 = 0 

goto loop-test 

loop-test: 

x$1 = phi(x$0, x$2) 

if ... goto loop-body else goto fallthrough 

loop-body: 

x$2 = x$1 ... 

goto loop-test 

fallthrough: 

return x$1 

``` 

In this example,`x$2` is defined by `loop-body` and used by `loop-test`. 
Similarly, `x$1` is defined by `loop-test` and used by `loop-body`. 

--- 

TODO: trying to come up with more test fixtures
2024-01-18 18:29:19 -05:00
Mofei Zhang
a0e90065c6 [patch][babel] check babel identifier before lowering to HoistedConst
Same babel identifier issue as #2510 but for HoistedConst 

Not sure how we should best test this -- one possibility is using constant prop. 
Currently, we have false positives for HoistedConst that prevent constant 
propagation. I don't want to over-rotate on babel apis tests in our fixtures 
(instead of semantically interesting ones) 

```js 

// input 

function Component() { 

{ x: 4 }; 

const x = 2; 

return x; 

} 

// output 

function Component() { 

const $ = useMemoCache(1); 

let x; 

if ($[0] === Symbol.for("react.memo_cache_sentinel")) { 

x = 2; 

$[0] = x; 

} else { 

x = $[0]; 

} 

return x; 

} 

```
2024-01-18 18:29:19 -05:00
Mofei Zhang
f88e7fe412 [repro] add fixture repro for destructuring bug 2024-01-18 18:29:18 -05:00
Mofei Zhang
241a615732 [babel][contextvar] Patch context identifier babel logic; only use referenced
identifiers 

--- 

A few fixes for finding context identifiers: 

Previously, we counted every babel identifier as a reference. This is 
problematic because babel counts every string symbol as an identifier. 

```js 

print(x);  // x is an identifier as expected 

obj.x      // x is.. also an identifier here 

{x: 2}     // x is also an identifier here 

``` 

This PR adds a check for `isReferencedIdentifier`. Note that only non-lval 
references pass this check 

```js 

print(x);  // isReferencedIdentifier(x) -> true 

obj.x      // isReferencedIdentifier(x) -> false 

{x: 2}     // isReferencedIdentifier(x) -> false 

x = 2      // isReferencedIdentifier(x) -> false 

``` 

Which brings us to change #2. 

Previously, we counted assignments as references due to the identifier visiting 
+ checking logic. The logic was roughly the following (from #1691) 

```js 

contextVars = intersection(reassigned, referencedByInnerFn); 

``` 

Now that assignments (lvals) and references (rvals) are tracked separately, the 
equivalent logic is this. Note that assignment to a context variable does not 
need to be modeled as a read (`console.log(x = 5)` always will evaluates and 
prints 5, regardless of the previous value of x). 

``` 

contextVars = union(reassignedByInnerFn, intersection(reassigned, 
referencedByInnerFn)) 

``` 

--- 

Note that variables that are never read do not need to be modeled as context 
variables, but this is unlikely to be a common pattern. 

```js 

function fn() { 

let x = 2; 

const inner = () => { 

x = 3; 

} 

} 

```
2024-01-18 18:29:18 -05:00
Mofei Zhang
8f18b8233f [patch][contextvars] Patch for variables reassigned after objectmethod
definitions
2024-01-18 18:29:18 -05:00
Mofei Zhang
f0ef0b7d0b Edit .git-blame-ignore-revs 2024-01-18 18:29:17 -05:00
Jan Kassens
da7c466f07 Rename eslint-plugin-react-forget to eslint-plugin-react-compiler
Rename eslint-plugin-react-forget to eslint-plugin-react-compiler
2024-01-16 17:36:37 -05:00
Jan Kassens
9b6605d313 Remove [ReactForget] prefix from eslint messages
Remove [ReactForget] prefix from eslint messages 

No other lint warnings have a prefix, removing this is cleaner.
2024-01-16 13:47:20 -05:00
Jan Kassens
fb0cf4f833 Make collapsed playground tabs more compact
Make collapsed playground tabs more compact 

We have a lot of steps creating a lot of tabs by now. This makes them visually a 
lot more compact without a full redesign. 

Makes it a bit harder to read and less modern looking, but I think usability was 
a bit bad with the wide tabs. 

**Before:** 


![image](https://github.com/facebook/react-forget/assets/11849/a87c8f7e-d60c-43c1-aaea-1a1f77e082d0) 

**After:** 


![image](https://github.com/facebook/react-forget/assets/11849/227ebf1e-055f-439a-bfc9-e69bae091f6a)
2024-01-16 13:10:32 -05:00
Sathya Gunasekaran
38d3423970 Treat function expression deps as conditional 2024-01-15 12:44:22 +00:00
Sathya Gunasekaran
121e72a342 Add flag for treating function deps as conditional 2024-01-15 12:44:22 +00:00
Sathya Gunasekaran
6d133111a9 Add test for function deps not treated as conditional
In the test, unconditionally reading props.bar.length will throw when props.bar 
is null.
2024-01-15 12:44:22 +00:00
Sathya Gunasekaran
25ec9fcea4 Add .git-blame-ignore-revs
Ignore commits that just change formatting and file structures
2024-01-15 12:44:22 +00:00
Joe Savona
90348cc873 [housekeeping] Remove disabled test262 setup
I sincerely appreciate the effort to get test262 up and running. This was my 
idea, it seemed like a really good way to test our correctness on edge cases of 
JS. Unfortunately test262 relies heavily on a few specific features that we 
don't support, like classes and `var`, which has meant that we never actually 
use this as a test suite. 

In the meantime we've created a pretty extensive test suite and have tools like 
Sprout to test actual memoization behavior at runtime, which is the right place 
to invest our energy. Let's remove?
2024-01-12 12:26:30 -08:00
Joe Savona
bcbbbec1f5 [housekeeping] Remove unused test fixtures
We don't use these fixtures, let's just clean them up.
2024-01-12 12:22:23 -08:00
Joe Savona
33118be835 [housekeeping] Remove fixtures/
Do we still use these? I'm happy to close this PR if we still want this but it 
feels like these may have served their purpose and no longer be necessary.
2024-01-12 12:22:19 -08:00
Joe Savona
653373141a Extra fixture for validating preserved memoization of non-escaping callbacks 2024-01-12 14:32:35 -08:00
Joe Savona
0c866672b0 Fix false positive on preserving memo of non-escaping values
Fixes the false positive in the previous PR. When we prune a scope because it's 
values are non-escaping, we now also remove any `Memoize` instructions for that 
scope. The intuition being that we're actively removing unnecessary memoization, 
so we don't need to check that the memoization occurred anymore.
2024-01-11 17:13:41 -08:00
Joe Savona
8e4d2fb69d Repro for false positive in validatePreserveMemoization on non-escaping value
This demonstrates a false positive in validatePreserveExistingManualMemoization. 
We prune memoization of non-escaping values, but the validation pass just sees 
that the value "should" have a scope and that scope doesn't exist, and thinks we 
failed to preserve memoization.
2024-01-11 16:52:57 -08:00
Joe Savona
c3a947643f Update hoisting error message to allow error aggregation
Interpolating values into the `reason` field of an error breaks our error 
aggregation. This PR moves the offending function name into the `description` 
field which isn't used for aggregation.
2024-01-11 15:54:55 -08:00
Jan Kassens
f6f042d747 Switch test262 submodule to https url
I had trouble checking out the repo using Sapling because the submodule couldn't 
clone properly. I got 

> Error: Permission denied (publickey) 

In my branch I tested that the https URL format seems to work okay with Sapling.
2024-01-12 15:07:24 -05:00
Jan Kassens
8a634bc1c0 Add frozen reason for props and hook arguments
Add frozen reason for props and hook arguments 

Improves the error message when mutating props or hook arguments. 

Previously, this would print a generic error about mutating global variables.
2024-01-12 14:51:59 -05:00
Joe Savona
c80f0f022c useContext returns frozen values
This was an oversight in the original definition of useContext (oops my bad). 
Context values are owned by React and should not be modified. I found this 
because some cases of existing useMemo were not preserved (tested via the 
validatePreserveExistingManualMemo flag) due to function calls referencing 
context being assumed to mutate. 

This change will allow more memoization, it's also just more correct for the 
rules of React. Note the new ValueReason variant so that we can provide a 
precise error message about mutating context values.
2024-01-10 16:12:17 -08:00
Joe Savona
7ca3b004ae Early branch with new type inference foundation
It's starting to get complex just with a couple of extra
passes — we either need to substantially extend the HIR or (as i've done so far)
pass information from early passes to later ones. This PR changes things so that
very early in the babel plugin we fork into a separate mode. Forest has
its own `compileProgram()` equivalent, its own pipeline, its own codegen, etc.
2024-01-03 10:47:47 -08:00
Joe Savona
bd37fbe06a [wip] Fix phi inference, expose InferMutableRange issue
> Update: this is now passing all tests. The approach is likely wrong, and even 
if it's fine it needs some cleanup. Putting up for review as folks (esp 
@gsathya) have time. 

## Background 

InferTypes was intended to infer types for phi identifiers, but by accident we 
ended up storing the inferred type on `phi.type` instead of `phi.id.type`, which 
is the type that usages of the phi will reference. Because of this, we weren't 
actually inferring types for several cases, for example if both if/else branches 
assign `x` to an array literal, we'd ideally like the corresponding phi id to be 
typed as a BuiltInArray: 

```javascript 

let x; 

let y = { ... }; 

if (cond) { 

x = []; 

} else { 

x = []; 

} 

// x should be BuiltnArray here. We inferred that on Phi.type but the x here 
wouldn't get that type previously 

x.push(y); 

``` 

## Circular Types 

I started by removing the `Phi.type` property and updating inference to store 
the result of phi unification on `phi.id.type` — but this revealed other issues. 

First was this can create circular types when there are loops. The solution is 
to basically allow circular types _for phis only_, and when we detect them we 
remove the cycle. Basically whenever we have a situation where we have some type 
variable X, and a type Y that is a (nested) phi type one of whose transitive 
operands contains X, we remove X from the transitive type and attempt to 
collapse the phi type upwards if all of its remaining operands are the same: 

``` 

X=Type(1) 

Y=Phi [ 

Type(2), 

Type(3) = Phi [ 

Type(1), // <-- cycle but we can prune this 

Type(2), 

Type(2), 

] 

] 

=> 

X=Type(1) 

Y=Phi [ 

Type(2), 

Type(3) = Phi [ // all remaining operands are the same, we can prune this 

Type(2), 

Type(2), 

] 

] 

=> 

X=Type(1) 

Y=Phi [ // all remaining operands are the same, we can prune this 

Type(2), 

Type(2), 

] 

=> 

X=Type(1) 

Y=Type(2) 

``` 

We have to do this not just doing unify(), but also in `get()` since there are 
cases where we don't know yet which type variables we can remove from a phi. 
Without also doing the pruning in get, we get an infinite loop. 

## Reactive Scope Alignment 

The above fixed the circular types, but exposed some new cases that can occur in 
terms of mutable ranges and ast structures: it wasn't possible before to have a 
Store on a phi node in practice, since that relied on type information which we 
didn't have for phis. 

The new validation that all instructions for a scope are part of that scope 
caught a couple issues, which were basically like this: 

``` 

[1] Sequence 

... 

[9] StoreLocal x@0[9:28] 

[10] ... 

``` 

Note that scope 0 starts at instruction 9, but that instruction is not at the 
block scope level. The first instruction at the block scope level that is within 
the range of scope 0 is instruction 10, which is after the scope should have 
started! So I also had to update AlignScopesToBlockScopes to handle the case of 
logical, conditional, and sequence expressions: we sometime need to adjust a 
scope start earlier in case they contain instructions that should start a scope.
2024-01-02 15:31:57 -08:00
Joe Savona
bf859705b5 Enable early return support by default
This PR enables the new feature flag by default, but keeps the flag around so 
that we can quickly turn it off if there are issues.
2023-12-20 13:52:43 -08:00
Joe Savona
fcc2182641 Handle scopes with only early return and no decls/deps/reassigns
Fixes the case from the previous PR by using a different sentinel for 
uninitialized cache values and early returns. I confirmed with console.log that 
the reactive scope for `x` only evaluates on the first execution, after which we 
figure out that we don't need to execute it again.
2023-12-20 13:52:42 -08:00
Joe Savona
a753a326ad Fixture for only early return without decls/deps/reassigns 2023-12-20 13:52:41 -08:00
Joe Savona
ad57e661f4 Sprout support for rendering multiple times w different props
RFC. This is a quick sketch of adding support to Sprout to render the same 
component instance multiple times with different props. This doesn't test 
memoization (though it forms a basis for testing it, more below), but does allow 
us to test that the code properly reacts to inputs and doesn't get "stuck" 
always returning the same output even when inputs change. 

Possible extensions: 

- Support calling non-component functions multiple times 

- Test memoization by having the `toJSON()` helper track objects it has 
encountered before, assign each object a unique id, and then emit subsequent 
references to the same value as the id instead of the printed form of the 
object. 

For example if we call a memoized function with the same input twice in a row, 
today we might get output like: 

``` 

[{a: 1}], 

[{a: 1}], 

``` 

Which doesn't tell us if the object is equal. Instead we could emit output like: 

``` 

[{a: 1}] #0, 

#0, 

``` 

Which allows verifying that memoization actually happened. Or we could automate 
this and just assert that anything structurally equal has to be referentially 
equal — though there are cases with conditionals that break this.
2023-12-20 13:52:41 -08:00
Joe Savona
a86d279c46 Initial (flagged) support for reactive scopes with early return
Adds support for early returns within reactive scopes, behind a new feature 
flag. The flag is off by default, where this case continues to throw a Todo 
bailout. 

Since implementing a sketch of the codegen in the previous PR I realized that 
it's easy enough to implement the more optimal output, so i've updated that 
here. Rather than both the if and else branch of the reactive scope having an 
"if the return value was not a sentinel return it" check, we instead make the 
return temporary a proper declaration of the reactive scope. Then, since it's 
actually an output it's available in the outer block scope, and we could do a 
single if-return after the reactive scope. 

Edit: see comment below for thoughts on test cases.
2023-12-20 13:52:40 -08:00
Joe Savona
3175056935 Codegen for early returns in reactive scopes
Implements codegen for reactive scopes with early returns, though we don't ever 
construct such a case yet. See comments in the code. There is a slightly more 
optimal output that would require a larger refactor (also described in code 
comments), for now i'm starting with the simpler approach since this is 
relatively rare so we don't need to optimize code size / runtime as much.
2023-12-20 13:52:39 -08:00
Joe Savona
ed9d6a2ca1 Scaffolding for early return from reactive scopes
Adds a new `earlyReturnValue` property on ReactiveScope which will be set if the 
scope had one or more early returns, with information about the temporary 
identifier that the early return value will be assigned to, as well as the label 
to be used for breaking (to simulate the early-return). The next PR shows the 
intended codegen.
2023-12-20 13:52:38 -08:00
Joe Savona
4c68da2e60 Todo for early return within reactive scopes
Adds a new compiler pass that will eventually actually handle early returns 
within reactive scopes. For now it just detects them and throws a Todo error.
2023-12-20 13:52:37 -08:00
Joe Savona
f504eaa16e Add back transitive freeze functions option
Adds back a mode to transitively freeze function expressions, independently from 
the mode to preserve existing manual memoization. This lets us experiment with a 
few variants: 

* Preserve existing memoization 

* Validate existing memoization with: 

* `enableAssumeHooksFollowRulesOfReact` && 
`enableTransitivelyFreezeFunctionExpressions` 

* `enableAssumeHooksFollowRulesOfReact` only 

* neither of those flags 

Note that `enableTransitivelyFreezeFunctionExpressions` alone probably doesn't 
make sense, it's more aggressive than 

`enableAssumeHooksFollowRulesOfReact` so we might as well try them together.
2023-12-18 15:33:02 -08:00
Joe Savona
c77acb3ac6 Separate mode to validate preserving manual memoization
Adds a new mode which validates that existing manual memoization is preserved 
_without_ using information from the manual memoization to affect compilation. 
This gives us a way to try out the more aggressive version of Forget — ignoring 
manual memoization — first and see how much code bails out and what patterns 
cause this. 

We can then proceed to enable the mode to actually _preserve_ existing memo 
guarantees only where necessary.
2023-12-15 17:12:06 -08:00
Joe Savona
3e79c38604 More useCallback with ref fixtures 2023-12-15 16:59:19 -08:00
Joe Savona
af1aa8d0d3 Validation that useMemo/useCallback is preserved in the output
Extends `@enablePreserveExistingMemoization` to validate that all of the 
original values were actually memoized. This works nearly identically to how we 
validate effect deps are memoized. We look for Memoize instructions whose values 
need memoization but whose range extends past the memoize instruction, or where 
the value isn't memoized at all.
2023-12-15 16:22:17 -08:00
Joe Savona
0b32173d09 More fixtures for useCallback with refs
Extra fixture confirming that some usage of refs can break memoization, both in 
normal mode and in preserve-existing-memo mode.
2023-12-15 16:22:13 -08:00
Sathya Gunasekaran
721b6a4f91 [patch] Compile hooks with any number of args in infer mode 2023-12-18 13:35:56 +00:00
Joe Savona
c6200d1a2b Fix comments, extend fixtures 2023-12-15 15:19:41 -08:00
Joe Savona
6747d4e33c PreserveMemo for useCallback transitively freezes function exprs
Merges `@enableTransitivelyFreezeFunctionExpressions` into the new 
`@enablePreserveExistingMemoizationGuarantees` mode, since they are both 
motivated by the same use case of preserving effect behavior by preserving 
existing memoization behavior. 

The idea is that `useCallback` has an implicit assumption: that the variables 
captured by the callback aren't subsequently modified. Previous PRs treated the 
values directly captured by the callback as frozen. But if those variables were 
themselves another function expression, and that expression captured a mutable 
value, then we wouldn't consider the freeze to be transitive: 

```javascript 

const object = makeObject(); 

useHook(); // oops, hook call inside `object`'s mutable range, can't memoize 
object, log, or onClick! 

const log = () => { console.log(object) }; 

const onClick = useCallback(() => { log() }); 

maybeMutate(object); 

``` 

However, the assumption of such code is that it _doesn't_ modify such 
transitively captured values. So here we merge 
`@enableTransitivelyFreezeFunctionExpressions` mode into the 
memoization-preserving mode. Now, the memoize instructions emitted for 
useCallback (and useMemo) will transitively freeze captured function 
expressions, allowing us to memoize. 

The flip side of this is that some code may be violating these rules. We'll rely 
on runtime validation to detect such cases.
2023-12-15 15:19:40 -08:00
Joe Savona
08e92a3a8d More test cases for useCallback
Adds test cases per the previous PR for useCallback: 

* callback that references another callback, which in turn references a 
possibly-mutated value 

* callback that references a ref
2023-12-15 15:19:39 -08:00
Joe Savona
5bfd70ac6f Preserve memoization guarantees for useCallback
Improves `@enablePreserveExistingMemoizationGuarantees` for the useCallback 
case. Similar to useMemo, we add an explicit `Memoize` instruction for the 
callback function itself _and_ for its dependencies. This means we'll assume the 
callback doesn't mutate any captured variables. 

TODO: check this with cases involving refs (should be allowed, but also not 
accidentally freeze the ref) and reassignment of locals (should be disallowed, 
though that might just be a validation we're missing today)
2023-12-15 13:47:24 -08:00
Joe Savona
86e2edfa87 Prune memoize instructions in codegen
The previous PR introduced `memoize` instructions whose lvalues aren't used, but 
which can't be pruned by DCE due to pipeline ordering. Here we change to make 
memoize an instruction intended for its side effects only, and prune during 
codegen.
2023-12-15 13:47:23 -08:00
Joe Savona
723b616c67 enablePreserveMemo treats memo deps as frozen
See discussion on #2448 for full context. In the new 
`@enablePreserveExistingMemoizationGuarantees` mode, the goal is to preserve the 
existing referential equality guarantees from the original code. #2448 lays the 
groundwork by explicitly marking the _output_ of each useMemo block as memoized, 
hinting to the compiler that the value cannot subsequently change. This ensures 
the mutable range doesn't extend _later_, possibly overlapping a hook call and 
causing memoization to gett pruned. 

This PR fixes the other direction. There are cases where free variables 
referenced in the useMemo block could have been inferred as mutated, which could 
then extend the _start_ of the range earlier past a hook: 

```javascript 

const foo = createObject(); 

useBar(); 

const baz = useMemo(() => { 

const baz = createObject(); 

maybeMutate(foo, baz); 

return baz; 

}, [foo]); 

``` 

Here the compiler would infer that both `baz` and `foo` are mutable at the 
`maybeMutate()` call, grouping them in the same scope. But that scope would span 
the `useBar()` call, and be pruned, meaning that `baz` went unmemoized. 

However, useMemo blocks shouldn't be mutating free variables. Only variables 
newly created within the useMemo block should be mutable. So this PR extends the 
feature to treat all free variables referenced in a useMemo block as frozen as 
of the block itself.
2023-12-15 13:47:22 -08:00
Joe Savona
ec27708024 [be] Remove unnecessary check for SetState type in analyzefunctions
This was used as part of the previous ValidateNoSetStateInRender, but i rewrote 
that to not rely on the mutable range of function expressions.
2023-12-15 13:47:21 -08:00
Joe Savona
2abd439b43 Option to preserve existing memoization guarantees
Adds an option to preserve existing memoization guarantees for values produced 
with useMemo and useCallback. We still discard the calls to these hooks, but we 
preserve the information that the value is frozen at that point in the program. 
Because these values are produced solely within the useMemo/useCallback 
callback, their mutation cannot have any interspersed hook calls. This means 
that the values mutable range will never span a hook and end at the point of the 
useMemo, ensuring that they are memoized at the same point. 

The main things that can change (relative to the orignal code) are: 

* Forget will infer a precise set of dependencies, ignoring the user-provided 
values. In practice this should only occur if the original code had a lint 
violation, which Forget would bail out on. So in practice this shouldn't happen 
unless the code doesn't use the React linter. 

* Forget may start the memoization block earlier than the developer did if other 
values are mutated along with the value being produced. This can cause 
memoization to fail, but only in situations where it would have failed 
previously: 

```javascript 

const a = []; 

useFoo(); 

const b = useMemo(() => { 

const c = a; 

c.push(1); 

return c; 

}, [a]); 

``` 

In this example (sans Forget) the useMemo will invalidate on every render 
because `a` will always be a new array and its listed as a dependency of the 
useMemo. Forget would correctly determine that the memoization would have to 
work as follows: 

```javascript 

let c; 

if (...) { 

const a = [] 

useFoo(); // OOPS we made a hook call conditional 

const t0 = a; 

t0.push(1); 

c = t0; 

... 

} else { 

c = $[...] 

} 

``` 

Because this is invalid, Forget would (later in the pipeline) strip out this 
memoization block and (as with the original) leave `c` un-memoized. 

In this same example, removing the hook would cause Forget to be able to memoize 
a value that wasn't memoized before: 

```javascript 

const a = []; 

const b = useMemo(() => { 

const c = a; 

c.push(1); 

return c; 

}, [a]); 

``` 

This invalidates every render without Forget, but would memoize correctly with 
Forget (it would expand the memoization block to include the declaration of 
`a`).
2023-12-15 13:47:20 -08:00
Joe Savona
fc36043019 Fixture for pruning unmemoized nonreactive deps
Adds a fixture for our existing behavior that reactive scope dependencies 
exclude values which are non-reactive. The idea is that regardless of whether 
the value may actually get recreated over time or not, a "nonreactive" value 
cannot semantically change and therefore we can ignore changes in its pointer 
address.
2023-12-14 12:05:25 -08:00
Joe Savona
873a286029 Convert value block terminal invariant to todo 2023-12-12 16:52:24 -08:00
Joe Savona
a6e65e83e8 Prefer reporting conditional hook violations over "hook as value" violations
After running the latest hook validation internally, I found some cases where 
there was a violation but the error message was not ideal. For example on this 
code: 

```javascript 

usePossiblyNullHook?.(); 

``` 

We reported a "hooks can't be used as normal values" violation, when we'd 
ideally report a "hooks can't be called conditionally" violation. The solution 
in this PR is to track errors by source location, and upgrade the former 
violation to the latter, more serious violation. See fixtures for examples.
2023-12-12 16:42:51 -08:00
Jan Kassens
afbaa8d3ca Add a reason to ValueKind for better error messages (#2447) 2023-12-15 10:20:14 -05:00
Mofei Zhang
06376b906d [patch] Generate hook guards as function expressions
--- 

Arrow functions are not compatible with es3, which we sometimes target. This 
helps us make babel transform logic less complex
2023-12-14 19:42:24 -05:00
Sathya Gunasekaran
651d3448ac [vscode] update settings for v1.85
See https://code.visualstudio.com/updates/v1_85#_code-actions-on-save-and-auto
2023-12-14 15:47:38 +00:00
Sathya Gunasekaran
a3c48def1c Add Babel plugin to annotate react components
This can be used to figure out all the components in the bundle by grepping the 
bundle for the typeof check.
2023-12-14 15:52:17 +00:00
Sathya Gunasekaran
201b46d947 [eslint] Make the plugin configurable
As part of this PR, we remove the custom defined logger and use the logger from 
the plugin options.
2023-12-14 15:37:41 +00:00
Mofei Zhang
12de013aa4 [patch] ObjectMethods should have the same scope as their parent
ObjectExpressions 

--- 

Currently, we're removing all reactive scopes containing object methods. This 
could produce incorrect output as object method instructions may still be 
included in other reactive scopes (and will lose their dependencies).
2023-12-12 17:44:01 -05:00
Mofei Zhang
0966480d16 [be] More tests for object methods 2023-12-12 17:44:01 -05:00
Mofei Zhang
4c2b89288f [be][fixtures] Pass message directly instead of overwriting Error object
--- 

Started getting crashes in watch mode from trying to overwrite `error.message` 
(invalid setter or frozen object). This should fix
2023-12-11 22:59:33 -05:00
Mofei Zhang
bfa0aa2813 [be][test] use mutateAndReturn in test to render more values 2023-12-11 22:59:33 -05:00
Joe Savona
a1c7a26fc6 Put type-annotation-based inference behind feature flag 2023-12-11 11:34:29 -08:00
Joe Savona
12fbfc4fee Use variable type annotations to drive inference
Builds on the utilities added previously to infer types from type annotations on 
variable declarations. This is a limited form, where currently we only infer for 
local identifiers (not function parameters) and only infer a type for the 
variable initializer and not subsequent reassignments.
2023-12-11 11:34:29 -08:00
Joe Savona
8b3898c164 Use type assertions to drive inference
This PR uses the information from type cast expressions (`as`  or `(variable: 
type)`)  to inform type inference. BuildHIR converts the type annotation to our 
internal type format where possible, falling back to the generic `makeType()`. 
This is then used in InferTypes to help set the value's type.
2023-12-11 11:34:28 -08:00
Joe Savona
d163c0b7c3 Support TS type annotations in TypeCastExpression instr
Adds support for TSAsExpression in BuildHIR, which lowers to a 
TypeCastExpression, and to Codegen to reproduce the `as` expression.
2023-12-11 11:34:28 -08:00
Joe Savona
59b32f0813 🌲 Property/Computed load from props is a signal
Extends the previous analysis to work for PropertyLoad and ComputedLoad, so that 
if the object is a prop we track the resulting value as a signal. We also 
disallow PropertyLoad/ComputedLoad outside of a reactive scope where the object 
is a signal (since that would drop reactivity).
2023-12-11 11:34:27 -08:00
Joe Savona
3f4a9bd330 ReactiveFunctionTransform allows replacing values w transformValue
Previously the only way to replace a value was to override transformInstruction 
and transformTerminal, and to be careful to find nested values. This PR adds 
`ReactiveFunctionTransform#transformValue()` which allows returning an optional 
new value, which if present will replace the value in whatever context it 
appeared. ReactiveFunctionTransform now reimplements all the methods necessary 
to replace any value anywhere in the AST. See the next PR for an example usage.
2023-12-11 11:34:24 -08:00
Joe Savona
fa8f47eb41 InferReactivePlaces understands setState type
I realized this while working on Forest. When computing the dependencies of a 
reactive scope we can omit setState functions in the general case (exception 
described below). Currently that's implemented in PruneNonReactiveDependencies. 
However, this causes us to miss some optimizations — a value isn't reactive if 
its only dependency is a setState, and that may allow further downstreams values 
to become non-reactive. We lose out on that by only filtering out setStates in 
PruneNonReactiveDependencies — this logic really belongs in InferReactivePlaces. 

So this PR moves the check for setState types to that pass. The updated fixtures 
show that this already uncovers some wins. The _new_ fixtures covers the 
exception. It's possible for a value to be typed as being a setState function, 
but to still be reactive: if its a local that is conditionally assigned 
different setState function values. Currently this test happens to work because 
our phi type inference is incomplete (see #2296). I'm adding the test now though 
to prevent regressions when we fix phi type inference.
2023-12-11 11:34:24 -08:00
Joe Savona
9be2efdc57 🌲 Create scopes for primitive operations
In normal React certain operations don't allocate new objects (property loads, 
binary expressions, etc) and therefore don't need a reactive scope in Forget. 
For example, property loads only extract part of an existing value and don't 
allocate something new, while binary expressions are known to produce primitive 
values that don't allocate. We rely on the fact that whenever their inputs 
change we will re-run the component/hook and propagate the result forward. 

For Forest, the only way to propagate data is via reactive scopes: the component 
code is equivalent to a "setup" function. This PR updates some of our passes to 
ensure that we create (and don't prune) scopes for these types of operations. I 
started with a conservative set for now.
2023-12-11 11:34:23 -08:00
Joe Savona
1debb59830 🌲 Remove reactive scope handling from codegen
The previous PR converts reactive scopes to normal instructions, so that Forest 
mode won't have any scopes left by the time we reach codegen. This PR removes 
the now-unused codegen logic for forest.
2023-12-11 11:34:22 -08:00
Joe Savona
b3391295c8 🌲 Separate pass for lowering reactive scopes
For Forest, we previously converted reactive scopes into derived signals during 
Codegen. I'm moving this to a separate pass primarily to keep codegen simple 
since there's enough complexity just dealing with core JS semantics. Ideally 
we'd do a similar setup even for regular Forget, ie lower reactive scopes just 
prior to codegen. 

At the same time i also reordered the forget passes to be just before codegen, 
and cleaned things up a bit. For state lowering, we now just rewrite `useState` 
-> `createState`, because we actually need to keep around the setter function to 
trigger scheduling updates in addition to writing the signal value.
2023-12-11 11:34:22 -08:00
Joe Savona
1b5ef83b78 🌲 Separate flag for jsx compilation
Per discussion w @pieterv we'd like to have a mode where we compile JSX 
separately, so splitting this flag into two variants.
2023-12-11 11:34:21 -08:00
Mofei Zhang
9e267714db [patch] Compile hooks with any number of args in infer mode 2023-12-07 16:14:25 -05:00
Mofei Zhang
995f4b0528 [bugfix] Fix constant propagation to ObjectMethods 2023-12-06 13:20:50 -05:00
Mofei Zhang
d3e84d4dff [repro] repro for ContextVariable bug (ObjectMethod)
Found from eslint validator on www after doing a local sync + RunForget test of 
#2432 

P898168203 

> New Errors: 

> no-undef:'VARIABLE_NAME' is not defined.
2023-12-06 13:20:49 -05:00
Mofei Zhang
7e9f6ecfea [patch] Make holey array handling compatible with older babel versions
--- 

Copied from comments 

Older versions of babel have a validation bug fixed by 

- https://github.com/babel/babel/pull/10917 

- (code pointer) 
e7b80a2cb9 (diff-19b555d2f3904c206af406540d9df200b1e16befedb83ff39ebfcbd876f7fa8aL52) 

Link to buggy older version (observe that elements must be PatternLikes here) 


https://github.com/babel/babel/blob/v7.7.4/packages/babel-types/src/definitions/es2015.js#L50-L53 

Link to newer versions with correct validation (observe elements can be 
PatternLike | null) 


https://github.com/babel/babel/blob/v7.23.0/packages/babel-types/src/definitions/core.ts#L1306-L1311 

Tested on 3000+ components in fb: 

P898166848 

> Unexpected Errors 

> (count = 0) 

[patch] Make holey array handling compatible with older babel versions 

--- 

Copied from comments 

Older versions of babel have a validation bug fixed by 

- https://github.com/babel/babel/pull/10917 

- (code pointer) 
e7b80a2cb9 (diff-19b555d2f3904c206af406540d9df200b1e16befedb83ff39ebfcbd876f7fa8aL52) 

Link to buggy older version (observe that elements must be PatternLikes here) 


https://github.com/babel/babel/blob/v7.7.4/packages/babel-types/src/definitions/es2015.js#L50-L53 

Link to newer versions with correct validation (observe elements can be 
PatternLike | null) 


https://github.com/babel/babel/blob/v7.23.0/packages/babel-types/src/definitions/core.ts#L1306-L1311 

Tested on 3000+ components in fb: 

P898166848 

> Unexpected Errors 

> (count = 0)
2023-12-06 13:20:49 -05:00
Mofei Zhang
01c1b16db4 [patch] Make Codegen ObjectMethod call compatible with babel v7.7
--- 

Tested on - with RunForget 
([before](P897673438), 
[after](P897678035)) 

Compiles 12 more files
2023-12-06 13:20:48 -05:00
Mofei Zhang
41b164ed24 [validation] Runtime validation for hook calls
--- 

I modeled guards as try-finally blocks to be extremely explicit. An alternative 
implementation could flatten all nested hooks and only set / restore hook guards 
when entering / exiting a React function (i.e. hook or component) -- this 
alternative approach would be the easiest to represent as a separate pass 

```js 

// source 

function Foo() { 

const result = useHook(useContext(Context)); 

... 

} 

// current output 

function Foo() { 

try { 

pushHookGuard(); 

const result = (() => { 

try { 

pushEnableHook(); 

return useHook((() => { 

try { 

pushEnableHook(); 

return useContext(Context); 

} finally { 

popEnableHook(); 

} 

})()); 

} finally { 

popEnableHook(); 

}; 

})(); 

// ... 

} finally { 

popHookGuard(); 

} 

} 

// alternative output 

function Foo() { 

try { 

// check current is not lazyDispatcher; 

// save originalDispatcher, set lazyDispatcher 

pushHookGuard(); 

allowHook(); // always set originalDispatcher 

const t0 = useContext(Context); 

disallowHook(); // always set LazyDispatcher 

allowHook(); // always set originalDispatcher 

const result = useHook(t0); 

disallowHook(); // always set LazyDispatcher 

// ... 

} finally { 

popHookGuard(); // restore originalDispatcher 

} 

} 

``` 

Checked that IG Web works as expected 

Unless I add a sneaky useState: 

<img width="705" alt="Screenshot 2023-12-05 at 6 44 59 PM" 
src="https://github.com/facebook/react-forget/assets/34200447/3790bd76-7d71-44b5-a62e-f53256fb5736">
2023-12-05 15:02:34 -05:00
Mofei Zhang
37e1975050 [be] Move codegen logic Program.ts -> Codegen
--- 

Prior to this PR, we were mutating functions after CodegenReactiveFunction 
completes (in `Entrypoint/Program.ts`). 

The reasoning for this separation was that we wanted to keep non-compiler logic 
out of the core Pipeline. However, it made our code difficult to read and reason 
about. 

Open to other alternatives, like adding a pass after Codegen.
2023-12-05 15:02:33 -05:00
Mofei Zhang
346350f77d Revert: revert tsconfig module change for rollup
--- 

Currently on main, rollup does not inline source files 

```js 

// in packages/babel-plugin-react-forget 

// $yarn build 

// output 

var CompilerError_1 = require("./CompilerError"); 

Object.defineProperty(exports, "CompilerError", { enumerable: true, get: 
function () { return CompilerError_1.CompilerError; } }); 

// ... 

``` 

I debugged a bit but not familiar with node or rollup. 

- It seems that rollup fails to recognize source file imports with this setting, 
as resolveId no longer gets called 

- current `module` option defaults to `ESNext`, which works for some reason. 

Let's revert for now to unblock syncs. 

Sanity checked my repro by reinstalling node-modules and cleaning rollup cache.
2023-12-05 17:22:37 -05:00
Joe Savona
1d36764ca9 [be] Align tsconfig module/moduleResolution settings 2023-12-04 11:19:41 -08:00
Joe Savona
9fb3a26174 Add logging for number of memo *blocks* (in addition to slots)
Useful for understanding and comparing how much memoization Forget applies vs 
how much developers previously memoized by hand.
2023-11-30 15:20:01 -08:00
Joe Savona
3639704f2b Enable hooks validation in eslint 2023-12-04 08:22:51 -08:00
Joe Savona
0c0bedd377 New approach to hook validation
New approach to hooks validation per recent discussion. The idea is to avoid 
false positives while still preventing serious violations. See the comments in 
the file for more details about the approach. It uses a somewhat similar idea to 
InferReferenceEffects in that we track a "Kind" for each IdentifierId, and 
various instructions propagate or derive a result Kind from the operands. Kinds 
form a lattice and can be joined, allowing us to be more precise about known vs 
potential hooks, and known vs potential _sources_ of hooks.
2023-12-04 08:15:26 -08:00
Joe Savona
7da906d648 Fix Array#at and similar cases to capture if receiver is mutable
The previous PR helped me realize we weren't handling Array#at correctly. If the 
receiver is a mutable value its effect should be Capture and the lvalue effect 
needs to be Store. This PR updates the definition for Array#at to make the 
receiver Capture, and then updates inference to automatically set the lvalue 
effect to Store if _any_ argument (or the receiver) was Capture.
2023-11-29 12:05:25 -08:00
Joe Savona
75c7fdcbd0 MutableIfOperandsAreMutable flag handles mutation via capturing
There was one missing piece to the optimization from the previous PR: Array#map 
can return an alias to the receiver in its output, which means that mutations of 
the result have to be treated as mutations of the receiver. This means we need 
to use a Capture effect on the receiver. If that doesn't get downgraded to a 
Read bc the value was immutable, we then also need to make the lvalue effect a 
Store (so that InferMutableRanges actually looks at it for aliasing).
2023-11-29 10:46:44 -08:00
Joe Savona
f7d16db544 [RFC] Refine memoization for Array#map with non-mutating callbacks
Improves memoization for cases such as #2409: 

```javascript 

const x = []; 

useEffect(...); 

return <div>{x.map(item => <span>{item}</span>)}</div>; 

``` 

We previously thought that the `x.map(...)` call mutated `x` since its kind was 
Mutable. However, in this case we can determine that the map call cannot mutate 
`x` (or anything else): the lambda does not mutate any free variables and does 
not mutate its arguments. 

This PR adds a new flag to function signatures, used for method calls only, that 
checks for such cases. The idea is that if the receiver is the only thing that 
is mutable — including that there are no args which are function expressions 
which mutate their parameters — then we can infer the effect as a read. See 
tests which confirm that function expressions which capture or mutate their 
params bypass the optimization.
2023-11-29 10:46:43 -08:00
Joe Savona
65c54e2d70 Repro for unmemoized array due to mutation surrounding hook
Distilled repro of an internal example we found. Forget determines a mutable 
range for the array, but that mutable range spans a hook call, so the reactive 
scope gets pruned. That's all working as expected. 

What isn't ideal though is that if we know `x` is an array and `f` can't mutate 
its arguments, then `x.map(f)` shouldn't count as a mutation of `x`, since 
Array.prototype.map can only mutate the receiver via the callback (if the 
callback mutates its args). 

Improving on this example requires a) we have to know it's an Array, via type 
information or bc we saw an array literal and b) being precise about which 
functions could possibly mutate their parameters, which is tricky because of 
indirect mutations via stores, etc.
2023-11-29 10:46:42 -08:00
Joe Savona
b952bc3d87 [be] Consistently use known returnValueKind from signatures
We were using `returnValueKind` from function signatures for CallExpression but 
not MethodCall; this PR changes to use this signature information for both 
instruction kinds.
2023-11-29 10:46:42 -08:00
Mofei Zhang
359b9b1589 [ez] Patch unsound array destructuring
--- 

Going to hold off on landing until after codefreeze, it's not urgent as we 
already fixed playground in #2404. All other internal pipelines do error 
handling through Entrypoint, which catches and creates UnexpectedErrors as 
needed.
2023-11-28 17:48:22 -05:00
Sathya Gunasekaran
0a6d3b31de Add compilation failure test for Logger 2023-11-28 15:35:48 +00:00
Sathya Gunasekaran
e53e944580 [test] Add test for Logger 2023-11-28 15:35:46 +00:00
Sathya Gunasekaran
18d4913406 [babel] Don't use source location for hoisting check
Instead of using the source location to check for hoisting, just stop checking 
for a given component after we reach it's declaration. 

By definition all references to it before are (potential) hoisting errors. 

Note that there could be false positives but that's ok.
2023-11-28 14:18:24 +00:00
Sathya Gunasekaran
42fa01ec87 [test] Add failing test for component syntax with refs
The desugaring of Component syntax with refs is not compatible with our gating 
lowering.
2023-11-28 14:18:24 +00:00
Sathya Gunasekaran
be03439687 [yarn] Update hermes-parser
Need new version for component syntax updates
2023-11-28 14:18:24 +00:00
Sathya Gunasekaran
1ebb7ae376 [eslint] Assume external modules are non side-effecting
This lets us tree shake out the `chalk` module entirely
2023-11-28 14:10:48 +00:00
Sathya Gunasekaran
0ba3065812 [eslint] Don't rollup zod 2023-11-28 14:10:48 +00:00
Joe Savona
445baf9ae4 Extend effect dep validation to handle pruned memoization
Extends the validation that effect deps are memoized to handle an additional 
case that @gsathya  pointed out: when a dependency has a reactive scope but that 
scope ends up being pruned. We track reactive scopes which actually exist in the 
ReactiveFunction, and reject useEffect deps that have an associated reactive 
scope but where that scope does not exist (bc it got pruned).
2023-11-27 12:31:27 -08:00
Joe Savona
d7db416167 [RFC] useEffect dependency memoization check
This is one approach to testing whether useEffect dependencies are memoized. The 
idea is based off the observation that the only reason dependencies wouldn't be 
memoized (other than compiler bugs) is that they are mutated later. If they're 
mutated later, then the dep array will have a mutable range which encompasses 
the InstructionId of the useEffect call. So we look for that pattern and throw a 
validation error. 

The downside of this approach is that we might reject code that happens to be 
valid: specifically, that the lack of memoization isn't a problem in practice 
because the effect won't trigger a loop. But (per test plan) this doesn't seem 
to introduce that many new bailouts on www. Rather than implement a complex 
validation that checks whether we un-memoized something that was memoized in the 
input, it seems more practical to: 

1. Enable this more comprehensive validation against any form of un-memo'd 
effect dependency 

2. Flip the default for hooks (to assume they follow the rules), which will fix 
the primary cause of Forget pessimistically not memoizing dependencies. 

## Test Plan 

Synced to www and checked output via the upgrade script: a few components stop 
getting memoized bc they have un-memoized effect dependencies. Let's chat!
2023-11-27 10:20:43 -08:00
Mofei Zhang
5b8e94c6d8 [ez][wip] Make playground more resilient to crashes
--- 

<img width="1031" alt="image" 
src="https://github.com/facebook/react-forget/assets/34200447/8e475472-45c3-4ef0-aa4b-d187e72b999c"> 

Behold! No instacrash on `useMemo()`
2023-11-21 09:30:29 -06:00
Mofei Zhang
6a6ef0ab59 [repro] Forget outputs invalid code when we bailout
Sprout output: 

``` 

$ sprout --filter --verbose 

FAIL: bug-invalid-code-when-bailout 

Difference in forget and non-forget results. 

Expected result: { 

"kind": "ok", 

"value": "{}", 

"logs": [] 

} 

Found: { 

"kind": "exception", 

"value": "Cannot access 'bar' before initialization", 

"logs": [ 

"'The above error occurred in one of your React components:\\n' +\n  '\\n' +\n  
'    at globalThis.WrapperTestComponent 
(/Users/feifei0/fb/react-forget/packages/sprout/dist/runner-evaluator.js:30:26)\\n' 
+\n  '\\n' +\n  'Consider adding an error boundary to your tree to customize 
error handling behavior.\\n' +\n  'Visit 
https://reactjs.org/link/error-boundaries to learn more about error 
boundaries.'" 

] 

} 

```
2023-11-21 09:06:15 -06:00
Mofei Zhang
bfefbf3fce [repro] dce bug for JSX memberexpr tags in lambda 2023-11-21 09:06:14 -06:00
Joe Savona
8176ebb546 BuildHIR lowers FunctionDecl instead of rewriting to FuncExpr
We were modifying the Babel AST as a shortcut to lowering function declarations, 
instead we can explicitly lower them equivalently to a `let <id> = 
<function-expression>`.
2023-11-16 15:41:19 -08:00
Lauren Tan
a1e3891189 [rfc][babel] InvalidConfig always throws
When you have your panic threshold set to "NONE" as we recommend, it's easy to 
miss that your config is wrong (which makes everything not compile) because 
those errors were being silenced. This made debugging FluentUI and the 
forget-feedback testapp pretty difficult to figure out at first, and defeats the 
purpose of having config validation in the first place. 

This pr makes it so InvalidConfig errors always throw, regardless of the panic 
threshold set. In general our plugin should never throw at build time due to 
component bailouts, but because an InvalidConfig would bailout everything from 
being compiled at all, it seems reasonable to throw here
2023-11-17 12:01:20 -05:00
Lauren Tan
6d101435d4 [Babel] Fix up eslint suppression logic
Babel doesn't attach Comment nodes to anything, so they dangle off of 

the Program node while only specifying a range. This meant that 

previously we first had to traverse all of the Program's comments to 

find an eslint suppression of the rules of React, then during traversal 

of the individual functions, we would check if there were any *global* 

eslint suppressions, then bailout all components. 

This PR updates our logic to determine if individual functions are 

affected by an eslint suppression range: 

- If an eslint suppression range falls within its body; or 

- If an eslint suppression wraps the function
2023-11-17 10:22:46 -05:00
Lauren Tan
fbcc21c37a Add repro for bug with 'use no forget'
For some reason, when there are other hooks/components defined in the file, the 
'use no forget' directive stops working
2023-11-17 10:22:45 -05:00
Mofei Zhang
96f10e058b [be][tests] Change fixtures to evaluate successfully instead of throwing
--- 

Not dependent on changes from #2366, but now the fix is easy to review
2023-11-16 18:12:00 -05:00
Mofei Zhang
66748b00f2 [be][sprout] Snapshot files for sprout with shared utils
--- 

16 out of ~150 recently added sprout fixtures have exceptions that reflect 
easy-to-miss mistakes in the fixture input, like forgetting to import 
`useState`. 

This PR adds snapshot files for sprout to prevent these mistakes (or catch them 
at diff review time). This describes what it implements, but happy to take other 
suggestions as well! 

1. One sprout snapshot file for each input. 

This significantly increases the number of files we have, but makes it clear 
what the fixture is testing. I was hoping to expand on these snapshots to record 
the result of multiple re-renders, or mounts (with different parameters), but 
the tradeoff is that adding / changing fixtures will now require running `yarn 
sprout --mode update`. 

Some alternatives I've considered: 

- Only record snapshots for fixtures that error (`result.kind == "exception"`). 
This change would be pretty easy. This doesn't help sanity check sprout results 
for general mistakes though ("am I testing what I want to test?) 

- Warn loudly for fixtures that error -- seems like these are easy to ignore, 
but.. worth it for the convenience? 

- Some github action that runs and comments on your PR with sprout fixture 
changes; i.e. never manually update sprout files? 

2. Sprout and snap share a common snapshot file, with some parsing hackery. 

Previously, the implementation added new files to a separate sprout snapshot 
directory, assuming that devs don't need to read these often. 

After feedback from @josephsavona, I updated to have snap and sprout write to 
the same file for ease of reviewing / debugging (to be able to see the input / 
output side by side). 

- `snap --mode update` will update snap's part of the file. 

- `sprout --mode update` will update sprout's part 

- `snap` and `sprout` will both compare oldSnapshot (read from the file) with 
`newSnapshot = merge(oldSnapshot, newData)` to determine if a test pass. 

3. Some hacks 😅 Absolute paths to project directory (e.g. stack traces) are 
replaced with `<project_root>` in a mocked `console.log`
2023-11-16 18:12:00 -05:00
Mofei Zhang
fa07eb0b3b [be] make evaluator and worker easier to debug in sprout
--- 

jsdom and other libraries seem to cause jest workers to exit with 
`forceExit:true`. Not sure what option I set (or global I've overwritten) but 
console logs aren't flushed as a result, making debugging a bit confusing. 

This PR: 

- Moves some code from `eval(...)` to a typechecked real js function. I always 
had trouble debugging the `eval`ed code, so smaller code snippet is better here. 

- waits for jest workers to end before exiting
2023-11-16 18:12:00 -05:00
Joe Savona
94348be2d9 Enable ValidateNoSetStateInRender in ESLint plugin
Same as previous: this rule is working accurately with no false positives, let's 
enable it.
2023-11-16 07:52:13 -08:00
Joe Savona
7b6b370638 Consolidate ValidateNoSetStateInRender flags
I ran the plugin with the extended version of ValidateNoSetStateInRender enabled 
(incl. function expressions) and there are no false positives. Let's remove the 
flag for the function expression case since the whole rule is working 
accurately.
2023-11-15 13:05:24 -08:00
Joe Savona
6a1343c82b Repro of undefined expressioncontainer expression
Repro from T169063835. This works, but since it came up it seems good to add a 
test case for it just in case there's something funny here that we could regress 
on w/o realizing it.
2023-11-15 14:23:53 -08:00
Joe Savona
df42058237 Fix variable-resolution hoisting issues
Fixes one category of bugs with const hoisting. The algorithm finds all consts 
that need to be hoisted, then looks through the statements of a block to find 
the first statement which references that const, delaying the emission of the 
HoistedConst instruction until its actually used. To determine if a statement 
references a const we find every identifier in the statement and check if its 
binding is one of the hoisted bindings. 

There's a very small bug here: when we resolve the binding of each identifier, 
we need to resolve it in its own scope. We're currently resolving these 
identifiers agains the outer block statement's scope, which can cause us to 
misattribute identifiers when there is shadowing: 

``` 

const items = props.items.map(x => x); // we scan this statement, resolve 'x' in 
the block statement scope, and mis-attribute it to the outer x. 

const x = 42; // (1) this x is a candidate for hoisting, so the binding is in 
the set of hoisted consts 

```
2023-11-15 16:55:01 -08:00
Joe Savona
b55ccb1b84 Repro for hoisting bug
I had added a repro for this earlier but hadn't realized it was due to const 
hoisting. Renaming this test to clarify what's causing the problem and to make 
it easier to find.
2023-11-15 16:34:28 -08:00
Sathya Gunasekaran
eacf189eca [playground] Remove button from error message
We show the entire error in panel, no need to click to console.log 

This makes it easier to copy the error now. 

Before: 


https://github.com/facebook/react-forget/assets/565765/4c13abfe-a06d-4580-b3d7-b02792f53e57 

After: 


https://github.com/facebook/react-forget/assets/565765/0dff95cc-5207-48a5-a0b6-2055cadcb780
2023-11-16 12:01:59 +00:00
Sathya Gunasekaran
369c315ac4 [hir] Update error message to say global
This is non ideal but at least it's a step in the right direction. 

Getting the correct error requires us to track every identifier and global, 
which seems a bit excessive for now. 

We can revisit and improve this error if this is starting to confuse folks.
2023-11-15 17:03:55 +00:00
Sathya Gunasekaran
392a4fd9da [test] Add tests for mutating a global
The error is thrown correctly but the error message is incorrect.
2023-11-15 17:03:51 +00:00
Sathya Gunasekaran
306fe03e78 [printer] Print function name if available 2023-11-15 09:36:32 +00:00
Sathya Gunasekaran
58947d9075 [babel] Rename isReactFunction to isReactAPI
Disambiguates better between this and the existing `isReactFunctionLike` 
function.
2023-11-15 09:36:31 +00:00
Joe Savona
696434fed8 Fix ValidateNoSetStateInRender for loops 2023-11-14 11:33:39 -08:00
Mofei Zhang
c434ef4b70 [validation] Patch false positives for hook calls after loops
--- 

We were throwing `InvalidReact` errors on valid inputs. 

```js 

// Input 

function Foo() { 

log("block0"); 

for (const _ of foo) { 

log("loop"); 

} 

useBar(); 

} 

// IR 

bb0: 

// log("block0"); 

ForOf init=bb2 loop=bb3 fallthrough=bb1 

bb2: 

// init 

Branch: then:bb3 else:bb1 

bb3: 

// log("loop") 

Goto(Continue) bb2 

bb1: 

// useBar(); 

Return 

``` 

We correctly compute post dominators here. 

```js 

// In validateUnconditionalHooks 

console.log(dominators.debug()); 

/* Output: 

(read x => y as x is the post-dominator for y (all paths from x to the exit must 
go through y)) 

"bb4" => "bb4", 

"bb1" => "bb4", 

"bb2" => "bb1", 

"bb3" => "bb2", 

"bb0" => "bb2", 

*/ 

``` 

However, `findBlocksWithBackEdges` prevented us from adding `bb1` to the 
`unconditionalBlocks` set as `bb2` (its post dominator) has a back edge. I'm not 
sure what the `findBlocksWithBackEdges` was doing previously, so I replaced it 
with an invariant asserting that the loop terminates.
2023-11-14 13:04:31 -05:00
Mofei Zhang
00abc5acf5 [be][tests] Run todo fixtures
--- 

Snap should compile all fixtures to record changes in results, even `todo` 
prefixed ones. Previously, they were skipped as we noted the correlation of `// 
@skip` pragmas and file naming. 

Now, no fixture should be skipped as our compiler pipeline should be able to 
handle every kind of error.
2023-11-14 13:04:28 -05:00
Sathya Gunasekaran
2efd85f145 Convert CompilationModeScham to zod type 2023-11-14 11:46:26 +00:00
Sathya Gunasekaran
084e4325b9 Convert PanicThresholdOptions to zod type 2023-11-14 11:46:23 +00:00
Sathya Gunasekaran
0821a57fa8 [playground] Capture Babel parsing errors
Before: 

<img width="1416" alt="Screenshot 2023-11-14 at 9 09 53 AM" 
src="https://github.com/facebook/react-forget/assets/565765/f53daf34-080a-4804-9a61-77c141c05d21"> 

After: 

<img width="1444" alt="Screenshot 2023-11-14 at 9 09 45 AM" 
src="https://github.com/facebook/react-forget/assets/565765/8861c266-9a49-4ff7-99cd-7240ca1750b1">
2023-11-14 18:24:38 +00:00
Sathya Gunasekaran
07d6c6d8aa [playground] Show error when compiling unsupported functions
Before: 

<img width="1117" alt="Screenshot 2023-11-14 at 9 11 28 AM" 
src="https://github.com/facebook/react-forget/assets/565765/d1ec2b5f-1bc5-4d29-9811-effcd39581e4"> 

After: 

<img width="1115" alt="Screenshot 2023-11-14 at 9 11 18 AM" 
src="https://github.com/facebook/react-forget/assets/565765/9a41889c-cf7b-4f31-8b6e-e26bfb204883">
2023-11-14 09:10:31 +00:00
Sathya Gunasekaran
0794aacdbf Turn off validateNoSetStateInRender in babel-plugin
There's false positives that we found in the Eslint plugin
2023-11-14 16:59:10 +00:00
Sathya Gunasekaran
1641f94a86 [eslint] Turn off validations
We're seeing false positives
2023-11-14 16:59:09 +00:00
Sathya Gunasekaran
3da32312e7 [globals] type useEffect return value as primitive
useEffect returns undefined
2023-11-13 10:51:08 +00:00
Lauren Tan
480c11bdb1 [hoisting] Make hoisting related errors consolidatable
Noticed from our paste that we weren't correctly rolling up hoisting related 
errors due to specific information being in the error title, so this PR moves 
them into description instead.
2023-11-13 16:54:54 -05:00
Joe Savona
800b874ed1 fix main
Forgot to update snapshots when addressing PR feedback.
2023-11-13 13:14:27 -08:00
Joe Savona
e6c5c9a005 Improve NoSetStateInRender function expression check
Updates the approach used in ValidateNoSetStateInRender to detect function 
expressions called during render. We now do the following: 

* Track function expression which are known to unconditionally call setState 
themselves- if these functions get called, that’s equivalent to calling 
setState. We call the validation recursively to compute this. 

* Track LoadLocal/StoreLocal indirections for such function expressions. 

* Check CallExpressions where the callee is either a known SetState (via type 
info) _or_ (new) where the callee is in the set of known-to-setState function 
expressions. 

The Set is shared throughout the analysis, so we can even find multiple levels 
of indirection (see new test case).
2023-11-13 11:28:16 -08:00
Joe Savona
dcbdf06491 Extra test case related to memoization "within" freeze
I found an interesting edge case in the previous diff with mutation of a value 
that appears in the expression of an object key: 

```javascript 

const key = {} 

const object = { 

[mutateAndReturnOtherValue(key)]: 42, 

}; 

mutate(key); 

``` 

We analyze and represent this correctly all the way through to codegen, but then 
we hit the bug that @mofeiZ has noticed before: the temporary for `t = 
mutateAndReturnOtherValue(key)` isn't emitted immediately (bc its a temporary). 
It gets emitted inside the memo block for `object`, which is incorrect. 

I tried to reproduce that here with JSX and it works as expected. It's an 
interesting case though so let's land this to ensure we don't regress.
2023-11-13 11:03:00 -08:00
Joe Savona
8d41529600 Support computed object keys 2023-11-13 09:55:52 -08:00
Mofei Zhang
f47685c602 [be] Flag and test for unexpected exceptions during compilations 2023-11-12 14:56:10 -05:00
Mofei Zhang
7db2388fff [be] tsconfig: useUnknownInCatchVariables 2023-11-12 14:44:15 -05:00
Joe Savona
3d2a05490f Update eslint plugin to enable more safe validations 2023-11-10 17:08:26 -08:00
Joe Savona
fc85f24865 Separate flag for ref access violation within function expressions
Adds a separate compiler flag for enabling the incomplete validation of ref 
access within function expressions. Unlike the previous PR for 
set-state-in-render validation, ref access in render can be okay in some 
circumstances so i'm leaving this off by default. The point of splitting this up 
is that our linting will be able to enable the rule without risk of false 
positives.
2023-11-10 17:07:21 -08:00
Joe Savona
6e038f9699 Partially enable validateNoSetStateInRender
The approach i initially took to validating function expressions was to try to 
extend the mutable range if they are called during render, and then use the 
mutable range of a function to determine if it's called during render later. 
However there are cases where the range can be extended for other reasons, as 
@poteto discovered, so we can't rely on the range extension. We've had several 
of our validations completely off as a result of this. 

In this PR i'm re-enabling @poteto's ValidateNoSetStateInRender pass by default, 
but making the function expression checking use a separate compiler flag. This 
means we'll have some false negatives, but should guarantee that we avoid false 
positives. This means we can definitely catch things like: 

``` 

const [state, setState] = useState(false); 

setState(true); 

``` 

Which we would have allowed by default before.
2023-11-10 16:52:29 -08:00
Mofei Zhang
c08b7be34e [be] Add comments for LoggerEvent type 2023-11-10 17:09:31 -05:00
Mofei Zhang
9965db70bd Revert "[babel] Remove unused PipelineError"
This reverts commit 10d129a8406e9d226abdb6943bf8512e34ce91db 

--- 

Reverts #2311 due to undocumented assumptions being broken. I also added some 
comments to `LoggerEvents` to explain each event type. 

In `Program.ts`, we have something like the following code. `compile` could 
produce any number of errors (not just expected errors / instances of 
`CompilerError`). As an example, we sometimes error in `Codegen` due to babel 
version incompatibilities (`Error: ObjectMethod: Too many arguments passed. 
Received 7 but can receive no more than 5`). 

```js 

try { 

// any error could be thrown here 

compile(input); 

} catch (e) { 

// unknown type for e 

handleError(e, ...); 

} 

```
2023-11-10 17:09:29 -05:00
Sathya Gunasekaran
bd3661020e Add test for bailouts skipping compilation 2023-11-09 12:00:37 +00:00
Sathya Gunasekaran
69eb9ccccc [snap] Add option to set panicThreshold 2023-11-09 12:00:34 +00:00
Joe Savona
f92a058ca5 Codegen comments to explain output 2023-11-10 11:45:29 -08:00
Joe Savona
956c1ee7e2 Repro for bug with incorrectly using context variables when name overlaps 2023-11-01 17:42:14 -07:00
Joe Savona
ab0f698585 More tests for fbt whitespace handling
I experimented with more variations but prettier collapses most of them: any run 
of consecutive whitespace within a jsxtext node will get collapsed into a single 
space, for example. So the main difference in practice is whether a jsxtext node 
(preceding a value) has a trailing space/newline or not: 

``` 

Text <fbt:param /> Text 

// vs 

Text<fbt:param />Text 

``` 

which we now have tests for.
2023-11-10 11:21:10 -08:00
Joe Savona
127b5df7ca [be] Group fbt fixtures in directory 2023-11-10 11:21:07 -08:00
Joe Savona
6f2fe1e7e9 Fix FBT whitespace handling (again (again))
Nice find, @mofeiZ! I really tried to thoroughly test all the examples I could 
think of for FBT whitespace but i missed the newline case. Initially Mofei found 
[this code potentially related to whitespace 
handling](https://github.com/facebook/fbt/blob/main/packages/babel-plugin-fbt/src/fbt-nodes/FbtImplicitParamNode.js#L230-L233) 
but Babel never seems to produce consecutive JsxText nodes — this looks like 
maybe a leftover from older babel versions. 

I noticed that code wasn't actually trimming the whitspace but clearly it was 
happening somewhere, so i grepped for 'trim' and found that [this 
code](0b4e0d13c3/packages/babel-plugin-fbt/src/babel-processors/JSXFbtProcessor.js (L143)) 
calls a 
[normalizeSpaces](0b4e0d13c3/packages/babel-plugin-fbt/src/FbtUtil.js (L86C10-L86C45)) 
helper. Updating our logic to handle whitespace similarly just for children of 
fbt nodes produces the expected result.
2023-11-10 10:21:10 -08:00
Lauren Tan
82ed13b8b4 [eslint] Don't inline everything
Turns out this will cause an OOM in node.js when running eslint as part of the 
IDE 

``` Before: 16M   packages/eslint-plugin-react-forget/dist/index.js After:  2.2M 
 packages/eslint-plugin-react-forget/dist/index.js ```
2023-11-10 13:49:30 -05:00
Joe Savona
2927792dca Extra tests for #2339 2023-11-10 09:25:40 -08:00
Joe Savona
4742d27f9e [be] Clarify naming in scope merging pass
Anytime we have a nested `.scope` in code my brain hurts. For example 
`scope.scope.dependencies`. This PR updates the scope merging pass to use the 
name `scopeBlock` for a ReactiveScopeBlock and `scope` only for ReactiveScope 
values, to make things a bit more clear.
2023-11-09 16:33:41 -08:00
Joe Savona
691cad6b51 Optimization for scope merging when no deps
Addresses T168684688 (#2242). MergeReactiveScopesThatInvalidateTogether does not 
merge scopes if their output is not guaranteed to change when their inputs do. 
So for example a case such as `{session_id: bar(props.bar)}` will not merge the 
scopes for `t0 = bar(props.bar)` and `t1 = {session_id: t0}`, because t0 isn't 
guaranteed to change when `props.bar` does, and we want to avoid recreating the 
t1 object unless it semantically changes. 

But there's a special case: if a scope has no dependencies, then we'll never 
execute it again anyway. So it doesn't matter what kind of value it produces and 
it's safe to merge with subsequent scopes: 

```javascript 

return {session_id: bar()} 

``` 

Without the reactive input, `bar()` will always return the same value since 
we'll only ever call it once anyway. So it's safe to then merge with the scope 
for the outer object literal.
2023-11-09 16:21:00 -08:00
Sathya Gunasekaran
e35df45eaf Test component name access in gated setup
Jesse flagged this as a potential bug internally but this looks correct to me. 

Adding a test case so that we don't regress in the future.
2023-11-09 10:30:54 +00:00
Sathya Gunasekaran
cdf39e7956 [sprout] Add ReactForgetFeatureFlag 2023-11-09 10:30:52 +00:00
Mofei Zhang
6c327b0f3e [repro] Repro for fbt preserve whitespace bug
Current sprout output (if you remove the line in `SproutTodoFilter`): 

``` 

Failures: 

FAIL: bug-fbt-preserve-whitespace 

Difference in forget and non-forget results. 

Expected result: { 

"kind": "ok", 

"value": "Before text hello world", 

"logs": [] 

} 

Found: { 

"kind": "ok", 

"value": "Before texthello world", 

"logs": [] 

} 

``` 

`fbt` transforms run before jsx ones, so our lowering should also account for 
[fbt's whitespace 
rules](https://github.com/facebook/fbt/blob/main/packages/babel-plugin-fbt/src/fbt-nodes/FbtImplicitParamNode.js#L230-L233) 
in `BuildHIR:trimJsxText`.
2023-11-09 18:55:06 -05:00
Mofei Zhang
59ddf537ed [tests][fixtures] Enable sprout on basic existing fbt tests
--- 

Output: 

``` 

$ yarn sprout:build && yarn sprout --verbose 

... 

PASS  fbt-call-complex-param-value 

ok <div>Hello, Sathya!</div> 

PASS  fbt-call 

ok <div>2 items</div> 

PASS  fbt-template-string-same-scope 

ok <div>{"children":"for 3 experiences"}</div> 

... 

```
2023-11-09 18:55:05 -05:00
Mofei Zhang
372696c541 [sprout] Add support for fbt
Fbt + typescript [seems](https://github.com/facebook/fbt/issues/49) [to 
be](https://github.com/facebook/sfbt/issues/72) a non-blessed workflow. We do 
want to allow for both flow and typescript tests, so I followed some 
instructions [from a 
guide](https://dev.to/retyui/how-to-add-support-typescript-for-fbt-an-internationalization-framework-3lo0) 
to add support. 

- fbt tags desugar to.. "fbt" strings. By default, typescript removes unused 
imports at parse step (and on autoformat steps). We pass special 
babel-typescript configs and change vscode settings to mitigate this. 

- I tried adding `fbt` to the global scope, but the fbt transform asserts that 
`fbt` is actually imported in the source program. 

- Other hacks are available, like saying we'll only allow for fbt in flow files, 
or always patching the source code to have an "fbt" import. This seemed the most 
reasonable and easiest to debug / follow when writing tests
2023-11-09 18:55:05 -05:00
Mofei Zhang
b25c14feb1 [tests][be] Clean up fixture selection logic
--- 

Refactor selection logic to be easier to read; add support for .jsx test files 
(feels a bit weird adding a `.jsx` fixture and not seeing it get run)
2023-11-09 16:50:00 -05:00
Lauren Tan
2726484823 Add license to rolledup output 2023-11-09 13:00:13 -05:00
Lauren Tan
a6e492d8d0 [feedback] Add script to build packages for testapp
In order to make changes to the testapp's dependencies, we need to update the 
lockfile which means modules need to actually exist. This adds a new script to 
just run a build of the packages we sync so that module resolution in the 
testapp will work
2023-11-09 11:31:33 -05:00
Sathya Gunasekaran
f13594280b [ez] Use BabelFn type 2023-11-09 08:38:00 +00:00
Lauren Tan
bb92520ba5 Some existing InvalidConfig errors should be invariants
Now that we have validation of the compiler config, these old errors weren't 
categorized correctly. Readjusted them to be invariants instead.
2023-11-08 14:10:18 -05:00
Lauren Tan
973ec414bd [eslint] Try inlining everything in build 2023-11-08 15:19:07 -05:00
Sathya Gunasekaran
7aca04a0d6 [babel] Refactor handleError to pass the error first
Pass the most important and required argument as the first parameter.
2023-11-08 16:09:19 +00:00
Sathya Gunasekaran
4e7d437880 [babel] Remove unused PipelineError
Most of the use cases are already handled with ErrorSeverity.InvalidConfig
2023-11-08 16:09:18 +00:00
Sathya Gunasekaran
0beeb1e7ad Remove unncessary nullcheck 2023-11-08 16:09:18 +00:00
Sathya Gunasekaran
521f056875 Always clone DEFAULT_HOOKS
Reusing DEFAULT_HOOKS instance is a bit scary in case we mutate it by mistake 
and this gets reflected across all compiles. 

This isn't a concern now as we can't change the config during compilation. But 
this PR keeps us safe in case we change this behavior in the future. 

The tradeoff is a bit of perf which I think is the right tradeoff here.
2023-11-08 16:09:17 +00:00
Sathya Gunasekaran
ec089d5454 [test] Add test to make sure stringy enums are parsed correct
Meta internal config is going to be stringy, so let's add tests.
2023-11-08 16:09:16 +00:00
Sathya Gunasekaran
6d2fce8eb5 Make CustomHooks optional and add default value
Don't propogate nullability into the plugin.
2023-11-08 16:09:16 +00:00
Sathya Gunasekaran
8b12f179d3 Add tests to make sure our config validation error message doesn't regress 2023-11-08 16:09:15 +00:00
Sathya Gunasekaran
3f7b9e23e3 Don't allow null to be passed to validateEnvironmentConfig
zod will throw an error if we pass null, so let's not do this. 

Adding a temporary workaround until we start validating PluginOptions.
2023-11-08 16:09:14 +00:00
Sathya Gunasekaran
42497b60f5 [error] Prefix side-effecting function names with throw
I did a double take when I thought we didn't handle returning the 

error when reading the code and when I edited the code, typescript told 

me that there's no need to return as creating the error will throw. 

This PR makes it clear from the name of the function that we will throw.
2023-11-08 16:09:13 +00:00
Sathya Gunasekaran
33ffde4836 [playground] turn of ppr 2023-11-08 15:46:43 +00:00
Sathya Gunasekaran
6405c980eb Use starred-block for multi line comments 2023-11-08 08:27:41 +00:00
Sathya Gunasekaran
3d6d81052d [be] Upgrade Eslint to 8.27.0 2023-11-08 08:27:41 +00:00
Joe Savona
702aadd82b Fix to only add imports if we compiled something
We should only add imports if we actually compiled anything, this is what caused 
the internal issue despite the file in question not having any functions 
opted-in to compilation.
2023-11-07 16:22:03 -08:00
Joe Savona
64dffe9e78 repro for gating error despite no compiled functions 2023-11-07 16:09:29 -08:00
Lauren Tan
9d5ff320ba [babel] Fix default value for environment option
When an `environment` isn't explicitly provided by the user's config, we used to 
default this to `null` in `parsePluginOptions` which is called right at the 
start of the Babel plugin. These parsed options were then being passed to zod, 
which was expecting an object type, not null. 

Zod can reify a default config based on the schema, if an empty object is passed 
in. So by changing our default value to `{}` this should fix the compiler 
bailing out incorrectly on valid compiler options 

Test plan: tested this manually on the external repo by modifying node_modules
2023-11-07 18:58:21 -05:00
Sathya Gunasekaran
00cb557b12 Add runtime validation for EnvironmentConfig 2023-11-07 11:04:37 +00:00
Sathya Gunasekaran
1c53385db5 Don't plumb PartialEnvironmentConfig
Let's do validation at the API level and pass around the fully parsed config 
inside the plugin.
2023-11-07 11:04:37 +00:00
Sathya Gunasekaran
58a10c0ac1 [babel] Outline insertNewFunctionDeclaration
This lets us use the parsed and validated `gating` and `instrumentForget` 
options.
2023-11-07 11:04:36 +00:00
Sathya Gunasekaran
d8734b5136 Remove hasForgetMutatedOriginalSource
Derive it from compiledFns rather than duplicating state
2023-11-07 11:04:35 +00:00
Sathya Gunasekaran
bccd06a632 [babel] Simplify noEmit and hasCriticalError check 2023-11-07 11:04:35 +00:00
Sathya Gunasekaran
688305a978 Check if critical error before throwing 2023-11-07 11:04:34 +00:00
Sathya Gunasekaran
cf70ef899e Validate options before replacing with compiled function 2023-11-07 11:04:34 +00:00
Sathya Gunasekaran
d6ff65ecc7 Add runtime validation for ExternalFunction
Use zod to do runtime validation and throw if incorrect. 

This PR only adds validation for ExternalFunction, will validate other options 
in follow on PRs.
2023-11-07 11:04:33 +00:00
Joe Savona
337c17a66f More React API coverage
Redo of #2248 in its own stack
2023-11-06 16:27:11 -08:00
Joe Savona
72c27b6893 Feature flag for transitively freezing values
This PR adds a feature flag to model a potential new-in-practice rule in React: 
that freezing a function expression also freezes its closed-over values, 
transitively. For example, in the following code `data` is frozen when the 
lambda that captures it is is passed to useEffect: 

```javascript 

const data = []; 

// useEffect freezes its argument (the function expr), which transitively 
freezes its captured value data 

useEffect(() => { 

foo(data); 

}, [data]); 

data.push(true); // ERROR: mutating a frozen value 

mutate(data); // we conservatively assume this doesn't mutate but could be wrong 

``` 

Note that this rule has never been written down or enforced. It is theoretically 
equivalent to the rule (already implemented in Forget) that values captured by 
JSX are frozen: 

```javascript 

const style = {...}; 

<div style={style}>...</div> 

style.width = 10; // ERROR: mutating a frozen value 

mutate(style); // we conservatively assume this doesn't mutate but could be 
wrong 

``` 

However, JSX is typically constructed toward the very end of a render function. 
Thus in practice there isn't much subsequent code that could even modify such a 
captured value. But for the useEffect case (and other hooks that take closures 
as arguments), they tend to occur much earlier in a render function. There's 
more code that can run later and still modify the captured values, without 
causing issues in practice. The _practical_ rule today is that you can't modify 
values captured by frozen lambdas _after the component returns_: it's fine in 
practice to modify captured values between calling eg useEffect and returning 
from render. 

Thus this feature flag is fairly likely to break some percent of real product 
code. I'm adding this so that we can experiment and see how unsafe it actually 
is.
2023-11-06 08:33:32 -08:00
Joe Savona
c424cbbfa7 Add validation against instructions not part of their scope
Adds an internal compiler assertion pass which checks that all the instructions 
which are necessary for constructing a given scope correctly end up within the 
corresponding ReactiveScopeBlock. All known cases where this can occur are fixed 
earlier in the stack, but this assertion will help us catch any other cases we 
haven't thought of. See docblock comment for more info. 

## Test Plan 

I manually reverted the fixes from the previous PRs while keeping the new 
fixtures, and verified that this new assertion pass flags the fixtures as 
invalid.
2023-11-06 08:33:31 -08:00
Joe Savona
35fee31355 Handle IIFE with logical mutated later
The previous changes mostly meant that we removed the label terminal and didn't 
have instructions for the same scope split in a way that we couldn't merge. But 
logicals were still causing a split because MergeConsecutiveScopes can't merge 
the blocks in that case. Here we move PruneUnusedLabels earlier in the pipeline 
to ensure that instructions from IIFEs have floated up to the parent block scope 
level.
2023-11-06 08:33:30 -08:00
Joe Savona
e502e69b2d Restore React.useMemo validation
This got lost by moving the validation earlier.
2023-11-06 08:33:30 -08:00
Joe Savona
0d3f3884b8 Fix for IIFE return values mutated later
Fixes for the previous PR. What was happening is that our inference was 
inferring the correct mutable ranges and reactive scopes, but the inlining 
process left the instructions from the IIFEs inside a separate block, with a 
'label' terminal preceding it. When we converted to ReactiveFunction this was 
preserved as a ReactiveLabelTerminal, which meant that the first instruction for 
the mutable range could be nested inside one LabelTerminal, while more would be 
in a subsequent LabelTerminal. But we close blocks based on the block scope! 
This meant that we'd have leftover instructions (in the second LabelTerminal) 
that got left out of the block. 

Furthermore, because inlining was happening after EnterSSA we weren't creating 
phis correctly. This PR fixes a bunch of these issues, and a subsequent PR 
handles the remaining cases: 

* We move DropManualMemo and InlineIIFEs before EnterSSA. This means we lose the 
ability to use type information, but we ensure that we create proper SSA ids and 
phis for any reassignments within the IIFE 

* We also update PruneUnusedLabels to not just remove the unused labels, but to 
actually remove LabelTerminals that don't need them.
2023-11-06 08:33:29 -08:00
Joe Savona
1607fb39c3 Repro for incorrect memoization of iife
We construct invalid mutable ranges in these cases because the range starts 
within a labeled block. We need to run merge consecutive scopes and EnterSSA 
after inlining so that the code is lifted out of the labeled block to the 
correct scope, and so that we create phis for reassignments within the IIFE.
2023-11-06 08:33:28 -08:00
Sathya Gunasekaran
60af6671d7 [ez] Check for use-before-decl only when gating is on 2023-11-02 12:32:40 -07:00
Lauren Tan
48e709e7ad [eslint] Add test for classes
This used to cause issues because I hadn't configured Babel in the eslint plugin 
properly, so adding this as a regression test
2023-11-03 16:32:53 -04:00
Lauren Tan
3634980183 [eslint] Switch compiler to "infer" mode 2023-11-03 16:27:20 -04:00
Lauren Tan
a2aa032276 [eslint] Plugin should never throw
This has caused issues for people when things like Babel cause issues. It's not 
actionable and it crashes eslint. Just like the Babel plugin, the eslint plugin 
should never throw. Instead, let's log the error so the data isn't lost.
2023-11-03 16:27:18 -04:00
Lauren Tan
fe147cb16a [babel] Add hermes-parser type definitions to babel-plugin 2023-11-03 15:22:53 -04:00
Lauren Tan
a030d05d95 [eslint] Add test for component syntax 2023-11-03 15:22:52 -04:00
Lauren Tan
a74982d660 [eslint] Update hermes-parser, add hermes-eslint 2023-11-03 11:49:02 -04:00
Sathya Gunasekaran
4afee45440 [ez] Add missing ts type 2023-11-02 11:10:33 -07:00
Sathya Gunasekaran
adadc21021 [babel] Check if the gated component is used before decl 2023-11-02 11:10:30 -07:00
Lauren Tan
d49eb8b580 [ez] Run copyright script 2023-11-02 12:43:35 -04:00
Lauren Tan
0cc473fc9a [ez] Move copyright script to top level 2023-11-02 12:43:33 -04:00
Lauren Tan
5bd9642578 [feedback] Combine bundle scripts and don't commit dist
This combines our scripts and makes it so we no longer need to create a separate 
commit to add the forget-feedback/dist directory into the react-forget repo. I 
originally did that so I could run tests here, but now that the external repo is 
created and has its test suite hooked up to CI, this is now unnecessary friction 
to run a sync
2023-11-02 11:58:11 -04:00
Lauren Tan
4a793bb86e [hoisting] Add more repros for hoisting bugs 2023-11-02 11:01:29 -04:00
Sathya Gunasekaran
dca54c8932 [playground] Use standalone prettier
All of these warnings go away: ``` ➜  playground git:(remove-prettier) ✗ yarn 
dev yarn run v1.22.19 $ NODE_ENV=development && next dev ready - started server 
on 0.0.0.0:3000, url: http://localhost:3000 warn  - You have enabled 
experimental feature (appDir) in next.config.js. warn  - Experimental features 
are not covered by semver, and may cause unexpected or broken application 
behavior. Use at your own risk. info  - Thank you for testing `appDir` please 
leave your feedback at https://nextjs.link/app-feedback 

event - compiled client and server successfully in 964 ms (199 modules) wait  - 
compiling /page (client and server)... warn  - 
../../node_modules/prettier/index.js Critical dependency: the request of a 
dependency is an expression 

../../node_modules/prettier/index.js Critical dependency: require function is 
used in a way in which dependencies cannot be statically extracted 

../../node_modules/prettier/index.js Critical dependency: the request of a 
dependency is an expression 

../../node_modules/prettier/index.js Critical dependency: the request of a 
dependency is an expression 

../../node_modules/prettier/third-party.js Critical dependency: the request of a 
dependency is an expression wait  - compiling... warn  - 
../../node_modules/prettier/index.js Critical dependency: the request of a 
dependency is an expression 

../../node_modules/prettier/index.js Critical dependency: require function is 
used in a way in which dependencies cannot be statically extracted 

../../node_modules/prettier/index.js Critical dependency: the request of a 
dependency is an expression 

../../node_modules/prettier/index.js Critical dependency: the request of a 
dependency is an expression 

../../node_modules/prettier/third-party.js Critical dependency: the request of a 
dependency is an expression ``` 

Standalone prettier is meant to be used in the browser: 
https://prettier.io/docs/en/browser
2023-11-02 07:00:05 -07:00
Sathya Gunasekaran
3d3ad1b9ef [LeaveSSA] Process all phis in a block
I don't know if it's possible to write a test for this as I can't seem to get 
the codegen to change. 

For the following testcase: ``` function useFoo(setOne) {   let x;   let y;   if 
(setOne) {     x = 1;     y = 3;   } else {     x = 2;     y = 5;   } 

return { x, y }; } ``` 

The LeaveSSA changes from: ``` .... bb1 (block):   predecessor blocks: bb2 bb3   
x$36:TPrimitive: phi(bb2: x$19, bb3: x$19)   y$21[8:14]:TPrimitive: phi(bb2: 
y$21, bb3: y$21) 

... ``` 

to ``` ... bb1 (block):   predecessor blocks: bb2 bb3   x$36:TPrimitive: 
phi(bb2: x$19, bb3: x$19)   y$38:TPrimitive: phi(bb2: y$21, bb3: y$21) ... ``` 

Notice how `y`'s reassignment got skipped previously.
2023-10-24 16:09:08 +01:00
Joe Savona
3571dfa056 [be] remove dead code in InferReactiveScopeVariables 2023-11-01 17:13:06 -07:00
Joe Savona
3e157bbc27 Propagate reactivity to other operands accounting for mutable ranges
Previously if any operand was reactive, we transferred that reactivity to other 
operands that had a mutable effect (capture, conditionally mutate, mutate, or 
store). But a value can be captured without ever being modified again. This PR 
updates the logic to only transfer reactivity among operands that are actually 
mutable at the given instruction, based on the mutable range. This is strictly 
more precise.
2023-11-01 17:13:05 -07:00
Joe Savona
b34172c11e Reactivity inference is single-pass when no loops
We can complete in a single-pass if there are no loops, as a performance 
optimization.
2023-11-01 17:13:04 -07:00
Joe Savona
3ab47ac6bd Replace InferReactiveIdentifiers w new inference
This PR adds one remaining feature to InferReactivePlaces: tracking indirections 
like LoadLocal, PropertyLoad, and similar. Consider something like: 

``` 

// INPUT 

x.push(reactiveValue); 

// HIR 

t0 = LoadLocal 'x' 

t1 = PropertyLoad t0, 'push' 

t2 = LoadLocal 'reactiveValue' // reactive 

t3 = CallExpression mutate t0 . read t1 ( read t2 ) 

``` 

Because a reactive value (`t2`) flows into `t0`, we want to record t0 as 
reactive as well. But that's just the temporary for `LoadLocal 'x'` - what's 
really happening is that from this point, `x` is reactive. 
InferReactiveIdentifiers tracked this, and now that logic is ported into 
InferReactivePlaces as well. That lets us remove all the actual inference from 
InferReactiveIdentifiers.
2023-11-01 17:13:04 -07:00
Joe Savona
8610533ed1 Infer reactive control dependencies
Updates `InferReactivePlaces` to infer control dependencies. We build on the 
formal definition of control dependencies, which is that statement S2 is 
control-dependent on statement S1 if S1 is in the post-dominance-frontier of S2. 
Intuitively, if S1 decides whether S2 is reached or not, then S1 is a control 
dependency of S2. The post dominance frontier of a given statement S is the set 
of statements which may or may not reach S, and captures the intuitive notion. 

We take advantage of phis: phis are the point where a variable may have multiple 
values depending on the path we took. If a phi is not already known to be 
reactive from data dependencies we check for control dependencies. Specifically 
we look at each phi operand. We check if the block that the operand came from 
has any reactive control dependencies, and if so we mark the phi itself as 
reactive. 

The post-dominance-frontier (PDF) algorithm requires walking the post-dominator 
tree a bunch, so we cache the PDF of blocks so that we don't have to recalculate 
on subsequent iterations. 

In addition, `InferReactiveIdentifiers` now uses the _union_ of its own 
inference plus the new `InferReactivePlaces` output when deciding what 
identifiers are reactive. This ensures that control dependencies are recorded 
correctly, fixing the previous test cases. The next diff adds the remaining 
features to InferReactivePlaces so that it can fully replace 
InferReactiveIdentifiers.
2023-11-01 17:13:03 -07:00
Joe Savona
d5c3fb87e6 HIR-based reactive identifier analysis
See context from #2187 for background about control dependencies. 

Our current `PruneNonReactiveIdentifiers` pass runs on ReactiveFunction, after 
scope construction, and removes scope dependencies that aren't reactive. It 
works by first building up a set of reactive identifiers in 
`InferReactiveIdentifiers`, then walking the ReactiveFunction and pruning any 
scope dependencies that aren't in that set. 

The challenge is control variables, as demonstrated by the test cases in #2184. 
`InferReactiveIdentifiers` runs against ReactiveFunction, and when we initially 
wrote it we didn't consider control variables. To handle control variables we 
really need to use precise control- & data-flow analysis, which is much easier 
with HIR. 

This PR adds the start of `InferReactivePlaces`, which annotates each `Place` 
with whether it is reactive or not. This allows the annotation to survive 
LeaveSSA, which swaps out the identifiers of places but leaves other properties 
as-is. This version does _not_ yet handle control variables, but it's already 
more precise than our existing inference. In our current inference, if `x` is 
ever assigned a reactive value, then all `x`s are marked reactive. In our new 
inference, each instance of `x` (each Place) gets a separate flag based on 
whether x can actually be reactive at that point in the program. 

There are two main next steps (in follow-up PRs): 

* Update the mechanism by which we prune non-reactive dependencies from scopes. 

* Handle control variables. I think we may be able to use dominator trees to 
figure out the set of basic blocks whose reachability is gated by the control 
variables. This should clearly work for if/else and switch, as for loops i'm not 
sure but intuitively it seems right.
2023-11-01 17:13:02 -07:00
Joe Savona
748859a00e Add reactive flag to Place
This is part of a stack for inferring variables which are reactive via *control 
dependencies* as opposed to a data dependency. In compiler engineering, a 
statement S2 is control-dependent on statement S1 if S1 is in the post-dominance 
frontier of S2. Stated more intuitively: if S1 decides whether or not S2 is 
reached, then S1 is a control dependency of S2. 

As a start, we add `Place.reactive: boolean` so that individual places can track 
whether they are reactive or not. This lets us do fine-grained reactivity 
inference on the control-flow graph, even taking into account different SSA 
instances of a variable, so that we can say that a particular SSA version of `x` 
is reactive, while other "versions" of x (due to reassignment) are not.
2023-11-01 17:13:01 -07:00
Lauren Tan
5cdfa7cdc4 [feedback] Don't skip copying dist 2023-11-01 13:45:50 -04:00
Lauren Tan
c289d098d7 [babel] Rename useMemoCacheSource to enableUseMemoCachePolyfill 2023-11-01 13:28:18 -04:00
Lauren Tan
139336b87c [feedback] Specify react dep for react-forget-runtime 2023-11-01 13:12:09 -04:00
Lauren Tan
7042983f69 [feedback] Add changelog to build 2023-10-31 18:31:09 -04:00
Lauren Tan
8860faa65c [feedback] Add script to copy forget-feedback to new repo 2023-10-31 17:14:19 -04:00
Lauren Tan
776f2148b4 [feedback] More efficient userspace impl for uMC 2023-10-31 13:17:40 -04:00
Lauren Tan
a0de8e6d2d [babel] Disable ts declaration maps
This was unnecessary, switching it back to disabled
2023-10-31 13:06:19 -04:00
Lauren Tan
e4acd448f6 [feedback] Add script to bundle for oss 2023-10-31 12:42:39 -04:00
Lauren Tan
0a7bb7ac7a [babel] Clean up import logic a bit
Addressing feedback from #2218
2023-10-31 12:16:34 -04:00
Lauren Tan
847a6f0a09 [feedback] Make babel/types a dep not a peerDep 2023-10-31 12:16:33 -04:00
Lauren Tan
5ebc9fe388 [feedback] Add github action to run e2e test 2023-10-31 12:16:33 -04:00
Lauren Tan
3246dc641f [feedback] Add testapp with e2e test
Adds a toy next.js app which doesn't do anything interesting. It has a single 
e2e test which can be run via `npm run test:e2e`, which checks if a `$` variable 
was injected by the compiler, as a sanity check whenever we commit a new version 
of the compiler to the external repo.
2023-10-31 12:16:32 -04:00
Lauren Tan
ac170f4f0d [babel] Allow userspace useMemoCache
Not every consumer of Forget will be able to run an experimental version of 
React. In the meantime before useMemoCache is stable, provide a way for OSS to 
pass in a userspace impl.
2023-10-31 12:16:31 -04:00
Mofei Zhang
c7c57ec531 [playground] QoL use webpack to bundle monaco-editor's loader
--- 

This lets us load playground locally, entirely offline (e.g. on an airplane, 
when a cdn is down, at ReactAdvanced, etc) 

Before: 

See external script link 

<img width="691" alt="image" 
src="https://github.com/facebook/react-forget/assets/34200447/85500e86-c1a5-4dff-bdcd-58db0d6fd453"> 

Now: 

No external script 

<img width="476" alt="image" 
src="https://github.com/facebook/react-forget/assets/34200447/fdcbea09-0b5f-42e3-9310-35d8d1d726b7">
2023-10-30 14:53:00 -04:00
Mofei Zhang
16bef168c1 [playground] Move Editor/index for next PR (dynamic import) 2023-10-30 14:53:00 -04:00
Lauren Tan
0841caab8e [rollup] Include typescript definitions in build
Emit our TypeScript definitions as well in the build so things like options have 
typechecking
2023-10-30 13:58:03 -04:00
Lauren Tan
52afd8a566 [ext-preview] Inline @babel/generator
Turns out we were only using this in PrintHIR and in the logger, so it should be 
safe to inline and makes installing the plugin for external collaborators a 
little easier. 

I'm assuming inlining this is ok because we only need to defer to the host's 
Babel version(s) when it affects parsing or the final codegen output, hence why 
we continue to no-inline @babel/types – as it is responsible for creating AST 
nodes during codegen
2023-10-30 13:58:02 -04:00
Mofei Zhang
e2836eec5b [bugfix] do not hoist computed memberpaths in lambdas 2023-10-30 12:56:50 -04:00
Mofei Zhang
3e812df89c [repro] Test case for more variable naming collision bugs
--- 

bug repro from @JacksonGL 

(we currently bailout for references to `useMemoCache`, but not other variables 
that may collide with codegenned ones). 

``` 

% yarn sprout --filter 

yarn run v1.22.19 

$ node ../sprout/dist/main.js --filter 

FAIL  todo 

Failures: 

FAIL: todo 

Difference in forget and non-forget results. 

Expected result: { 

"kind": "ok", 

"value": "0", 

"logs": [ 

"'module_$'", 

"'module_t0'", 

"'module_c_0'" 

] 

} 

Found: { 

"kind": "ok", 

"value": "0", 

"logs": [ 

"[ 0, 0 ]", 

"0", 

"true" 

] 

} 

```
2023-10-30 12:01:24 -04:00
Sathya Gunasekaran
f648e4ce85 [test/react] Turn on react tests in CI 2023-10-25 12:39:20 +01:00
Sathya Gunasekaran
51baf5807e [test/react] Fix github workflow
The flattening broke the shell script because the directory structure changed. 

Instead of depending on a flaky shell script, this PR encodes the commands as 
part of the github workflow.
2023-10-25 12:36:55 +01:00
Lauren Tan
419310cd15 Bundle babel-plugin-react-forget with rollup
Attempt to bundle the plugin with rollup instead of just tsc. I'm intentionally 
not inlining babel related modules, as we should ideally rely on the host 
environment's version instead
2023-10-27 14:07:56 -04:00
Lauren Tan
473237ba1f Remove prettier from babel-plugin as dependency
We can now fully remove prettier from babel-plugin-react-forget. I moved it to 
devDependencies instead, to make the rollup build simpler and so we can continue 
to prettify our internal source code.
2023-10-27 14:07:56 -04:00
Lauren Tan
c72673bd8f [fixtures] Run prettier after babel-plugin
This moves prettier formatting into fixture-test-utils instead, so we can remove 
the dependency on prettier in the babel plugin. I want to do this because I 
don't want to include prettier in the rolledup artifact when we build the babel 
plugin.
2023-10-27 14:07:55 -04:00
Sathya Gunasekaran
ad1fe50546 [playground] Turn on Forget
Runs Forget on the playground, so we can dogfood Forget and experiment with 
improvements to UX. 

Opts out of compiling the layout page because thats run on the server which 
doesn't seem to have access to useMemoCache.
2023-10-23 15:41:01 +01:00
Sathya Gunasekaran
1138ef00e3 [playground] Force nextjs to use experimental react
These experimental features aren't used by the playground, but turning it on 
forces nextjs to use experimental react which has the useMemoCache hook.
2023-10-23 15:41:00 +01:00
Sathya Gunasekaran
48d465eb22 [playground] Upgrade to latest nextjs 2023-10-23 15:41:00 +01:00
Sathya Gunasekaran
23a226227f Remove use of prettier in babel-plugin-react-forget
This kept throwing warnings in our playground build because prettier uses node 
APIs and is not meant to be bundled and run on the browser. 

There's a separate standalone version that runs on the browser. A follow on PR 
will make the playground use that but this PR is a first step towards using a 
standalone prettier.
2023-10-25 15:40:31 +01:00
Lauren Tan
b049c14bb3 Repro of hoisting bug
Adds a test case reproducing an issue with hoisting. This seems like a case 
where hoisting was incorrectly applied as this example doesn't require it
2023-10-20 11:34:41 +01:00
Joe Savona
f8cee28f1d Retain minimal variable declarations in DCE
Currently DCE can remove variable declarations that are unused, ie where all 
control-flow paths to usage of the variable are overwritten by a reassignment. 
We then have to reconstruct the original variable declaration at the appropriate 
block scope during LeaveSSA, which is complex and can actually be incorrect in 
some cases. 

This PR updates to ensure that DCE will not remove the original variable 
declaration for any variable that is used (even in the case of always being 
reassigned before use). The main changes are: 

* DCE retains variable declarations, but if a variable declaration is always 
shadowed by reassignments then DCE will rewrite StoreLocal -> DeclareLocal so 
that it can DCE the unused initial value. 

* BuildHIR now has to change its handling for reassignment destructure 
instructions with nesting. Nesting uses a temporary which would appear as a 
declaration of a new variable, which is incompatible with other reassignments. 
See comments in the file. 

* LeaveSSA is quite a bit simpler now, since we never need to reconstruct a 
declaration.
2023-10-19 22:08:15 +01:00
Joe Savona
84da8994bd Test cases for reactive (control) dependencies
Adds test cases for all the cases of control dependencies that I can think of. 
We don't currently handle control dependencies correctly in any of these cases. 

There's also another test case which demonstrates why reactive dependency 
inference needs to be fixpoint, even for non-control dependencies.
2023-10-17 09:48:23 +01:00
Lauren Tan
4fc392e628 [qol] Add sprout to top level scripts 2023-10-16 16:05:08 +01:00
Lauren Tan
640994f49b [playground] Only log error to console when clicked 2023-10-16 15:54:33 +01:00
Lauren Tan
f5d1ff0a3c Update .gitignore for watchman-cookie 2023-10-16 15:54:30 +01:00
Joe Savona
fa2a67dd1d Feature flag to opt-in to emitting change variables 2023-10-12 16:26:42 -07:00
Joe Savona
28767a7afb Inline reactive scope dep checks; no more change vars
Reactive scopes are currently preceded by individual variable declarations, one 
for each of the scope's dependencies. Only after checking independently if each 
of these dependencies has changed do we then do an "or" to check if we should 
actually recompute the body of the scope. 

But that's wasteful: it's more efficient to rely on `||` short-circuiting, and 
recompute as soon as any input changes. 

The main reason I can see not to do this is debugging. Having the change 
variables makes it easy to see what changed. But at this point I think it makes 
sense to optimize for code size and performance; we can always add back a 
dev-only mode that uses change variables if that turns out to help debugging.
2023-10-12 12:03:42 -07:00
Joe Savona
4fe0ab41e3 Repro for unnecessary memoization of array filter 2023-10-12 12:03:38 -07:00
Joe Savona
87b1549d5c Improve MergeConsecutiveScopes
Rewrites the core logic of MergeConsecutiveScopes to be easier to follow and fix 
bugs. We now do a two-pass approach: 

* First we iterate block instructions to identify scopes which can be merged, 
without actually merging the instructions themselves. 

* Then we iterate again, copying instructions from the block either into the new 
output block, or into their merged scope, as appropriate. 

I think the simplicity here is worth the performance cost, and we can always 
revisit later as necessary.
2023-10-12 09:47:26 -07:00
Joe Savona
52c6a34da3 [be] Limit scope of temporaries in codegen
This morning @mofeiZ reminded me that our codegen doesn't really have any guards 
against reordering, since temporaries are lazily emitted. We're relying on the 
fact that our lowering and memoization carefully preserves order of evaluation, 
such that delaying the instructions in codegen doesn't change semantics. 

To help catch any mistakes with this, I had previously added code that reset the 
codegen context's temporaries before/after exiting a reactive scope. That 
ensured that temporaries from within the scope weren't accessible outside it. 
This PR extends that approach to _all_ blocks, so that temporaries created 
within a block aren't accessible outside it. 

I'm also going to explore more actively resetting temporaries after they 
"should" be used. There are a couple cases where temporaries are reused, though, 
which we have to change first.
2023-10-10 13:15:51 -07:00
Joe Savona
495fec19e7 [be] Update docblock comment for ConstantPropagation 2023-10-10 13:15:46 -07:00
Joe Savona
a92bfd7fdc [be] Factor out named type for JSXText instr variant 2023-10-10 13:15:42 -07:00
Joe Savona
ba23b990ac [be] Remove dead code in ConstantPropagation 2023-10-10 13:15:39 -07:00
Joe Savona
5a8c7594c5 Implement JSX whitespace rules
So far we've been preserving JSX whitespace all the way through to codegen. But 
JSX has clear rules around whitespace handling, which allows us to trim 
whitespace in the input in lots of cases. For the most part this doesn't change 
our output, but I think that’s generally because of prettier. This PR should 
make a big difference when debugging the compiler, by removing all the 
whitespace JsxText values. 

But in some edge-cases it really makes a difference in the output since we can 
avoid memo slots for strings like `"\n      "`.. 

## Test Plan 

* Experimented with our internal tool to verify transform output to confirm that 
JSXText whitespace does not impact fbt transform results. 

* Synced and tested profile page, looks fine
2023-10-09 16:35:47 -07:00
Joe Savona
b61dba64ad Enable MergeConsecutiveScopes by default
## Test Plan (wip) 

Sync internally, manually verify code changes and test products.
2023-10-09 16:15:30 -07:00
Joe Savona
a1461016df Improve MergeConsecutiveScopes
Rewrites the core logic of MergeConsecutiveScopes to be easier to follow and fix 
bugs. We now do a two-pass approach: 

* First we iterate block instructions to identify scopes which can be merged, 
without actually merging the instructions themselves. 

* Then we iterate again, copying instructions from the block either into the new 
output block, or into their merged scope, as appropriate. 

I think the simplicity here is worth the performance cost, and we can always 
revisit later as necessary.
2023-10-09 16:15:30 -07:00
Joe Savona
49d16ba7e5 Repro for scope merging bug
This is a repro for a bug that occurs when `enableMergeConsecutiveScopes` is 
enabled. Reminder that this feature is off by default and not enabled yet 
internally. 

I found this via #2121, where eliminating extraneous whitespace JSXText 
instructions meant that MergeConsecutiveScopes started merging a fixture 
differently, revealing a bug. This PR reproduces that case by keeping the 
identical structure, but using plain objects to represent the JSX elements 
instead of JSX syntax.
2023-10-09 16:15:29 -07:00
Joe Savona
529568c7db [be] Rename InlineUseMemo imports 2023-10-09 16:07:09 -07:00
Joe Savona
428c47581e Generalize InlineUseMemo to inline IIFEs
This PR completes the refactor. We now do the following sequence: 

* ValidateUseMemo. This is a new pass that extracts just the validation logic 
from the existing InlineUseMemo. This was always being run before, so this pass 
also always runs. 

* DropManualMemoization. As before, this converts useMemo calls into an IIFE 
(immediately invoked function expression). 

* InlineImmediatelyInvokedFunctionExpressions (prev InlineUseMemo). This pass 
now inlines _all_ IIFEs, including both useMemo calls that were dropped as well 
as IIFEs that the user wrote. 

The motivation for this change is that some codebases use IIFEs as a workaround 
for lack of if expressions, but we're unable to optimize within function 
expressions. This is the reason we originally added inlining for useMemo, but 
given that IIFEs are common it makes sense to generalize the inlining. 

## Test Plan 

* Manually checked changes in output 

* Synced internally and tested on profile page, no issues observed. Also 
spot-checked some of the changes in ouput and it looks as expected.
2023-10-09 15:56:10 -07:00
Joe Savona
444a1ad9c1 Reorder InlineUseMemo after type inference
The goal of this stack is to generalize `InlineUseMemo` into a pass that inlines 
all immediately invoked function expressions (IIFEs). Rather than specialize 
just useMemo calls, we'll rely on DropManualMemoization running first and 
turning useMemo calls into IIFEs. Then the generalized inlining pass can handle 
those IIFEs as well as others present in the source. 

For now, moving the order of the pass makes the output closer to what it will 
eventually be after this stack is complete.
2023-10-09 15:25:18 -07:00
Joe Savona
74c6c992ba [be] s/DropUseMemo/DropManualMemoization/
Renames this pass to clarify that it's dropping all manual memoization (within 
components), including both useMemo and useCallback.
2023-10-09 15:25:17 -07:00
Joe Savona
683a197a89 Manually revert #2127 (allow mutating context in callbacks)
#2127 introduced a special type for the result of `useContext()` that was sort 
of ref-like. The intent was to allow code like this: 

``` 

function Foo() { 

const cx = useContext(...); 

function onEvent() { 

cx.foo = true; 

}; 

return <Bar onEvent={onEvent} />; 

} 

``` 

However, that code actually is allowed by the compiler by default. It's only a 
bailout when `@validateFrozenLambdas` is enabled. The "fix" in #2127 therefore 
wasn't strictly necessary to unblock rollout, and it's also flawed in a few 
ways: 

* First, `useContext(FooContext)` should have equivalent behavior to a custom 
hooks which does the same thing, ie `function useFooContext() { return 
useContext(FooContext) }`. Specializing the type of useContext makes the 
behavior different. 

* Second, it meant that even readonly accesses of the context inside a callback 
marked the function as capturing, which in turn prevented those callbacks from 
being memoized. 

So i'm reverting this and we'll have to think a bit more about this case.
2023-10-09 15:21:12 -07:00
Mofei Zhang
b55cf78a4d [syntax] Lower empty jsx attributes to true
--- 

Found when enabling Forget for webamp 

``` 

% yarn sprout --filter --verbose 

yarn run v1.22.19 

$ node ../sprout/dist/main.js --filter --verbose 

PASS  jsx-attribute-default-to-true 

ok <div>{"truthyAttribute":true}</div> 

1 Tests, 1 Passed, 0 Failed 

  Done in 2.45s. 

```
2023-10-06 17:38:02 -04:00
Mofei Zhang
392d0b127b [patch] Patch import specifiers check for react
--- 

Patch for original patch of injecting `useMemoCache` logic 😅 This is failing on 
a good number React components internally (not in our current experiments)
2023-10-06 17:38:00 -04:00
Mofei Zhang
fd87c9b9e1 [patch] Bug in deriveMinimalDeps
Found when enabling Forget on Webamp
2023-10-06 17:37:57 -04:00
Joe Savona
2b417e2a52 Playground uses new pragma parser, shows config being used
The playground now uses the new pragma parser so it's guaranteed to use the 
right defaults and have consistent parsing with snap/sprout. In addition, we now 
emit a debug event from the compiler which contains pretty-printed environment 
config, making it easy to check which settings are being applied in playground. 

<img width="3008" alt="Screenshot 2023-10-05 at 11 05 58 AM" 
src="https://github.com/facebook/react-forget/assets/6425824/2417f40c-1320-4c39-a661-a4e34e3d69c4">
2023-10-05 11:06:56 -07:00
Joe Savona
ed9ea81e4a Snap/sprout use new pragma parser and compiler defaults
Updates Snap and Sprout to use the new pragma parser, which also means they will 
always use the same default flags as the compiler itself sets. A side benefit of 
this is that you no longer need to rebuild snap/sprout to update their flags, 
since they will take flags from the version of the compiler being executed.
2023-10-05 10:57:20 -07:00
Joe Savona
cadcf3de2d Reusable function for parsing config strings
Adds a helper function for parsing pragma strings to the compiler itself, and 
exports it. This will be used in follow-ups to make Snap, Sprout, and Playground 
all use the same pragma parser. The helper also starts from the default values, 
so adopting this will also make it easy for all those places to have the same 
defaults automatically.
2023-10-05 10:18:24 -07:00
Joe Savona
80d867d1bd Promote inlineUseMemo to stable (remove flag)
Per title, this feature flag is enabled everywhere and is clearly stable, let's 
promote to stable and remove the flag to simplify.
2023-10-05 09:20:19 -07:00
Joe Savona
3fdb237682 Promote enableNoAliasOptimizations to stable (remove flag)
Per title, this feature flag is enabled everywhere and is stable enough, let's 
promote to stable and remove the flag to simplify.
2023-10-05 09:20:14 -07:00
Joe Savona
11d03ffbe0 Promote enableFunctionCallSignatureOptimizations to stable (remove flag)
Per title, this feature flag is enabled everywhere and is clearly stable, let's 
promote to stable and remove the flag to simplify.
2023-10-05 09:20:09 -07:00
Joe Savona
0b0f8994ba Promote enableTreatHooksAsFunctions to stable (remove flag)
Per title, this feature flag is enabled everywhere and is clearly stable, let's 
promote to stable and remove the flag to simplify.
2023-10-05 09:20:05 -07:00
Joe Savona
8e8ae96478 Support empty catch clause
Adds support for empty catch clauses in a try/catch. We add a new block kind 
`catch` which prevents the empty catch block from being merged with other types 
of blocks, preserving the block structure within the HIR and allowing us to 
reconstruct the empty catch.
2023-10-04 12:24:19 -07:00
Mofei Zhang
1ec1a0ceb8 [fix] JSXElement identifiers now included in lambda capture deps
--- 

`gatherCapturedDeps` previously did not visit JSXElements, so Forget did not 
read any local JSX identifiers as dependencies (in lambdas)
2023-10-04 14:01:34 -04:00
Mofei Zhang
3f8831fb5c [snap][QoL] Pragma for implicit debug mode
--- 

Implements popular feature request  per feedback from a majority of snap users. 

**Add `@debug` to the first line of your `testfilter.txt` file to opt into 
implicit debug mode**, in which debug logging is enabled anytime filter mode is 
on + only one fixture file is found. 

- live edits to testfilter.txt are reflected in watch mode, so you can add / 
remove `@debug` to `testfilter.txt` in the middle of a watch session 

- I personally don't use debug mode all the time (I often a single file filtered 
+ a lot of console log traces), but it should be easy to add `@debug` to the top 
of `testfilter.txt` and leave it there forever.
2023-10-04 14:01:33 -04:00
Mofei Zhang
05d620521b [ez] missed TODO bailout for holey arrays
--- 

Missed this when adding #2114 (only found when running thousands of fb 
components through forget and noticing that holey arrays still fail)
2023-10-04 14:01:33 -04:00
Joe Savona
91dab8e19c [be] Dont copy default config values if key is present 2023-10-03 16:57:00 -07:00
Joe Savona
b96dd26be1 [qol] Improve printing of ReactiveFunction
This is a (hopefully) better approach at printing ReactiveFunction. The nesting 
wasn't always clear in the previous version, this should help. See playground to 
experiment.
2023-10-03 16:56:59 -07:00
Joe Savona
870a378ebb [be] Rename EnvironmentConfig type after refactor 2023-10-03 15:01:55 -07:00
Joe Savona
44a6a602a7 [be] Update feature defaults to reflect usage
Updates feature flag default values to match the flags we have turned on 
internally for some time w/o issues.
2023-10-03 14:49:58 -07:00
Joe Savona
34890556ec [be] Improve feature flags setup
Updates `Environment` to store all feature flags on a single `config` object. We 
now also define an object with all the default config values, and use this to 
populate defaults for any missing values in the user-provided config.
2023-10-03 13:58:05 -07:00
Joe Savona
9fc5e55b56 [be] Access all features flags through Environment
Most of our feature flags are accessed via the `Environment`, but a few cases 
have slipped in where we look at the `config` object directly. The problem is 
that the config object doesn't set defaults, so the check is effectively 
encoding what the default is. 

This PR moves to always accessing flags off of the environment, and adds a few 
flags that weren't yet defined there.
2023-10-03 11:46:33 -07:00
Sathya Gunasekaran
0f97ea4c71 [sprout] Make mutate incrementally attempt to add property
Previously, we stored a global count variable that was updated every time we 
added a property to the `arg` object. This was added to prevent collisions, and 
make sure we do actually mutate the object. 

But the count value was shared by the forget compiled and uncompiled versions, 
so the same object mutated in either versions would result in having different 
properties leading to potential test failures. 

Instead, let's make count local and attempt to incrementally mutate the object 
with different keys.
2023-10-04 12:36:04 +05:30
Sathya Gunasekaran
3aaf8be25c [hir] Use a stable identity for undefined value
InferReferenceEffects uses object identity to merge states, which breaks when we 
create a new object to model `undefined`. 

Two value objects representing `undefined` are not equal due to referential 
equality. 

Instead, let's use a singleton to represent `undefined` value.
2023-10-04 12:11:35 +05:30
Joe Savona
b7a14ecc8d Detect unknown hooks on React namespace
Handles an edge-case from earlier in the stack. When looking up a property on a 
shape, if the property is defined we return it. But if it isn't defined, and the 
property name is a hook, we treat it like a default custom hook.
2023-10-03 09:00:11 -07:00
Joe Savona
a2dd376820 Drop useMemo/useCallback when called via React namespace
Teaches `DropUseMemo` (which also handles useCallback) to also transform the 
methodcall case.
2023-10-03 08:47:07 -07:00
Joe Savona
989eaafa43 Support hooks/methods on the React namespace
Allows using hooks/methods off of the `React` namespace, for example 
`React.useState(sathya)`. Thanks to the previous PR we correctly handle things 
like validation of hooks called via propertyload syntax. The main change here is 
to teach the compiler about the `React` namespace. This is a bit of a hack since 
we treat it as a global, but we're transforming React code so this seems 
reasonable (?). 

There are a few additional touch-ups which I'll do in subsequent PRs to make 
review easier. For example, we need to teach our useMemo/useCallback flattening 
logic to also handle the case of `React.useMemo()` etc.
2023-10-03 08:47:06 -07:00
Joe Savona
b3d5e92667 Support/validate hooks called as methods
Hooks can be called via method call syntax, eg `Foo.useBar(sathya)`. This PR 
teaches the compiler about this form of hooks for things like flattening scopes 
with hooks, validating conditional hooks, etc. 

Note that we still disallow calls on the React namespace, so things like 
`React.useState()` continue to error. That's the next PR in the stack!
2023-10-03 08:47:05 -07:00
Joe Savona
216696dd88 Allow context values to be mutated in callbacks
Adds a new type for representing context values, which is transitive. So 
`useContext(a).b.c` also gets inferred as a context type. This allows us to 
refine our inference, and allow passing callbacks that modify context where a 
"frozen" lambda is exepcted.
2023-10-03 08:47:04 -07:00
Joe Savona
41cb3c722e Fix duplicate declaration from MergeConsecutiveScopes
This is a distilled version of the duplicate declaration @mofeiZ and I saw when 
trying to sync latest Forget internally, plus a fix to avoid the duplicate 
instruction.
2023-10-02 16:40:33 -07:00
Joe Savona
4779fa0f81 Wrap MergeConsecutiveScopes in a feature flag
I should have done this originally, but figured if there were issues on the sync 
we could just do it then. Here we are!
2023-10-02 15:46:04 -07:00
Joe Savona
e54e2e6ee4 Distinguish JSXText/StringLiteral during codegen
Fixes an issue with incorrect spacing where spaces were getting dropped, despite 
an explicit `{" "}` in the input. The issue is that we didn't maintain JSXText 
all the way through compilation. BuildHIR distinguishes string literals (such as 
the above, inside an expressioncontainer) from JSXText, and we propagate this 
distinction all the way through to codegen. 

But then codegen stores temporary values as `t.Expression` nodes, which means we 
have to convert the JSXText nodes to StringLiteral and we lose the distinction. 
This PR updates codegen to save temporaries as `t.Expression | t.JSXText` so 
that we can preserve the difference. In most places we just coerce the value to 
an expression, but the code for emitting JSX child items looks at the raw value 
so it can distinguish them. JSXText is emitted as-is, while StringLiterals are 
always wrapped in an expression container. 

See the new test case which demonstrates the expression being preserved.
2023-10-02 14:58:05 -07:00
Sathya Gunasekaran
e49a62c15d [hir] Do not memoize object methods separately
Object methods must not be cached independently, so this PR flattens the 
reactive scope to prevent memoization. 

In the future, we can combine the FlattenScopesWithObjectMethods and 
FlattedScopesWithHooks passes by making them more modular. But this works for 
now.
2023-10-02 20:22:49 +01:00
Sathya Gunasekaran
f8996476ff [hir] Wire up object method lowering and codegen
Object methods are lowered to functions and added to ObjectExpression. The 
codegen is interesting because we shouldn't emit code that lowers the object 
method into a separate statement and then stores it into an object expression. 

An shorthand object method has different semantics than an object method using 
the function syntax, so we need to preserve the shorthand object method syntax 
in the generated code. 

To do this, we don't immediately generate an AST node for the ObjectMethod but 
instead store it in a side table during codegen. Only when emitting code for an 
ObjectExpression, we lookup this side table and emit the object method inline in 
the body.
2023-10-02 19:26:00 +01:00
Joe Savona
b61cfc01a5 Support rest params
Adds support for lowering rest element parameters to spreads. We eagerly create 
a temporary, similar to the approach for destructuring. In theory we could do 
something more optimal if you have a `...foo` (rest element where the argument 
is an Identifier) but it doesn't seem worth optimizing yet.
2023-09-27 14:04:02 -04:00
Joe Savona
be7f98d584 Scaffolding for rest params
Updates HIR to represent rest element function parameters. We don't construct 
these yet.
2023-09-27 14:04:02 -04:00
Joe Savona
687ad02e43 Dont merge if scope output is not guaranteed to change
As noted earlier in the stack and in chat, there are some cases where the output 
of a reactive scope is not guaranteed to change just because its inputs did. 
Consider a function `foo(x) { return x < 10 }`. If x was 0 and changes to 1, the 
result of `foo(x)` won't change. 

For code such as `[foo(x)]`, then, merging the scope for `t0 = foo(x)` and `t1 = 
[t0]` into a single `t1 = [foo(x)]` could cause us to invalidate `t1` 
unnecessarily. For example, x changing from 0 to 1 would allocate a new array of 
`[true]` even though the value didn't meaningfully change. This is the second 
category of merging, where we merge scopes A and B if the outputs of A are the 
inputs to B. 

With this change, we only do this type of merge if the outputs of the first 
scope are known to invalidate whenever the input does. We're conservative about 
this, and only consider function expressions, arrays, object, and jsx to always 
invalidate. Function calls _may_ invalidate, but as w the `foo()` example here 
they also may not. 

Note that this is purely about optimization and not correctness. We could always 
merge in this case (per earlier in the stack) but that might invalidate more 
often than we would like.
2023-09-27 14:04:01 -04:00
Joe Savona
66841aa9ac Prune declarations not used after merging
This is the optimization mentioned in #2113. When we merge scopes, often the 
declarations from the first scope become unnecessary, since those values are 
only consumed by the subsequent, now-merged scope. There's no point emitting 
those values as outputs of the merged scope since no one can consume them. 

Thanks to the logic in #2116 we now know the last place each identifier is used. 
We use that again here, to prune declarations that aren't used past the end of 
the merged scope. This is a pretty dramatic win on cache slots used.
2023-09-27 14:04:00 -04:00
Joe Savona
d1355179d7 Improved check for intermediate values for merged scopes
We can't merge scopes if the intervening instructions produce values that are 
used later on. This PR improves the mechanism for detecting this case: first we 
build up a mapping of the last time (max instruction id) each identifier is 
used. Then when we're about to merge scopes we check if all the intervening 
lvalues are last used at or before the scope. If so that means it's safe to 
merge. 

I can't think of any edge cases that are problematic in the old behavior, but 
this version is more trivially correct and should allow us to extend to other 
types of instructions more easily.
2023-09-27 14:04:00 -04:00
Joe Savona
959c6685be Merge consecutive reactive scopes that invalidate together
This is something we've wanted to do for a while, and which @sophiebits also 
brought up. The idea is to merge consecutive reactive scopes that will always 
invalidate together. There are two cases of this: 

* Both scopes have the exact same inputs 

* Or the inputs of the second scope are the outputs of the first 

In both these cases it's pure overhead to keep the scopes separate. In the first 
case where both scopes have the same inputs, merging allows us to check the 
inputs once instead of 2+ times. In the second case, we know that the second 
scope will invalidate when the first does so it's wasteful to recheck the 
outputs of the first scope for changes. 

This is already cutting down on memoization quite a bit in the fixtures. Note 
that there's an additional optimization we can do after merging the second 
category, which is to remove the first scope's declarations if they were only 
referenced by the second scope. I'll add that in a follow-up.
2023-09-27 14:03:59 -04:00
Mofei Zhang
7b58600fb9 [tests] Repros for bugs and todos 2023-09-21 22:38:40 -04:00
Mofei Zhang
b9275f65bc [ez] Add TODO bailout on non-backward compatible holey arrays
--- 

(pasted from comment) 

Instead of handling holey arrays, bail out with a TODO error. 

Older versions of babel seem to have inconsistent handling of holey arrays, at 
least when paired with HermesParser. When using these versions, we should bail 
out instead of throwing a Babel validation error. 

Issue: 

The babel ast definition for array elements changed from Array<PatternLike> to 
Array<PatternLike | null>. Older versions do not expect null in the ArrayPattern 
ast and will throw a validation error during Codegen. 

- HermesParser will parse [, b] into [NodePath<null>, NodePath<Identifier>] 

- Forget will try to preserve this holey array when we codegen back to js 

(e.g. we call a babel builder function arrayPattern([null, identifier])) 

- Babel will fail with `TypeError: Property elements[0] of ArrayPattern    
expected node to be of a type ["PatternLike"] but instead got null` 

PR that changed the AST definition: 


https://github.com/babel/babel/pull/10917/files#diff-19b555d2f3904c206af406540d9df200b1e16befedb83ff39ebfcbd876f7fa8aL52-R56 

[ez] Add TODO bailout on non-backward compatible holey arrays 

--- 

(pasted from comment) 

Instead of handling holey arrays, bail out with a TODO error. 

Older versions of babel seem to have inconsistent handling of holey arrays, at 
least when paired with HermesParser. When using these versions, we should bail 
out instead of throwing a Babel validation error. 

Issue: 

The babel ast definition for array elements changed from Array<PatternLike> to 
Array<PatternLike | null>. Older versions do not expect null in the ArrayPattern 
ast and will throw a validation error during Codegen. 

- HermesParser will parse [, b] into [NodePath<null>, NodePath<Identifier>] 

- Forget will try to preserve this holey array when we codegen back to js 

(e.g. we call a babel builder function arrayPattern([null, identifier])) 

- Babel will fail with `TypeError: Property elements[0] of ArrayPattern    
expected node to be of a type ["PatternLike"] but instead got null` 

PR that changed the AST definition: 


https://github.com/babel/babel/pull/10917/files#diff-19b555d2f3904c206af406540d9df200b1e16befedb83ff39ebfcbd876f7fa8aL52-R56
2023-09-21 20:54:58 -04:00
Lauren Tan
8b679e9138 [ez] Fix prettier not formatting test fixtures in vscode
This was super annoying. Turns out we had a top level directory for old projects 
called fixtures that shared a name with our fixture directories.
2023-09-21 17:04:25 -04:00
Lauren Tan
1ce952e687 Support hoisting const variable declarations
This PR adds preliminary support for hoisting const variable declarations. We do 
this via BuildHIR when lowering top level statements in a BlockStatement, by 
first checking which bindings are in scope to be hoistable if referenced before 
they are declared. The declarations are then hoisted to their earliest point 
where they are referenced (ie the top level statement just before) as context 
variables. 

Later, prior to codegen, we restore the original source by removing the 
DeclareContexts and transforming their associated StoreContexts back. 

Support for hoisting other kinds of declarations will come in future PRs!
2023-09-21 17:00:04 -04:00
Mofei Zhang
36f979d9d4 [patch] Repro and fix for duplicate import statements 2023-09-19 18:34:14 -04:00
Joe Savona
2fa04d128e Transitive mixed type, take 2
This is a redo of #1640 now that we've established the necessary infrastructure, 
most notably `Effect.ConditionallyMutate` and `noAlias` from #2103 earlier in 
this stack. We can now understand the semantics of hooks that return deeply 
readonly values composed of primitives, arrays, or objects such that any 
`.map()` or `.filter()` calls are guaranteed to be the corresponding array 
methods. That further allows us to refine, since we know that the lambdas passed 
to these calls can't alias, are conditionally mutable, etc. All in all this 
should let us memoize less in practice.
2023-09-18 16:42:25 -07:00
Joe Savona
32569d2113 noAlias support for CallExpr incl hooks
Adds `noAlias` support for CallExpression, including hooks. Note that we treat 
hook arguments as escaping by default — ie we assume that they don't just flow 
into the hook return value, but are just outright escape points equivalent to a 
return. A `noAlias` annotation on a hook definition disables both: this will 
allow us to avoid memoizing the `graphql` tag arguments to `useFragment`, for 
example.
2023-09-18 14:38:26 -07:00
Joe Savona
0810175bba More tests for noAlias
Adds another test for `noAlias` to show that if part of the receiver could 
escape that we still memoize it.
2023-09-18 14:25:12 -07:00
Joe Savona
b02f5a5e0e Dont compile code w useMemoCache
Skips compilation of code that has a reference to `useMemoCache()`, as a 
last-resort to avoid double-compilation of code. This is meant as a quick way to 
unblock since we're still seeing some double compilation issues when syncing 
internally.
2023-09-18 09:46:04 -07:00
Joe Savona
1879fbe644 Option to not memoize non-aliased function parameters
This has been nagging at me for a _long_ time: we unnecessarily memoize function 
callbacks passed to things like Array.prototype.map, even though we know these 
functions can't escape. This PR fixes this as follows: 

* Adds a `noAlias?: boolean` flag to builtin function signatures, defaulting to 
false if not specified. 

* Adds a feature flag, `enableNoAliasOptimizations`, to gate optimizations based 
on the value of that new flag. 

* When the feature is enabled, `PruneNonEscapingScopes` now looks up the 
signature of method calls, and avoids memoizing the arguments if the signatures 
specifies `noAlias: true`. 

* Annotates Array.prototype.map and Array.prototype.filter as `noAlias`. 

This does not mean we'll never memoize arguments to Array.prototype.map, it just 
means that the argument itself won't be considered as escaping. If the function 
still escapes by some other means it will get memoized: 

``` 

function Component(props) { 

const f = () => {}; // memoized! 

const x = [].map(f); // not from here.. 

return [x, f]; // but bc it escapes here 

} 

``` 

Note: this delivers some of the wins from #1640. That PR tried to do a bunch of 
things, part of which I already landed w the introduction of 
ConditionallyMutate, which allowed us to type Array.prototype.map. This PR 
further gives us the ability to understand functions that don't alias their 
params at all. The remaining bit from #1640 is the idea of understanding that 
key hooks such as `useFragment()` return transitively readonly, transitively 
array/object/primitive values, and any `.map()` or `.filter()` calls must be on 
arrays, allowing us to optimize them. Without that extra step, we'll still have 
to memoize a lot of `array.map()` lambdas just because we aren't sure that the 
receiver is an Array. But this PR helps with some cases, and lays the groundwork 
for the rest of that PR.
2023-09-15 16:18:03 -07:00
Joe Savona
bde7875f20 fbt:param does not allow jsxfragment children 2023-09-15 12:10:51 -07:00
Joe Savona
dc4e63e2d5 fbt:param does not allow jsxtext children
Per the title, `<fbt:param>0</fbt:param>` is invalid FBT, you must wrap the text 
in an expression container. But that's not all, `fbt:param` can only have a 
single child, which means we have to strip out the text elements that occur from 
the whitespace in the source.
2023-09-15 11:30:01 -07:00
Joe Savona
e5e71dfd59 Memoize fbt children in same scope
Fixes another special-case rule of fbt that i wasn't aware of: apparently 
`<fbt:param>` elements don't have to appear as direct children of `<fbt>`, they 
can be nested, and in this case they must appear as direct children of the fbt 
and not via an identifier indirection. This PR recursively extends the scope of 
FBT operands to make this work.
2023-09-15 10:31:55 -07:00
Sathya Gunasekaran
35fb6e05e1 [hir] Refactor function lowering from lowerFunctionExpression
Split out the HIR generation bits from the lowering of the actual function. The 
lowering will get reused when we lower object methods.
2023-09-15 17:16:05 +01:00
Sathya Gunasekaran
b26cc58070 [hir] Add a "type" field to ObjectProperty
This will be used to distinguish between a property and a method.
2023-09-15 17:16:05 +01:00
Sathya Gunasekaran
88f65d44a7 [hir] Refactor out LoweredFunction from FunctionExpression 2023-09-15 17:16:04 +01:00
Sathya Gunasekaran
3bda9f78c3 [hir] Introduce ObjectMethod type
This will be used to store fields representing an object method.
2023-09-15 16:28:44 +01:00
Sathya Gunasekaran
365a88a8aa [hir] Refactor out ObjectPropertyKey
The type of ObjectProperty is specific to the key, not the ObjectProperty. In 
the future, we want to add a type to represent the value of the ObjectProperty. 

This PR moves out the fields related to the key of the ObjectProperty to a 
separate type.
2023-09-15 16:08:41 +01:00
Mofei Zhang
00e9888fb8 [ez][patch] Remove calls to NodePath.hasNode
--- 

Recent commits added calls to `NodePath.hasNode`, which does not exist in babel 
v7.1.6 (internal). Forget is failing with this error. 

```js 

TypeError: handlerPath.hasNode is not a function 

at lowerStatement (.../babel-plugin-react-forget/HIR/BuildHIR.js:924:58) 

... 

```
2023-09-15 13:55:33 +01:00
Joe Savona
c70e87f535 Wrap JSXElement in expr container (for JSXAttribute) 2023-09-14 21:48:04 -07:00
Joe Savona
14630c0618 Dont compile functions w multiple args (infer mode) 2023-09-14 16:22:06 -07:00
Joe Savona
ca1217be6d Wrap JSXFragment in expr container for attributes
@mofeiZ noticed that some configurations of prettier don't support JSXFragment 
appearing as a JSXAttribute value, in violation of the spec. Coincidentally I 
noticed that our internal build system also fails on this as i was trying to 
roll out on more surfaces. This PR makes sure we wrap fragments in an 
expressioncontainer if they appear as jsxattribute values.
2023-09-14 16:22:05 -07:00
Joe Savona
88b1a069c0 Add examples for phi type inference
Adds test cases that would demonstrate different output if we were to update 
InferTypes to infer the types of phi identifiers when their operands have the 
same type. We currently do infer such types, but attach them to phi.type instead 
of phi.id.type, so the type doesn't effect inference. Fixing that would cause 
the output on these examples to change — however, per the discussion on #2079, 
we'd also incorrectly set the mutable ranges in some cases and cause incorrect 
compilation. 

I did a thorough review of InferReferenceEffects and can't figure out how to 
trigger the bug in our current implementation — the bug only kicks in when phi 
operands would be mutated as a store instead of a mutate, and that cannot happen 
given our currently imprecise types on phi identifiers.
2023-09-14 11:56:09 -07:00
Mofei Zhang
f993f0c2a5 [pipeline] panicOnBailout -> panicThreshold; remove isDev logging
--- 

Changed `panicOnBailout: boolean` to `panicThreshold`, which has the following 
options. Note that `ALL_ERRORS` corresponds to `panicOnBailout = true` and 
`CRITICAL_ERRORS` corresponds to `panicOnBailout = false`. `NONE` is a new 
option. 

```js 

export type PanicThresholdOptions = 

// Bail out of compilation on all errors by throwing an exception. 

| "ALL_ERRORS" 

// Bail out of compilation only on critical or unrecognized errors. 

// Instead, silently skip the erroring function. 

| "CRITICAL_ERRORS" 

// Never bail out of compilation. Instead, silently skip the erroring 

// function or file. 

| "NONE"; 

``` 

Jest seems to run babel through a different pipeline than Metro and - 
(perhaps through its complex `require` interjection logic). When running jest 
tests, exceptions thrown by babel transforms will bubble up to the nearest 
exception boundary which is often the jest test itself. This may not be a useful 
signal to anyone running a jest test with Forget enabled, as the erroring code 
may be within Forget itself or a transitively required module. 

Another reason to immediately bailing out on critical errors is that we may want 
to record errors found in the rest of the file. 

--- 

I'm not convinced that this change makes sense. A counterargument is that any 
CriticalErrors *should* be reported as parse errors, regardless of the runtime 
mode. Anyhow, this would be useful long term for our static analysis scripts 
(e.g. collecting info on bailouts and compilation info e.g. # slots used for 
JSX) as we want to compile-as-much-as-possible in those.
2023-09-14 20:00:43 +01:00
Mofei Zhang
b2230f62a3 [pipeline] Make logging more structured
--- 

Add types for logged events, including a `CompileSuccess` event which can help 
us record successfully compiled Forget functions and their compilation details 
(e.g. # memoSlots used).
2023-09-14 19:40:39 +01:00
Mofei Zhang
869466bb0b Revamp compilation modes
We currently have multiple flags for targeting which functions to compile, but 
they are actually mutually exclusive. This PR consolidates to a single 
`compilationMode: 'annotation' | 'infer' | 'all'` flag: 

* Annotation compiles only functions that explicitly opt-in with "use forget" 

* Infer compiles explicitly opted-in functions (via "use forget") as well as any 
known/inferred components or hooks: 

* Component declarations 

* Component or hook-like functions (same rules as the ESLint plugin but with an 
extra check for whether it uses JSX or calls a hook) 

* All compiles all top-level functions. We should get rid of this in a follow-up 
and make tests use infer mode by default, and add explicit opt-ins where 
necessary. 

In all modes, "use no forget" always takes precedence and can be used to 
opt-out. The default mode is now "infer".
2023-09-14 19:40:39 +01:00
Mofei Zhang
b3730eb496 [ez][be] Make early return explicit in compileAndInsert 2023-09-14 19:40:38 +01:00
Sathya Gunasekaran
88186db690 [hir] Make hoisting a TODO bailout 2023-09-13 17:58:58 +01:00
Joe Savona
d2f46b1437 Delete now-unused mermaid visualization 2023-09-11 16:58:22 -07:00
Joe Savona
ade3235ca3 ForInStatement: specialize inference/types
Replaces the use of `NextIterableOf` in for-in with a new `NextPropertyOf` 
instruction. The key distinction is `for-of` invokes an arbitrary iterator, 
which means a) each iteration may mutate the collection being iterated and b) 
the returned value may be mutable. However, `for-in` invokes a language-level 
mechanism to iterate: simply iterating alone _cannot_ modify the collection, and 
the returned value is known to be a primitive.
2023-09-11 16:54:05 -07:00
Joe Savona
51497c8c59 Basic ForInStatement support
First pass of ForInStatement support. This mostly just copies our handling of 
ForOfStatement, but the next PR updates to use a different instruction instead 
of `NextIterableOf`.
2023-09-11 15:56:49 -07:00
Joe Savona
911c988453 Support ObjectPattern within RestElement
Supports ObjectPattern within RestElement, the `...{y}` in `const [x, ...{y}] = 
z`
2023-09-11 13:03:02 -07:00
Joe Savona
1ec445d8f9 Support reordering call expressions
Supports call expressions if the callee and args are themselves reorderable. As 
part of this I realized that we currently allow identifier references to be 
reordered. To be safe, this PR updates the logic to continue consider 
identifiers as reorderable, but considers an arrow function to be not 
reorderable if it contains a local variable reference. We can likely relax that 
rule, but this quickly unblocks the next experiment.
2023-09-11 12:49:47 -07:00
Joe Savona
b1f7ebfd47 Reodering of unary and arrow function expressions
Supports reordering of unary and arrow function expressions: 

* Supports a trivially safe subset of unary operators, rejects things like 
`void` just because we don't need it yet. 

* Supports arrow function expressions that are either an empty block statement 
or a single expression which is itself reorderable.
2023-09-11 12:49:47 -07:00
Joe Savona
de2b684bc5 TryStatement: handle edge case control flows
Adds handling for some cases where the handler is unreachable (or is provably 
unreachable after analysis & optimization), where the try/catch can be flattened 
away: 

* The try block is empty. Nothing can throw, so the handler is unreachable. 

* The try block will always return. It can't return anything interesting (ie the 
result of a function call or variable load) since those could throw, but a try 
block that always return a primitive means the handler is unreachable. 

* The same, except where we only determine that the try block always returns via 
constant propagation.
2023-09-08 13:54:45 -07:00
Joe Savona
c8cebdd946 TryStatement: handle mutation of try values via catch param 2023-09-08 11:03:55 -07:00
Joe Savona
edd9c52142 Sprout tests to double-check our destructuring
I wanted to double-check the semantics of when default values are used, so i 
wrote out some fixtures with sprout enabled.
2023-09-07 17:17:03 -07:00
Joe Savona
e8d130bd1d Support type casts and conditionals as default values 2023-09-07 17:07:34 -07:00
Joe Savona
0052f2e591 TryStatement: enable sprout on new tests
Enables sprout for all the new try/catch fixtures in this stack. I added new 
helpers and tried to make sure we're testing the most interesting codepath of 
each fixture. This is where property testing would help, since we could test 
multiple paths with a single block of code, but for now this seems like a good 
balance of coverage.
2023-09-07 16:50:24 -07:00
Joe Savona
ee77d91ca2 TryStatement: handle catch clause params
It's possible that the value thrown during a `try` block actually is a reference 
to some value defined outside the scope of the try block. If the catch clause 
param is also mutated, that means the mutable range of the variable would have 
to include the entire try/catch. 

We handle this by emitting a DeclareLocal temporary for the catch param prior to 
the try/catch. If it is modified during the catch block, that will extend its 
mutable range to cover the full try/catch. If any values are mutated inside the 
try, their range will also (naturally) extend around the full try/catch block. 
These ranges will overlap and be merged, ensuring that we capture the 
possibility that the value is mutated via the catch param. See unit test.
2023-09-07 16:32:55 -07:00
Joe Savona
e3622ee413 TryStatement: disallow throw inside try/catch
Modeling `throw` inside of a try/catch is awkward because it's basically a 
variable reassignment and a goto together. Thankfully that is an antipattern — 
using exceptions instead of control-flow — so it seems pretty reasonable to just 
put a todo here and leave it.
2023-09-07 16:32:54 -07:00
Joe Savona
ff0b05848b TryStatement: optimization pass, handle early returns
Adds an optimization pass to prune unnecessary maybe-throw terminals, when the 
block can be proven not to throw. For now we're _very_ conservative about what 
instructions we consider not to throw. There isn't too much of an advantage in 
pruning further, either. 

This PR also updates BuildReactiveFunction to handle the possibility of early 
returns within try or catch blocks, making sure we don't hit the invariant of 
emitting the same block twice.
2023-09-07 16:32:53 -07:00
Joe Savona
936b96b24f TryStatement: partial lowering
Implements the core lowering logic for try/catch: 

* Inside of the `try` block, we use the new HIRBuilder mode to wrap every 
instruction in a separate basic block with a maybe-throw terminal 

* We emit a 'try' terminal for the try/catch itself 

For basic examples this already works correctly. But we don't handle catch 
clause params yet.
2023-09-07 16:32:53 -07:00
Joe Savona
7f5bac171e TryStatement: try terminal scaffolding
This PR adds the other piece, a 'try' terminal which represents try/catch and 
the possibility of fallthrough to the code afterwards. For now `finally` is 
unsupported. We don't yet produce these terminals, see later in the stack.
2023-09-07 14:22:33 -07:00
Joe Savona
0977115440 TryStatement: maybe-throw terminal for per-instruction throw points
Adds a "maybe-throw" terminal which represents the possibility that the block 
may or may not throw, and can either continue forward or exit to an exception 
handler (`catch`). Also updates HIRBuilder to track the current mode, and when 
inside a try block to wrap every instruction inside a basic block that ends in a 
maybe-throw. 

So far this code isn't used yet, so doesn't affect output.
2023-09-07 14:22:28 -07:00
Sathya Gunasekaran
20203cd889 [hir] Remove unnecessary check for place kind 2023-09-07 13:57:02 +01:00
Sathya Gunasekaran
603d2179a8 [hir] Pretty print object property keys 2023-09-07 13:56:59 +01:00
Sathya Gunasekaran
f2027b9a4d [hir] Add support for string literal property keys
Mostly reuses existing analysis of an identifier property key. 

Adds a new type field to ObjectProperty to propogate the type of the key. This 
is used in codegen to correctly emit a string literal or an identifier.
2023-09-07 13:56:56 +01:00
Sathya Gunasekaran
59504e1cb4 [hir] Traverse function to capture deps, not just body node
Technically there is a body node created for implicit return expression in a 
arrow function, so the existing logic should've worked fine. But there seems to 
be a Babel bug, so let's work around it by traversing the function. 

Added a test case that captures a dep as a param -- this is currently 
unsupported  and also something that would've been ignored before this PR. Added 
a failing test to make sure we think about this case when we add support for 
default params.
2023-09-06 14:58:04 +01:00
Sathya Gunasekaran
a93aa8f322 [hir] Encapsulate pipeline from rest of Forget
Future passes will lead to inconsistent state between passes and will require
passes to run in a specific order, so let's make sure no one will misconfigure
the passes.
2023-09-06 14:12:11 +01:00
Lauren Tan
31f0f426e1 [hoisting] Add failing tests
The current plan is to add support for hoisting function declarations, let/const 
declarations, but not var yet
2023-09-05 17:04:03 -04:00
Joe Savona
ef51c92c8a first rendering test
Updates the rendering code and gets a first unit test working with a fully
static component (written by hand rather than compiled).
2023-09-05 14:03:22 -07:00
Lauren Tan
9d6170ab98 Update snap fixture
Missed this in my last PR
2023-09-05 15:07:37 -04:00
Lauren Tan
6a71868d4c [sprout] Convert for-of-mutate to sprout 2023-09-05 12:35:18 -04:00
Lauren Tan
375e2d8f33 [babel] Remove babel/plugin-syntax-jsx
Internal version of babel/core doesn't support the `inherits` property, so let's 
try removing it. The tests still seem to pass, so this might be vestigial from 
the first iteration of the compiler from last year
2023-09-05 12:08:01 -04:00
Lauren Tan
fdd6a1b8b1 Add rollup to eslint-plugin-react-forget 2023-09-05 12:08:00 -04:00
Sathya Gunasekaran
2d3507b0e1 Add signal codegen for ReactiveScopes
Instead of emitting a memo block, emit a function expression and pass it as an
argument to derived (which will then create a computed).

The naming of 'derived' needs to be tweaked.
2023-09-05 11:13:31 +01:00
Sathya Gunasekaran
4571979e46 Add pass to lower useState 2023-09-05 11:13:30 +01:00
Sathya Gunasekaran
403881c548 [forest] Turn off jsx memoization 2023-09-01 11:24:19 +01:00
Sathya Gunasekaran
9b037118c5 [test] Add basic test 2023-09-01 11:24:19 +01:00
Sathya Gunasekaran
da61d47e87 Add flag 2023-08-31 11:15:41 +01:00
Sathya Gunasekaran
c456662318 [tsconfig] Specify module resolution to be nodenext 2023-08-31 11:15:37 +01:00
Joe Savona
41c23e87c5 Disable compilation of "Components" inside class methods
Minimal repro extracted from our internal codebase. Our inference mode sees that 
this arrow function is component-like and attempts to compile it, which then 
fails because the function accesses `this`  which we bailout on.
2023-08-31 23:16:27 +01:00
Mofei Zhang
e36c8cb395 [patch] tsconfig: unbreak vercel build on main 2023-08-30 10:36:46 -07:00
Mofei Zhang
7930a213d2 [sprout][QoL] remove isComponent annotation
--- 

quality of life improvement as this seemed to be confusing (oops, and thanks for 
the feedback!) 

We don't *really* need static annotations for whether a function returns jsx 
(e.g. should be rendered as a React element) or not (e.g. should be wrapped in a 
wrapper component. This PR adds check for returned jsx objects at runtime 

--- 

Tested by running diffing the output of `yarn sprout --verbose` between this PR 
and base.
2023-08-29 17:00:55 -07:00
Mofei Zhang
c7b5f2fde0 [yarn] Update typescript node16 -> node18 2023-08-29 16:51:32 -07:00
Joe Savona
8790324562 Workaround serious Babel bug
I ran into the same issue that @poteto and @gsathya (and probably @mofeiZ) have 
run into: "Duplicate declaration of '$'" caused by Babel visiting a function 
twice despite our calling `skip()`. This PR keeps a set of nodes that we have 
already visited to avoid visiting them again, as a workaround for skip not 
working. 

# Test Plan 

Synced to www and confirmed that the previous bug no longer reproduces, and the 
compiled output looks sane.
2023-08-30 13:21:51 +01:00
Joe Savona
4f55a66d5c Compile args to forwardRef/memo
Completes a todo (ie fixes a silly mistake) from a PR earlier in the stack, so 
we now correctly recognize and compile arguments to `React.forwardRef()` and 
`React.memo()`.
2023-08-29 22:09:42 +01:00
Joe Savona
ddc9f8030e Enable compilation of FunctionExpressions
Thanks to the previous diff we can trivially support components or hooks defined 
as a FunctionExpression.
2023-08-29 22:09:41 +01:00
Joe Savona
cee08ba750 [rfc] Dont convert ArrowFunction to FunctionDecl
This PR changes the way we compile ArrowFunctionExpression to allow compiling 
more cases, such as within `React.forwardRef()`. We no longer convert arrow 
functions to function declarations. Instead, CodeGenerator emits a generic 
`CodegenFunction` type, and `Program.ts` is responsible for converting that to 
the appropriate type. The rule is basically: 

* Retain the original node type by default. Function declaration in, function 
declaration out. Arrow function in, arrow function out. 

* When gating is enabled, we emit a ConditionalExpression instead of creating a 
temporary variable. If the original (and hence compiled) functions are function 
declarations, we force them into FunctionExpressions only here, since we need an 
expression for each branch of the conditional. Then the rules are: 

* If this is a `export function Foo` ie a named export, replace it with a 
variable declaration with the conditional expression as the initializer, and the 
function name as the variable name. 

* Else, just replace the original function node with the conditional. This works 
for all other cases. 

I'm open to feedback but this seems like a pretty robust approach and will allow 
us to support a lot of real-world cases that we didn't yet, so i think we need 
_something_ in this direction.
2023-08-29 22:09:41 +01:00
Joe Savona
7d11445d8e Revamp compilation modes
We currently have multiple flags for targeting which functions to compile, but 
they are actually mutually exclusive. This PR consolidates to a single 
`compilationMode: 'annotation' | 'infer' | 'all'` flag: 

* Annotation compiles only functions that explicitly opt-in with "use forget" 

* Infer compiles explicitly opted-in functions (via "use forget") as well as any 
known/inferred components or hooks: 

* Component declarations 

* Component or hook-like functions (same rules as the ESLint plugin but with an 
extra check for whether it uses JSX or calls a hook) 

* All compiles all top-level functions. We should get rid of this in a follow-up 
and make tests use infer mode by default, and add explicit opt-ins where 
necessary. 

In all modes, "use no forget" always takes precedence and can be used to 
opt-out. The default mode is now "infer".
2023-08-29 22:09:40 +01:00
Joe Savona
3653ae2de3 Option to infer React functions to compile
Adds a new option to infer which functions to compile, based on React's ESLint 
rule. The main difference is that in addition to checking the function name we 
also check that it creates JSX or calls a hook. This should cover a significant 
majority of components and reduce the chance of accidentally targeting 
non-components, but it will leave some false negatives. 

Note that some cases that the ESLint plugin infers as React functions don't work 
yet: we don't compile FunctionExpressions, only ArrowFunctionExpressions, and 
the way we handle ArrowFunctionExpression doesn't work with things like 
forwardRef or variable declarations. We'll need more updates to fully handle all 
these cases, which I'll do later in the stack.
2023-08-29 22:09:40 +01:00
Mofei Zhang
6dbc48075b [tests] bug repros 2023-08-26 14:00:43 -07:00
Mofei Zhang
bcbcd37eb0 [sprout] Allow forwardRef and memo components 2023-08-25 16:54:21 -07:00
Sathya Gunasekaran
56b0f971d0 [sprout] Port alias-capture-in-method-receiver-and-mutate test 2023-08-23 17:44:26 +01:00
Sathya Gunasekaran
aceacce5e2 [sprout] Add a mutate utility function 2023-08-23 17:37:54 +01:00
Lauren Tan
ce82a56fa6 [ci] Fix output of parser benchmark
yarn adds some other output, so just run the script directly
2023-08-24 10:14:28 -04:00
Lauren Tan
80efd75b8e [ci] Add benchmark comparison
Updates the parser-benchmark script to use a benchmarking framework, and 

also brings in yargs to make it easier to handle parsing arguments. 

Non-CI usage: 

``` 

$ cd bench/parser-benchmark 

$ yarn bench --help 

Options: 

--help        Show help                                              [boolean] 

--ci                                                [boolean] [default: false] 

--sampleSize                                             [number] [default: 1] 

$ yarn bench --sampleSize=3 

[ISOBENCH ENDED] Parser Benchmark (3 times) 

OXC           - 32 op/s. 3 samples in 9518 ms. 5.288x (BEST) 

SWC           - 20 op/s. 3 samples in 9487 ms. 3.256x 

HermesParser  - 6 op/s. 3 samples in 9124 ms. 1.000x (WORST) 

Forget (Rust) - 15 op/s. 3 samples in 9749 ms. 2.499x 

``` 

In CI this outputs JSON instead of logging to stdout. The results are 

stored in github's action cache and used as a point of comparison when 

new commits are pushed to main. The comparison should be shown on 

workflow [summary 
pages](https://github.com/facebook/react-forget/actions/runs/5956421081), but 
nothing will be populated until we merge this PR. 

We intentionally only run this workflow on main as any run would 

override the last cached result, so a PR with multiple pushes would then 

start having comparisons with itself.
2023-08-23 17:58:23 -04:00
Lauren Tan
aeae3ba0a7 [ci] Add rust-cache 2023-08-23 17:16:40 -04:00
Lauren Tan
fc506b5fed [ci] Run parser benchmark on ci
Example: 

![Screenshot 2023-08-23 at 11 22 55 
AM](https://github.com/facebook/react-forget/assets/1390709/158fa9f1-32f6-444e-8ea8-75f4f1475ee0)
2023-08-23 17:16:40 -04:00
Lauren Tan
3a9be52f51 Update test262 2023-08-22 15:07:46 -04:00
lauren
a0dc166991 [rfc] Remove top level forget directory
Sorry about the thrash in advance! This removes the top level `forget` directory 
which adds unnecessary nesting to our repo 

Hopefully everything still works
2023-08-22 15:04:54 -04:00
Joe Savona
a13df48d4e [rust] Serialize range as array
ESTree expects the `range` value to be an array of `[start, end]`, we support 
deserializing from that format but serialized as an object of `{start,end}`, now 
we serialize to the array form.
2023-08-21 21:55:08 -07:00
Joe Savona
9c13e4413c [rust] Expose parse function to napi
Updates the `forget_napi` crate to call the parser and semantic analyzer and 
convert their values to JS.
2023-08-21 16:04:03 -07:00
Joe Savona
7fc7488ac8 [rust] Scaffolding for napi-backed npm package
Scaffolds out a `forget_napi` crate following the guide for https://napi.rs/. 
This crate will be the public JS api for interacting with Forget.
2023-08-21 14:51:10 -07:00
Mofei Zhang
1c56843518 [tests] Allow .ts, .tsx fixture files 2023-08-22 12:54:01 -04:00
Lauren Tan
6be03b681e [ci] Split out babel-plugin-react-forget jest to its own job 2023-08-18 17:06:52 -04:00
Lauren Tan
e426cb553d [ci] Split out babel-plugin-react-forget prettier/eslint to its own job 2023-08-18 15:22:09 -04:00
Joe Savona
bcd692fca4 Promote enableOptimizeFunctionExpressions to default/only option
We’ve had this feature turned on internally for a while with only a couple minor 
bugs and no fundamental issues. It’s a necessary change for simplifying function 
expression dependencies and context variables, so let’s remove the feature flag 
and fix forward for any issues.
2023-08-18 12:06:16 -07:00
Lauren Tan
c4d000ce49 [ci] Rename TS ci to be consistent with other workflows 2023-08-18 14:16:21 -04:00
Lauren Tan
9e332ee05a [ci] Run yarn workspace tests in parallel 2023-08-18 14:10:41 -04:00
Joe Savona
a905a02901 [rust] Add SAFETY comments for unsafe blocks
Adds SAFETY comments for the two instances of unsafe blocks that are *not* just 
FFI. It seems like overkill to annotate all the FFI calls so i'm skipping that, 
let me know if anyone has strong preferences otherwise.
2023-08-17 15:23:46 -07:00
Joe Savona
d5a8e80157 [rust][ci] Run cargo check/fmt
Formats the codebase, fixes some clippy lints, and updates CI to check that code 
is formatted.
2023-08-17 12:59:29 -07:00
Joe Savona
5b6370234c Update readme to mention Rust CI config
This is mostly to test that the new configuration skips the Rust CI step if 
there are no rust changes - yay, that worked! But also worth mentioning in the 
readme so lets land.
2023-08-17 10:15:59 -07:00
Joe Savona
514916d9ce [rust][ci] Only run Rust actions when relevant files change 2023-08-17 10:10:44 -07:00
Joe Savona
3ce4116930 [rust][sema] Only create one scope per function/class
Fixes up function and class handling to not create nested scopes in some cases. 
Doesn't affect name resolution otherwise.
2023-08-17 10:03:00 -07:00
Joe Savona
543d27c2f0 [rust][sema] Support classes
Adds name resolution support for class declarations and expressions. Mostly this 
involves _not_ visiting some `Identifier` nodes that don't actually represent 
variables. For example, method names don't introduce new variables (but if they 
are computed, they may _refer_ to variables).
2023-08-17 10:02:56 -07:00
Joe Savona
dafa468765 [rust][sema] Allow defining/resolving known globals
Adds an option to pass a list of known globals into the semantic analyzer so 
that references to globals can be checked. As a follow-up we'll need to 
distinguish between different types of semantic analysis errors, so that callers 
which don't want to validate globals can ignore "unknown variable" reference 
errors while still handling definite errors such as duplicate declarations.
2023-08-16 16:44:40 -07:00
Joe Savona
bfb7d2bfbd [rust][sema] Handle var/let/const redeclaration
JavaScript has ~sane~ fun rules around where variables can be redeclared or not, 
and which kinds of variables this applies to. Actually the rules are pretty 
straightforward: 

* `var` can be redeclared any number of times. 

* In strict mode, other declarations cannot be redeclared within the same scope. 
This implies that a `var` declaration cannot conflict with these other forms, 
which must take into account hoisting. So you can't have a `var a; let a` in the 
same scope, but you also can't have a `let a` at a scope and then a `var a` 
which will hoist to that same scope.
2023-08-16 12:26:04 -07:00
Joe Savona
796080f4d1 [rust][sema] Statically detect some TDZ violations
The [temporal dead 
zone](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#temporal_dead_zone_tdz), 
often abbreviated TDZ, is the period between the start of its declaring block 
and the line that contains the let/const/class declaration. Not all instances of 
TDZ can be detected statically, because whether or not a TDZ error will occur at 
runtime is a property of which control flow path is taken and whether some other 
code has initialized the value. However, a subset of cases can be detected, and 
that's what we implement here. 

When we encounter a variable reference we record the next declaration id at that 
point in time. Then when resolving references after visiting the program, we can 
check: did the reference end up referring to a let/const/class binding whose id 
is equal or greater to that "next declaration"? If so, it means the reference 
refers to a variable that is provably declared later and is a known TDZ 
violation. The catch is that when resolving references, we reset the "next 
declaration" limit value when we bubble up out of a function scope. That's 
because references to let/const within a function may occur after the 
declaration, and we can't statically validate them.
2023-08-15 17:20:34 -07:00
Joe Savona
2edf66ecdf [rust][sema] Resolve all references after traversing program
Previously we attempted to resolve each reference at the close of its defining 
scope, and if it couldn't be resolved yet we bubbled the unresolved reference up 
to the parent scope. That approach isn't ideal for two reasons: 

* First, it's inefficient since we may have to make multiple attempts to resolve 
the same reference. 

* Second, it's incorrect. There can be cases where we think we can resolve a 
reference to a value defined in an outer scope, but there is a hoisted 
declaration from an intermediate scope that we haven't seen yet. 

The safest and most optimal thing is to just queue all references and resolve 
them at the end.
2023-08-15 12:33:45 -07:00
Joe Savona
34914796b2 [rust][sema] Set root scope kind based on program source type
Rather than always create a Global scope and then immediately add a Module child 
node, we now set the kind of the root scope to global (for scripts) or module 
(for modules) based on the program node.
2023-08-15 12:24:38 -07:00
Joe Savona
875d7175ac [rust] cargo fix 2023-08-15 09:31:49 -07:00
Joe Savona
b133b6c0b4 [rust][sema] Visit imports as declarations
Teaches the semantic analyzer about import statements. We now treat imports as 
declarations, so that subsequent references to the imported value can be 
resolved.
2023-08-15 09:30:43 -07:00
Joe Savona
15e0ecaa8f [rust] Convert Hermes source locations to ESTree SourceRange
Teaches the hermes->estree conversion to convert source ranges. This means our 
diagnostics now point to the actual source of the error: 

<img width="903" alt="Screenshot 2023-08-14 at 5 39 50 PM" 
src="https://github.com/facebook/react-forget/assets/6425824/2960d114-0cc6-4531-9e7d-00ff3bfb1eb3"> 

Of course I still need to teach our semantic analysis about imports, but that's 
a separate issue!
2023-08-14 17:36:58 -07:00
Joe Savona
f2a1a6a6e4 [rust][sema] Test for function hoisting 2023-08-14 17:36:54 -07:00
Mofei Zhang
1ad0460693 [sprout] Add shared runtime, improve error reporting
--- 

I added ~20 more tests to Sprout to get more of a feel for what the test 
framework would need to support all fixtures. I'm relatively confident that the 
approach outlined in [the original workplace 
post](https://fburl.com/workplace/ftu8woch) works to migrate almost all existing 
fixture tests to sprout (TLDR: use shared functions when possible, otherwise 
write helper functions in-file). 

Add `shared-runtime` file to reduce the amount of overhead needed to set up a 
fixture test. 

- All generalizable functions and values should be added here (e.g. 
`shallowCopy`, `deepCopy`, `sum`). 

- Editor integration is set up through a custom tsconfig, so importing from 
`shared-runtime` should just work (with hover annotations, click-to-definition, 
etc). 

<img width="682" alt="Screenshot 2023-08-17 at 11 09 47 AM" 
src="https://github.com/facebook/react-forget/assets/34200447/78c3dff9-ba10-4057-b3f6-2fa842d19b1d"> 

--- 

Tested new test fixtures added to sprout by adding them to `testfilter.txt` and 
running with `--verbose --filter`. 

Note that there are no unexpected exceptions, and all logs + returned values 
match. 

``` 

feifei0@feifei0-mbp babel-plugin-react-forget % yarn sprout:build && yarn sprout 
--verbose --filter 

yarn run v1.22.19 

$ yarn workspace sprout run build 

$ rimraf dist && tsc --build 

  Done in 2.07s. 

yarn run v1.22.19 

$ node ../sprout/dist/main.js --verbose --filter 

PASS  console-readonly 

ok {"a":1,"b":2} [ 

"{ a: 1, b: 2 }", 

"{ a: 1, b: 2 }", 

"{ a: 1, b: 2 }", 

"{ a: 1, b: 2 }", 

"{ a: 1, b: 2 }" 

] 

PASS  constant-propagate-global-phis-constant 

ok <div>global string 0</div> 

PASS  constant-propagate-global-phis 

ok <div>global string 1</div> 

PASS  dce-loop 

ok 10 

PASS  destructure-capture-global 

ok {"a":"value 1","someGlobal":{}} 

PASS  destructuring-mixed-scope-and-local-variables-with-default 

ok {"media":null,"allUrls":["url1","url2","url3"],"onClick":"[[ function 
params=1 ]]"} 

PASS  holey-array-expr 

ok [null,"global string 0",{"a":1,"b":2}] 

PASS  infer-global-object 

ok {"primitiveVal1":2,"primitiveVal2":null,"primitiveVal3":null} 

PASS  infer-phi-primitive 

ok 1 

PASS  infer-types-through-type-cast.flow 

ok 4 

PASS  issue933-disjoint-set-infinite-loop 

ok [2] 

PASS  jsx-tag-evaluation-order-non-global 

ok <div>StaticText1<div>StaticText2</div></div> 

PASS  jsx-tag-evaluation-order 

ok <div>StaticText1string value 1<div>StaticText2</div></div> 

PASS  method-call 

ok 4 

PASS  mutable-lifetime-loops 

ok {"a":{"value":6},"b":{"value":5},"c":{"value":4},"d":{"value":6}} 

PASS  mutable-lifetime-with-aliasing 

ok {"b":[{}],"value":[{"c":{},"value":"[[ cyclic ref *0 ]]"},null]} 

PASS  update-expression-in-sequence 

ok [4,2,3,4] 

PASS  update-expression-on-function-parameter 

ok [4,1,4,2,4,3,1,4,4] 

PASS  update-expression 

ok {"x":1,"y":1,"z":2} 

PASS  useMemo-multiple-if-else 

ok 2 

20 Tests, 20 Passed, 0 Failed 

  Done in 4.11s. 

```
2023-08-17 13:28:08 -04:00
Mofei Zhang
2b4a1d86ff [pipeline] Recognize "use no forget" directive
--- 

Add "use no forget", not gated behind a feature flag for simplicity ("use no 
forget" should always tell Forget to not compile a component, right?)
2023-08-17 11:03:20 -04:00
Mofei Zhang
28688ade9f [sprout] Add copyright headers etc 2023-08-17 11:03:18 -04:00
Lauren Tan
74aec8aa35 [ez] Add concurrently labels to yarn dev 2023-08-17 12:01:42 -04:00
Lauren Tan
9c2ce7a8c8 Try running tests for babel-plugin-react-forget concurrently
Unscientifically seems to be a little faster. I made sure to preserve the 
ordering of the logs even though the processes will run concurrently, so the ci 
output should still be readable 

![Screenshot 2023-08-17 at 11 51 45 
AM](https://github.com/facebook/react-forget/assets/1390709/b9878983-ed0c-4b93-bc10-61109ff8103d)
2023-08-17 11:58:15 -04:00
Mofei Zhang
5448b33eb0 [yarn workspaces] Add typescript dependencies to tsconfig
--- 

Previously, I used `yarn workspace build fixture-test-utils` and `yarn workspace 
build babel-plugin-react-forget` to recompile dependencies on sprout. However, 
tsc supports project references which was intended to solve this problem 
https://www.typescriptlang.org/docs/handbook/project-references.html 

Note that apparently this places `.tsbuildinfo` in package roots, according to 
https://github.com/microsoft/TypeScript/issues/30925
2023-08-16 15:38:30 -04:00
Mofei Zhang
d6dea6b9b9 [sprout][fixtures] Annotate some fixtures
--- 

This PR is an example of how to update fixture to add sprout annotations. 

Running with `yarn sprout --verbose --filter` shows the fixture outputs. 

``` 

PASS  array-access-assignment 

ok [[2],[[2],[],[3]]] 

PASS  array-expression-spread 

ok [0,1,2,3,null,4,5,6,"z"] 

PASS  array-map-frozen-array 

ok [[],[]] 

PASS  array-map-mutable-array-mutating-lambda 

ok [[],[]] 

PASS  array-pattern-params 

ok [{"a":"val1"},{"b":"val2"}] 

PASS  array-properties 

ok {"a":[[1,2],2,"hello"],"x":3,"y":"[[ function params=1 ]]","z":"[[ function 
params=1 ]]"} 

PASS  array-property-call 

ok {"a":[1,2,"hello",42],"x":4,"y":1} 

PASS  assignment-expression-computed 

ok [7] 

PASS  assignment-expression-nested-path 

ok {"b":{"c":6}} 

PASS  capturing-func-mutate-2 

ok {"a":2} 

PASS  capturing-function-alias-computed-load-2 

ok "val2" 

PASS  capturing-function-alias-computed-load-4 

ok "val2" 

```
2023-08-16 15:38:29 -04:00
Mofei Zhang
9d1a842a83 [sprout] Enable sprout on all new fixtures
--- 

Rename `SproutOnlyFilterTodoRemove.ts` to `SproutTodoFilter.ts`. I've tried to 
group the skipped fixtures by difficulty and add comments about what need to be 
done, also happy to change the structure of this. 

From this point, sprout will run on all new fixtures by default. If the fixture 
forgets to export a `FIXTURE_ENTRYPOINT`, sprout will fail with this error. 

<img width="853" alt="Screenshot 2023-08-15 at 5 59 32 PM" 
src="https://github.com/facebook/react-forget/assets/34200447/0f80d650-1dc1-4df4-9710-e49acbb424b0"> 

See #1961 for an example of how to annotate existing skipped fixtures or new 
ones. The `README.md` is also updated with a guide.
2023-08-16 15:38:28 -04:00
Mofei Zhang
c4ae2a48dc [sprout] patch bug + add verbose mode
--- 

``` 

yarn sprout --verbose 

``` 

Verbose mode prints out all outputs of tests. The test output is a status (`ok` 
or `exception`), a returned or thrown value, and a set of console logs. 

Currently as of #1960 , this is the output: 

```sh 

$ yarn workspace babel-plugin-react-forget run build && node 
../sprout/dist/main.js --verbose 

$ rimraf dist && tsc 

PASS  alias-nested-member-path 

ok {"y":{"z":[]}} 

PASS  assignment-variations-complex-lvalue 

ok {"y":{"z":4}} 

PASS  assignment-variations 

ok 1 

PASS  chained-assignment-expressions 

ok {"z":null} 

PASS  computed-call-evaluation-order 

ok {"f":"[[ function params=0 ]]"} [ 

"A", 

"B", 

"arg", 

"original" 

] 

PASS  const-propagation-into-function-expression-primitive 

ok 42 [ 

"42" 

] 

PASS  constant-propagation-for 

ok 0 

PASS  constant-propagation-while 

ok 0 

PASS  constant-propagation 

ok -6 [ 

"foo" 

] 

PASS  controlled-input 

ok <input value="0"> 

PASS  do-while-continue 

ok [1.5,1,0.5] 

PASS  do-while-simple 

ok [6,4,2] 

PASS  expression-with-assignment 

ok 5 

PASS  for-of-break 

ok [] 

PASS  for-of-conditional-break 

ok [] 

PASS  for-of-continue 

ok [0.5,1,1.5] 

PASS  for-of-destructure 

ok [0,2,4] 

PASS  for-of-simple 

ok [0,2,4] 

PASS  function-declaration-reassign 

ok {} 

PASS  function-declaration-redeclare 

ok "[[ function params=0 ]]" 

PASS  lambda-reassign-primitive 

ok 41 

PASS  lambda-reassign-shadowed-primitive 

ok {} 

PASS  property-call-evaluation-order 

ok {"f":"[[ function params=0 ]]"} [ 

"A", 

"arg", 

"original" 

] 

PASS  reactive-scope-grouping 

ok {"y":[{}]} 

PASS  sequentially-constant-progagatable-if-test-conditions 

ok "ok" 

PASS  simple-function-1 

ok "[[ function params=1 ]]" 

PASS  ssa-complex-multiple-if 

ok 

PASS  ssa-complex-single-if 

ok 

PASS  ssa-for 

ok 11 

PASS  ssa-if-else 

ok 

PASS  ssa-objectexpression-phi 

ok {"x":1,"y":3} 

PASS  ssa-property-call 

ok {"x":[[]]} 

PASS  ssa-property 

ok {"x":[]} 

PASS  ssa-return 

ok 2 

PASS  ssa-simple-phi 

ok 

PASS  ssa-simple 

ok 

PASS  ssa-single-if 

ok 

PASS  ssa-switch 

ok 

PASS  ssa-throw 

exception undefined 

PASS  ssa-while 

ok 10 

PASS  type-field-load 

ok 1 

PASS  type-test-field-store 

ok {} 

PASS  type-test-primitive 

ok 2 

PASS  update-expression-constant-propagation 

ok {"a":0,"b":0,"c":2,"d":2,"e":0} 

44 Tests, 44 Passed, 0 Failed 

  Done in 9.27s. 

```
2023-08-16 15:32:15 -04:00
Mofei Zhang
9d1f2c86ed [sprout] Codemod: add todos for test fixtures
--- 

Codemod to add an `export FIXTURE_ENTRYPOINT` with todo values for params for 
tests with at least one param and 0 refs to external values
2023-08-16 15:32:15 -04:00
Mofei Zhang
43e3cd61f8 [snap] Use sync fs apis to stop crashes
--- 

Not sure why, but snap kept silently crashing when I used fs/promises to write 
to many file handles. I tried a `.catch(...)`, but couldn't figure it out. This 
diff changes snap to use sync fs apis to avoid crashes, but I'd love to get 
feedback if someone knows how to debug this.
2023-08-16 15:32:14 -04:00
Mofei Zhang
ce015a164b [sprout] Codemod: add isComponent: false annotations to current fixtures
To keep the type signature simple, let's always have the same set of keys in 
`FIXTURE_ENTRYPOINT`
2023-08-16 15:32:13 -04:00
Lauren Tan
6b242c12a9 [ci] Don't run yarn build in ci
Can't recall why we added this, but it shouldn't be necessary and should shave 
off ~2 mins from ci
2023-08-16 12:16:51 -04:00
Lauren Tan
a59e15d40f [ci] Try to cache node_modules to speed up ci 2023-08-16 11:04:53 -04:00
Sathya Gunasekaran
b968d4da48 [hir] Validate globals are equal before propagating
Make sure the value of the globals in phi operands are the same before constant 
propagating them.
2023-08-16 15:39:01 +01:00
Sathya Gunasekaran
b686b5dd9d [hir] Refactor constant propagation of phis
This PR moves the phi evaluation to a separate function. 

Most importantly, it inverts the default case to _not_ constant propagate unless 
we have explicit validation of the phi operands.
2023-08-16 15:39:00 +01:00
Sathya Gunasekaran
c7210bac93 [test] Add failing test for constant propogation of globals as phi operands 2023-08-16 15:39:00 +01:00
Mofei Zhang
a418e35bf9 [sprout] QoL: use cli args, report pretty results
--- 

- Added sprout to Github Actions by updating `yarn test` command 

- Added cli args (`filter` and `sync`) 

- use chalk to make results nicer  

Tested locally: 

<img width="900" alt="Screenshot 2023-08-14 at 6 04 28 PM" 
src="https://github.com/facebook/react-forget/assets/34200447/b5481bbc-6a50-40c6-a85f-d2443130dd2a">
2023-08-14 19:26:05 -04:00
Mofei Zhang
d2983888f4 [sprout] Initial draft of sprout 🌱 test runner
--- 

## Sprout 🌱 Overview 

**(Overview copied from 
[sprout/README.md](0468ddf8bb/forget/packages/sprout/README.md))** 

React Forget test framework that executes compiler fixtures. 

Currently, Sprout runs each fixture with a known set of inputs and annotations. 
We hope to add fuzzing capabilities to Sprout, synthesizing sets of program 
inputs based on type and/or effect annotations. 

Sprout is currently WIP and only executes files listed in 
`src/SproutOnlyFilterTodoRemove.ts`. 

### Milestones: 

- [] Render fixtures with React runtime / `testing-library/react`. 

- [ ] Make Sprout CLI -runnable and report results in process exit code. 

After this point: 

- Sprout can be enabled by default and added to the Github Actions pipeline. 

- `SproutOnlyFilterTodoRemove` can be renamed to `SproutSkipFilter`. 

- All new tests should provide a `FIXTURE_ENTRYPOINT`. 

- [ ] Annotate `FIXTURE_ENTRYPOINT` (fn entrypoint and params) for rest of 
fixtures. 

- [ ] Edit rest of fixtures to use shared functions or define their own helpers. 

- [ ] *(optional)* Store Sprout output as snapshot files. i.e. each fixture 
could have a `fixture.js`, `fixture.snap.md`, and `fixture.sprout.md`. 

### Constraints 

Each fixture test executed by Sprout needs to export a `FIXTURE_ENTRYPOINT`, a 
single function and parameter set with the following type signature. 

```js 

type FIXTURE_ENTRYPOINT<T> = { 

// function to be invoked 

fn: ((...params: Array<T>) => any), 

// params to pass to fn 

params: Array<T>, 

// true if fn should be rendered as a React Component 

//  i.e. returns jsx or primitives 

isComponent?: boolean, 

} 

``` 

Example: 

```js 

// test.js 

function MyComponent(props) { 

return <div>{props.a + props.b}</div>; 

} 

export const FIXTURE_ENTRYPOINT = { 

fn: MyComponent, 

params: [{a: "hello ", b: "world"}], 

isComponent: true, 

}; 

``` 

--- 

## Implementation Details 

- jest-worker test orchestrator (similar to Snap 🫰). 

I chose to write a test runner instead of directly using Jest for flexibility 
and speed. 

- Sprout 🌱 currently runs much more code per fixture (2 babel transform 
pipelines + 2 `exec(...)` than snap, so all scaling concerns apply. 

- Sprout may need more customization in the future (e.g. fuzzing component 
inputs, caching artifacts) 

- We probably want to add snapshot files for Sprout, which is much easier with a 
custom runner. 

This is also one of the main reasons we wrote Snap. Jest consolidates all 
external snapshots (i.e. non-inline snapshots) from a test into [a single 
file](d0a006ffa9/forget/src/__tests__/__snapshots__/compiler-test.ts.snap). 
This was painful mainly for rebasing changes, but also for small papercuts (e.g. 
needing to run `yarn test -u` twice when deleting a fixture) 

- Currently does not save output to snapshot file, but we can easily add this 
later (i.e. each test would have a `.snap.md` and `.sprout.md` file) 

- Supports filter mode (same testfilter.txt file as snap) 

- Currently does not support watch mode. I expect that sprout's primary use will 
be catching bugs in the PR / Github Actions phase. Can be changed if we need to 
iterate on sprout output while developing. 

- All tests are run with `react-test-renderer` to access the React Runtime, 
which is needed for calls to `useMemoCache` (and other potential hooks). 

- react-test-renderer required a mocked DOM, so I ~~used js-dom~~ did a terrible 
js-dom hack to add document, window, and other browser globals to the 
jest-worker globals, then exec the test code in the jest worker global. 

I can clean this up later by bundling library code (e.g. react, 
react-test-renderer) to not use `require(...)`, then calling `exec` on all "test 
client code" in the js-dom mock global (instead of the real jest worker one) 

- Tests marked `isComponent` need to return valid jsx or primitives. Values 
returned by all other tests are converted to a string primitive via 
JSON.stringify. 

```sh 

# install new dependencies to node_modules 

$ yarn 

$ cd forget/packages/babel-plugin-react-forget 

$ yarn sprout:build && yarn sprout 

PASS  alias-nested-member-path 

PASS  assignment-variations-complex-lvalue 

PASS  assignment-variations 

PASS  chained-assignment-expressions 

PASS  computed-call-evaluation-order 

PASS  const-propagation-into-function-expression-primitive 

PASS  constant-propagation-for 

PASS  constant-propagation-while 

PASS  constant-propagation 

... 

  Done in 3.91s. 

```
2023-08-14 19:26:02 -04:00
Mofei Zhang
904ea6c9e4 [sprout] codemod for tests that can be run out of the box 2023-08-14 19:26:02 -04:00
Mofei Zhang
5669fc73c0 [snap] Refactor fixture utils to separate package
--- 

Move shared functions to `fixture-test-utils` package.
2023-08-14 19:26:01 -04:00
Mofei Zhang
02e8cf9790 [snap][easy] typecheck forget babel plugin options 2023-08-14 19:26:01 -04:00
Joe Savona
45d884bb6b Fix LeaveSSA missing param declarations
Nice find from @mofeiz, we weren't adding declarations for function params in 
LeaveSSA. This caused us to hit an invariant for update expressions that updated 
params which assumed that all named variables had a declaration. This is why 
it's helpful to add invariants, it helps you find places where you violate them 
:-)
2023-08-14 15:48:05 -07:00
Joe Savona
cb2ba7118f [rust][sema] Support hoisting semantics
Adds basic support for hoisting semantics: * Resolution of variable references 
is _always_ deferred in case the correct binding   hasn't been seet yet due to 
hoisting. * Var and function declarations bubble to the appropriate scope 

There are lots of subtleties that aren't implemented yet but these rules cover a 
lot.
2023-08-14 12:24:05 -07:00
Joe Savona
011570a078 [rust][sema] Bubble unresolved references (for hoisting)
This is a precursor to adding support for hoisting in semantic analysis. 
Previously when we encountered an unknown reference we immediately reported an 
error. But hoisted variables may be referenced before they're defined, so we 
don't know for sure when we see an unknown variable if its actually unbound or 
not. 

This PR adds the first part of hoistingn support: rather than immediately report 
an error when encountering an unbound variable we store it in a list of 
unresolved references on the current scope. As we close each scope we recheck 
and see if the variable can now be resolved. If yes we record that, otherwise we 
bubble up the unresolved reference to the parent scope (and try again there). 

The next PR(s) will handle hoisting of `var` and other syntax to the apropriate 
nearest scope boundary (function/module).
2023-08-14 10:08:02 -07:00
Joe Savona
57f648009c [rust] Cleanup visitors 2023-08-14 09:54:29 -07:00
Joe Savona
e248534184 [rust] SSA fixes after operand refactor
When updating the data model for operands from instruction indices to 
identifiers, I forgot to rewrite terminal operands.Doing so required a refactor 
to use the new BlockRewriter helper.
2023-08-14 09:54:26 -07:00
Joe Savona
a49335f72a [rust] Support destructuring
Adds a `Destructure` instruction closely following the design of the TS-based 
compiler. The main change is fairly small: the TS compiler doesn't support rest 
spreads that are not identifiers, such as the `...y[]` in `const [x, ...[y]] = 
z`. I added support for this in the Rust compiler.
2023-08-11 15:13:21 -04:00
Lauren Tan
298bf55f25 Address feedback 2023-08-14 12:05:52 -04:00
Lauren Tan
3ec95c531b [be] More babel plugin Program cleanup
Give a more descriptive name
2023-08-11 16:39:03 -04:00
Lauren Tan
25e3575c38 [be] Move more logic into Imports module 2023-08-11 16:39:01 -04:00
Lauren Tan
ac8d874711 [eslint-plugin] Silence babel warnings in test
be quiet Babel!
2023-08-11 16:38:59 -04:00
Lauren Tan
d2990f1592 [be] Extract import logic to own module
Extracting logic around inserting import statements into its own module, with 
more descriptive function names
2023-08-11 16:38:57 -04:00
Lauren Tan
ebd8ed5c71 [be] Extract gating logic to its own module
Just moving code around to another module.
2023-08-11 16:38:55 -04:00
Lauren Tan
dd8b743f6a [be] Refactor gating logic in babel plugin
`compileProgram` was getting complex, so this extracts some of the logic into 
smaller functions. Additionally, the `try` block now only wraps the `compileFn` 
generator from Pipeline, which means not accidentally catching other non-Forget 
errors
2023-08-11 16:38:52 -04:00
Lauren Tan
6c719f62a0 [ez] ComponentDeclaration transforms only into FunctionDeclaration
ComponentDeclarations can only be transformed into FunctionDeclarations, so the 
types were wrong
2023-08-11 13:31:06 -04:00
Lauren Tan
c3ec002d69 Invert shouldVisitNode logic 2023-08-10 17:24:22 -04:00
Lauren Tan
ec44c4789b Add ComponentDeclaration util 2023-08-10 17:24:21 -04:00
Sathya Gunasekaran
e70dfbb5a4 [babel] Lookup magic prop directly 2023-08-10 13:31:46 -04:00
Sathya Gunasekaran
c1c35407ba [test] Test with non component declaration 2023-08-10 12:02:08 -04:00
Joe Savona
c3ad8a86b1 [rust] Semantic analysis resolves break/continue 2023-08-10 10:59:51 -04:00
Joe Savona
ee13c30d10 [rust] Sketch of ReactiveIR (prev ReactiveFunction)
Initial data types for `forget_reactive_ir`, which is the Rust analogue of 
`ReactiveFunction` in the TS compiler. I'm renaming here for clarity, though 
naming suggestions are very welcome!
2023-08-10 10:59:51 -04:00
Joe Savona
7c38b15078 [rust] Align instruction data model closer to JS
Aligns the data model for `Instruction` closer to JS: * Adds an `lvalue: 
IdentifierOperand` property (Identifier + Effect) * Changes rvalues from being a 
reference to the definining instruction by   instruction offset (InstrIx) to be 
a variable reference (IdentifierOperand) 

There are pros and cons to the previous offset-based approach. It's definitely 
convenient to be able to jump directly to the instruction that defined an 
operand value. However: * Many passes need to track things like basic 
reassignments, which mean they need to build   up mappings of IdentifierId to 
some data. When all operands are IdentifierOperands, this   can be a single 
mapping. * It aligns closer to JS, which makes porting a bit easier. 

But most of all: porting the `ReactiveFunction` data type — which is in tree 
form - is non-trivial with the offset-based approach. As we map HIR to the tree 
form, we'd have to remap every operand offset. Using identifiers for operands 
simplifies this. 

We can always revisit this design choice later.
2023-08-10 10:59:50 -04:00
Joe Savona
cbb815e907 [rust] Port EliminateRedundantPhi fix 2023-08-10 10:59:50 -04:00
Joe Savona
74a062ba9d [rust] Autofix lints 2023-08-10 10:59:50 -04:00
Joe Savona
b8f1d37b6a [rust] Remove SWC
After the previous PR we no longer depend on SWC for the critical path of 
development/testing. Notably this unblocks adding support for the rest of the 
language: the new `forget_hermes_parser` crate automatically converts Hermes 
Parser's AST into our `forget_estree` format via codegen. Achieving full 
language support with SWC would have required manually defining the remaining 
conversions for the rest of the language. 

Long-term we'll need to revisit how to integrate into SWC, and more generally 
into setups build atop SWC such as Next.js's Turbopack-based build 
configuration. But i'll delete for now since we don't depend on it for 
iteration, it's slow to build, and requires us to opt-in to nightly Rust.
2023-08-10 10:59:49 -04:00
Joe Savona
7feaabfe68 [rust] Use hermes parser for Forget fixture tests
Replaces the use of SWC parser in the Forget fixture tests with Hermes Parser. 
This includes aligning on a single type to represent JS Values and numbers 
(combining semi-duplicated code from forget_hir and forget_estree) and adding 
support for lowering babel-style 
NumericLiteral/BooleanLiteral/StringLiteral/NullLiteral node types (since Hermes 
Parser produces a mix of estree/babel node types)
2023-08-10 10:59:49 -04:00
Joe Savona
6bca9fb7a3 [rust] Update HIR builder to use new semantic analysis
Updates HIR builder to rely on the new semantic analysis instead of assuming 
that the ast nodes will already have binding info attached. That was a stopgap 
until we had our own name resolution :-) 

Once this lands we can remove SWC and switch everything to forget_hermes_parser, 
and also remove the non-spec Identifier.binding field (which stored the 
temporary name resolution data).
2023-08-10 10:59:48 -04:00
Sathya Gunasekaran
5d8587b81c Add option to compile ReactScript 2023-08-09 14:49:29 -04:00
Sathya Gunasekaran
27f38ec321 Add hermes-parser 2023-08-09 14:47:56 -04:00
Sathya Gunasekaran
6075d7ea34 Upgrade babel 2023-08-09 14:47:56 -04:00
Joe Savona
43677da7e4 Add instructions for UpdateExpression variants
Adds new instructions to accurately model UpdateExpression semantics, since 
`x++` is un-intuitively not the same as `x = x + 1`. There are a few different 
ways to model the combination of prefix/postfix and increment/decrement: 

* One instruction for all combinations of prefix/postfix and 
increment/decrement, eg 'UpdateExpression' 

* Instructions for Increment/Decrement, each with a property to distinguish 
prefix/postfix 

* Instructions for Prefix/Postfix, each with aproperty to distinguish 
increment/decrement. 

I chose the latter, `PrefixUpdate` and `PostfixUpdate`, because it keeps the 
number of new instructions minimal while keeping separate instructions for the 
most important distinction: whether the result of the instruction is the value 
before applying the operation or after. I'm open to suggestions about this 
though. 

A few quick notes: 

* Constant propagation is supported but only for numbers (we don't support 
bigint yet anyway) 

* LeaveSSA needs to know about these instructions since their presence requires 
making the original variable declaration Let, not Const. 

* EnterSSA mapped lvalues before rvalues, which is out of order but didn't 
previously matter. I just had to flip the order and everything worked.
2023-08-07 13:50:32 -07:00
Mofei Zhang
e33c9c43cc [ssa] Patch: propagate every rewrite to function expressions
--- 

#1899 only propagated eliminated phi nodes to function expressions on the first 
iteration through blocks. However, this was buggy as later iterations could 
introduce rewrites that need to be propagated. 

[playground 
repro](https://0xeac7-forget.vercel.app/#eyJzb3VyY2UiOiJmdW5jdGlvbiBDb21wb25lbnQoKSB7XG4gIGNvbnN0IHggPSA0O1xuXG4gIGNvbnN0IGdldDQgPSAoKSA9PiB7XG4gICAgd2hpbGUgKGJhcigpKSB7XG4gICAgICBpZiAoYmF6KSB7XG4gICAgICAgIGJhcigpO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gKCkgPT4geDtcbiAgfTtcblxuICByZXR1cm4gZ2V0NDtcbn0ifQ==). 

I manually synced #1907 to check that this fix works for the VR Store codebase.
2023-08-07 17:41:35 -04:00
Mofei Zhang
ff2f2e0685 [patch] Gate EliminatePhi fix behind feature flag 2023-08-07 17:41:34 -04:00
Sathya Gunasekaran
adfae63f20 [hir] Remove unnecessary rewrite of instr.lvalue
The for loop over eachInstructionLValue already rewrites instr.lvalue: ```       
  for (const place of eachInstructionLValue(instr)) {           
rewritePlace(place, rewrites);         } ```
2023-08-07 13:41:59 -04:00
Joe Savona
461083d7bd [rust] Deprecate old manual ast visitor 2023-08-04 14:44:54 -07:00
Joe Savona
ab5213e8f4 [rust][sema] Comments 2023-08-04 12:16:08 -07:00
Joe Savona
3df04725bd [rust][sema] For statements and JSX
Adds semantic analysis support for normal `for` statements and for JSX. The main 
catch with JSX is that there are a bunch of identifiers that we have to ignore 
since they aren't variable references: jsx attribute names, namespace names, jsx 
member expression properties, and closing elements.
2023-08-04 12:02:21 -07:00
Joe Savona
4756c5ab26 [rust][sema] Shared logic for for-in/for-of
Shares the code for for..in and for..of, along with a few other internal 
refactorings.
2023-08-04 12:02:18 -07:00
Joe Savona
e6e2d9437e [rust] Improved name resolution
This is the start of an improved semantic analysis pass, reusing the 
ScopeManager added earlier in the stack but with new analysis built using the 
new visitor trait. The logic is a rough port of 


https://github.com/facebook/hermes/blob/main/tools/hermes-parser/js/hermes-eslint/src/scope-manager/referencer/Referencer.js 

Lots of bits are still missing, i'm starting with the parts that Forget needs.
2023-08-04 09:45:56 -07:00
Joe Savona
c6ae535e94 [rust] Codegen AST visitor
Updates `estree` codegen to emit a Visitor trait (temporarily named `Visitor2` 
since there is a hand-rolled one that some code is using). We use knowledge of 
the grammar to only visit fields whose type is a Node or Enum, or an "object" 
type that opts into being visitable. The latter is used for Function and Class. 

This will make it much easier to write the semantic analyzer.
2023-08-03 17:00:02 -07:00
Joe Savona
a67fefdebd [rust] Data structures for semantic analysis
This is two things: 

* A toy semantic analysis that handles a tiny subset of JS, including labeled 
statements, labeled break/continue, and variable 
declaration/reference/reassignment. This only exists as a way to prove out the 
API for the more important bit: 

* More importantly, this defines a data model for the semantic analysis results 
and an API for building up the semantic analysis. 

Subsequent diffs will replace the first bit (toy analysis impl), while keeping 
the second part.
2023-08-03 12:41:50 -07:00
Joe Savona
8042970ea4 [rust] Scaffolding for semantic analysis 2023-08-01 15:55:53 -07:00
Sathya Gunasekaran
3e9e3a1c92 [e2e] Add test for useState 2023-08-04 15:50:42 +01:00
Mofei Zhang
015029dc65 [patch] Fix for constant propagation bug in VR Store
--- 

Changes in `@enableOptimizeFunctionExpressions` caused a bug in the last Forget 
sync to VR Store. The repro can be summarized to something like this: 

```js 

function foo() { 

const x = true; // some constant or global 

// Add some branching for type inference 

// This can be a Logical expression as well (e.g. `4 || 5`) 

if (...) { } 

// In this HIR block, SSA inserts a `x$2 = phi(x$1, x$1)`. 

// EliminateRedundantPhiNodes needs to rewrite all references of `x$2` to `x$1` 

const accessXInLambda = () => x; 

return accessXInLambda; 

}
2023-08-02 17:30:57 -04:00
Mofei Zhang
50b0954279 [tests] Add typed CompilerOptions for e2e tests 2023-08-02 17:30:56 -04:00
Mofei Zhang
2a5a8d552f [tests] Revive Forget e2e tests
--- 

Revives e2e test infra from #587. 

- All React component-like functions are compiled. 

- `yarn jest` runs each e2e test twice (forget and no forget) 

Github Actions is already running `yarn test`, which includes all jest tests 

``` 

Run yarn test 

yarn run v1.22.19 

$ yarn workspaces run test 

> babel-plugin-react-forget 

$ yarn jest && yarn snap:build && yarn snap 

$ tsc && jest 

PASS main src/__tests__/Result-test.ts 

PASS main src/__tests__/DisjointSet-test.ts 

PASS e2e with forget src/__tests__/e2e/hello.e2e.js 

PASS e2e no forget src/__tests__/e2e/hello.e2e.js 

Test Suites: 
[4](https://github.com/facebook/react-forget/actions/runs/5732016200/job/15534129231?pr=1881#step:8:5) 
passed, 4 total 

Tests:       23 passed, 23 total 

Snapshots:   11 passed, 11 total 

Time:        
6.1[5](https://github.com/facebook/react-forget/actions/runs/5732016200/job/15534129231?pr=1881#step:8:6)3 
s 

```
2023-08-02 17:30:55 -04:00
Lauren Tan
2d8da0d32d [babel] Don't console.error on unknown options
Internally these are considered errors in our pipelines
2023-08-02 12:34:18 -04:00
Joe Savona
203d5ad43b [rust] ESTree cleanup 2023-08-01 13:55:19 -07:00
Joe Savona
c09bfff04c [rust] Tests for HermesParser->ESTree conversion 2023-08-01 13:55:17 -07:00
Joe Savona
e353f3702e [rust][wip] Codegen conversion from HermesParser (C++) 2023-08-01 13:55:13 -07:00
Joe Savona
ce2725eda5 [rust] Very rough sketch of consuming Hermes parser results 2023-07-28 16:12:18 -07:00
Joe Savona
b61a47eb2c Parser benchmark 2023-07-28 16:12:17 -07:00
Joe Savona
01ad9d895e [rust] Remove arena allocator
Using an arena allocator can be faster, but it comes with several challenges: 

* It requires tediously tagging nearly every value with a lifetime. Any function 
that has to deal with arena-allocated data (which is every meaningful function) 
ends up with a lifetime parameter. Bleh. We also have to thread the allocator 
itself wherever we need to allocate, though this is less of a problem since 
_most_ functions already need the Environment and we can store the allocator 
there. 

* There is not yet widespread support in the Rust ecosystem for using custom 
allocators with custom data types. This means that things like HashMap/Set and 
IndexMap/Set can't be arena allocated. This means that either we have to add 
support to these data types (by upstreaming or forking) or just accept that 
we're only partially using the arena allocator. 

* Finally, `bumpalo`'s `Box` cannot be moved out of, which turns out to be an 
annoying limitation that i've already had to work around several times. 

In the end i'm not sure arena allocators are worth it at this stage of the 
project. Relay Compiler has been successful without one, and other Rust-based 
internal compilers get by without them too.
2023-07-28 16:12:17 -07:00
Joe Savona
0e4656ac1c [rust][wip] swc->estree for jsx 2023-07-28 16:12:16 -07:00
Lauren Tan
5230e6d849 Add failing test case for ValidateNoSetStateInRender bug
The current heuristic to check if setState is called in render is based on 
whether the lambda containing the call to setState has a mutable range that got 
extended. This doesn't seem to work in all cases so this validation needs a bit 
more work before we can turn it on by default
2023-07-27 17:08:01 -04:00
Lauren Tan
e77d975b16 Flag ValidateNoSetStateInRender
This needs a bit more work before we can turn it on by default, see the next PR 
for a failing test case.
2023-07-27 17:08:01 -04:00
Lauren Tan
12e6cee06f [eslint-plugin] Update hermes-parser to 0.15.0
This includes a fix where the HermesToBabelAdapter was incorrectly outputting a 
`ClassMethod` instead of `ClassPrivateMethod` in certain scenarios. This was 
causing issues with our eslint plugin as it would crash on any file containing 
private methods because a malformed AST was formed.
2023-07-27 11:46:50 -04:00
Lauren Tan
20b175fcd5 [ci] Pass correct flags to yarn install
Turns out `--immutable` and `--immutable-cache` are for yarn 2, yarn 1.x needs 
to use the `--frozen-lockfile` flag instead
2023-07-27 11:06:29 -04:00
Lauren Tan
f45a4611e7 [ci] Don't run build on react
Building artifacts shouldn't be required for the tests, this should shave off a 
few minutes from CI
2023-07-27 11:06:27 -04:00
Lauren Tan
8405d8644f [eslint] Add plugin-proposal-private-methods
There was a bug in our internal Hermes to Babel adapter which caused some 
malformed AST to be constructed, which babel would then validate as being 
incorrect. That bug was fixed, but we still have to add this plugin so that 
private class methods can be parsed by babel. 

Tested internally
2023-07-27 10:52:28 -04:00
Joseph Savona
6912c31056 [CI] Run cargo build/test
Adds GitHub actions to build and test the Rust version of Forget on every commit 
to main. I didn't enable this on PRs for now just to avoid slowing down other 
folks, this seemed like a reasonable compromise while we're still in the 
experimentation phase. I test locally, and CI will catch if I or anyone else 
slips up.
2023-07-26 14:01:57 -07:00
Joe Savona
d4a1356eca [rust] swc->estree for optional chaining
Handles conversion of optional chains from swc -> estree.
2023-07-25 16:56:29 -07:00
Joe Savona
8bc6184728 [rust] deny unused fields on non-node structs
Rejects unknown fields during deserialization of helper structs (which don't 
have a `type` field). This would be super useful for AST `Node` types, too, but 
that requires implementing a custom deserializer so i'm punting on that for now.
2023-07-25 16:22:57 -07:00
Joe Savona
f21270a97d [rust] Complete estree through ES2021
Adds more AST types to handle the full ES2021 spec. At least, in theory I 
defined the schema correctly, and we'll have to just fix any bugs as we 
encounter them.
2023-07-25 15:58:58 -07:00
Joe Savona
90c3a3866d [rust] Improve estree serialization
Updates to use our own codegen'd `Serialize` implementation for AST node types. 
Notably, this allows us to ensure that we always emit a `type` field, even when 
the type is obvious from context (serde doesn't do this) and avoid 
double-emitting `type` fields (which serde can do if you try to force a tag to 
be emitted). We still use the Deserialize impl because it works fine for our 
purposes.
2023-07-25 15:16:22 -07:00
Joe Savona
c19286fce7 [rust] More es2015 ast types
Adds almost all the remaining AST types from ES2015 to `estree`, and updates the 
swc->estree->hir conversions accordingly. In a few places i punted with a 
`Diagnostic::todo()`.
2023-07-25 14:08:09 -07:00
Joe Savona
59c1f22057 [rust] port inline_use_memo
Ports InlineUseMemo to Rust, this is the only missing transform pass on the 
pipeline up through constant propagation. I wanted to finish this so that we 
could do benchmarking of the early phase of compilation. UseMemo does a bunch of 
rewriting so it seemed worth comparing performance. 

Included: 

* Add swc -> estree and estree -> HIR conversions for arrow functions and call 
expressions 

* Add HIR definition for labeled terminals and call expressions 

* Utility for inlining one function into another — note that this requires 
remapping InstrIx operands since instruction indices will change. An alternative 
would be to store all the instructions for both outer/inner functions in the 
same array, in which case we wouldn't need to remap. 

* Helpers for mutably iterating the operands of an instruction or terminal. 

* The actual inline_use_memo() function. The overall logic is similar, i just 
had to slightly shuffle the order of operations to satisfy the borrow checker.
2023-07-25 09:24:19 -07:00
Sathya Gunasekaran
4976f5c1be [deps] Update ts-jest version
Console warnings when running tests: ``` ts-jest[versions] (WARN) Version 5.1.3 
of typescript installed has not been tested with ts-jest. ``` 

Our typescript version is ahead of what's supported in ts-jest. This PR updates 
ts-jest to work with the latest typescript compiler.
2023-07-26 12:11:53 +01:00
Sathya Gunasekaran
25527b3c0a [hir] Add support for holey arrays
This PR adds a Hole kind that can be present in both ArrayExpression and 
ArrayPatterns. 

This Hole type is not interesting for our inference passes and is skipped over 
for all of the pipeline.
2023-07-26 12:03:19 +01:00
Sathya Gunasekaran
dcb6549dda [hir] Bailout when reading from React namespace
Forget doesn't understand the React namespace object and generates incorrect 
code when compiling code that loads props from this namespace object. 

This PR makes Forget bailout when we see a property load from React namespace 
object.
2023-07-25 16:08:32 +01:00
Sathya Gunasekaran
3f0afcbe47 [test] Failing test for using hooks from React namespace
Forget assumes hooks are imported and used directly without the React namespace 
and generates incorrect code when if there's a namespace.
2023-07-25 15:56:46 +01:00
Sathya Gunasekaran
82e426e229 [CI] Run react test suite on every PR
Add a new workflow to run test suite to run on every PR. 

Updates the build step to just build the UMD variant of react & react-dom for 
the tests.
2023-07-25 13:34:50 +01:00
Timothy Yung
49cf3b4038 [deps] Upgrade to hermes-parser@^0.14.0 and prettier@2.8.8
Upgrades every dependency on `hermes-parser` and `prettier` to the versions that 
we're using in the rest of our other codebases. Notably, these package versions 
are necessary for our other codebases to make use of mapped types in Flow.
2023-07-21 09:19:17 -07:00
Timothy Yung
fc9db4c487 [ez] Delete unnecessary yarn.lock files
8d021e5d797c5ef3b4e18a0be735c04005f2ae7a configured `./forget` as a workspace 
root, so these `yarn.lock` files in `app/*` and `packages/*` are no longer 
relevant. This PR deletes them to reduce noise and confusion.
2023-07-21 09:15:28 -07:00
Lauren Tan
5ac909172e Validate unconditional setState in render 2023-07-17 13:00:38 -04:00
Joe Savona
bac24ffd78 [rust] Use new block helper for more passes 2023-07-14 22:47:21 +09:00
Joe Savona
de0c2393ad [rust] MergeConsecutiveBlocks and block helpers
Ports `MergeConsecutiveBlocks` to Rust. This was a tricky one: as we iterate 
through the blocks _if_ the block ends up being merged with its predecessor we 
need to consume it and modify its predecessor block (ie, mutating two things 
from the same data structure - shared mutation!). But if we _don't_ need to 
merge, then we need to not drop the current block. Ie, we sort of need to 
conditionally take ownership of the current block during iteration and put it 
back. 

I added a `BlockRewriter` helper type for this which has a helper to iterate 
safely. It calls the iterator lambda, moving blocks one at a time into the 
lambda. The lambda returns either `Keep(block)` to give the block back and keep 
it or `Remove` to tell the rewriter to drop the block. Thanks to making the 
Blocks data structure hold `Option<Box<BasicBlock>>` items, "moving" the block 
actually just means nulling out the option and conceptually giving ownership of 
the pointer to the callback - the data itself never moves. 

This involved creating a custom `Blocks` wrapper type, which i had been putting 
off doing. This cleans up a bunch of other logic around traversing blocks.
2023-07-14 18:00:16 +09:00
Joe Savona
32286b74ac [rust] enter_ssa uses new diagnostic type 2023-07-14 15:56:53 +09:00
Joe Savona
a39545a575 [rust] mark generated code, skip formatting 2023-07-14 15:46:57 +09:00
Joe Savona
0a79974d0e [rust] Extract HIR initialization passes to hir crate 2023-07-14 15:46:51 +09:00
Joe Savona
19b0a49967 [rust] add+apply rustfmt config 2023-07-14 15:33:06 +09:00
Joe Savona
18f7481ebf [rust] General-purpose diagnostic type and helpers
This PR adapts the `Diagnostic` type and helpers from Relay Compiler to Forget. 
The main changes are: 

* Removing some fields it doesn't seem we'll use for a while, if ever (like 
machine-readable arbitrary key/value data) 

* Switching from Relay Compiler's `Location` type to our SourceRange type 

* Using the severity enum previously established in the `forget_build_hir` 
crate, with Todo/Unsupported/InvalidSyntax/InvalidReact/Invariant variants 

* Adding support for translating our `Diagnostic` into a `miette::Diagnostic` so 
we can use miette's pretty printing 

With the new Diagnostic type in place i updated the existing build_hir code to 
use it and confirmed that the errors are now even nicer (when we attach extra 
data to annotate labels): 

<img width="860" alt="Screenshot 2023-07-14 at 3 10 00 PM" 
src="https://github.com/facebook/react-forget/assets/6425824/9d29425a-938b-4872-b999-aa174a3c329a"> 

This addresses (or brings us closer to addressing) many of your comments on the 
last diagnostics PR, @poteto!
2023-07-14 15:21:45 +09:00
Joe Savona
3e317b8bbf [rust] Contributing guide and readmes for every crate
Per the title, this updates the main readme file with a guide to contributing to 
the Rust compiler, and ensures that we have a brief description of every crate 
in local readme.md files. The `forget_hir` one is the most extensive and 
describes the high-level design of the HIR.
2023-07-14 12:24:19 +09:00
Joe Savona
68b0effca3 [rust] Workspace hygiene
I've primarily used what Cargo calls virtual workspaces, where the top-level 
Cargo.toml just lists a bunch of packages and they each have their own 
dependencies. This is fine, but i've noticed that more repos are using real 
workspaces and they offer a bunch of benefits. You define the dependencies in 
the top-level Cargo.toml and then can easily refer to them from multiple crates, 
ensuring all the versions match up. It makes it easier to refer to other crates 
in the workspace, too, because you define the path once at the root, then every 
other crate can just say `forget_foo = { workspace = true }`. 

I also renamed all the crates to be prefixed with `forget_`, in some cases 
removing the redundant `hir` name, So `hir-optimization` became 
`forget_optimization`, `hir-ssa` became `forget_ssa`. Also note the switch from 
hyphenated names to underscores everywhere, since at the end of the day you have 
to write the name with underscores in source code. 

I also deleted the demo crate that i started with since we don't need it 
anymore. 

And finally, i added an explicit publish = false to all the crates just to 
prevent mistakes.
2023-07-14 11:39:46 +09:00
Joe Savona
8ed6c6ab08 [rust] simplify function context visitor 2023-07-14 10:46:42 +09:00
Joe Savona
ea22dfe0b7 [rust] update estree snapshots 2023-07-13 16:07:53 +09:00
Joe Savona
19ba002a12 [rust] First pass of function context analysis
This is a quick "good enough" first pass at computing function expression 
context variables. It definitely needs to be overhauled, but it's enough to make 
a lot of common cases work correctly. 

First, this PR adds a hand-rolled Visitor trait for `estree`. Long-term that 
should probably be code-generated, but there are some subtleties to it such as 
the `visit_lvalue(callback)` helper which has to be wrapped around various calls 
(or we need some other way to distinguish identifiers within lvalues from 
identifiers within rvaluess). So for now it makes sense to hand-roll it until we 
are more confident in exactly how it should work. 

Given that visitor, i was able to port part of the existing 
`gatherCapturedDeps()` to Rust with some modifications. Note that we assume 
_something_ has run name resolution on the estree to match up identifiers to the 
declaration they refer to. But we don't store information about parent scopes so 
we can't walk up to check where things were defined. Instead we do the 
following: 

* Build up a list of all referenced identifiers 

* Also build up a set of bindings defined in the function itself 

* After visiting, filter our the first list to only include identifiers not 
defined by the function itself, and to eliminate duplicates. 

This covers the majority of cases: the most obvious gap is nested function 
expressions though actually that should just work.
2023-07-13 15:57:27 +09:00
Joe Savona
f3d40c8c24 [rust] Recurse into functions in other passes
Updates eliminate_redundant_phis and constant_propagation to recurse into 
function expressions. I also realized there was a bug in EliminateRedundantPhis 
in which we wouldn't traverse into function expressions encountered after 
finding a back edge, so i fixed that logic in both versions.
2023-07-13 13:34:10 +09:00
Joe Savona
4762208b0f [rust] enter_ssa recurses into function expressions
Updates `enter_ssa()` to recurse into function expressions. In the TS compiler 
we use a single Builder instance and copy (references to) the function 
expression's blocks into the builder. That type of sharing just does not play 
nicely with Rust. But... we don't need to do that! We already know the context 
variables of the function expression, so we can lookup each of them to find 
their re-mapped identifier, and set that as the starting state for the entry 
block of the function expression. That lets us use normal recursion and 
otherwise not share any information between the outer and inner builders. 

Of course to make this work we actually have to populate Function.context, but 
the algorithm _should_ work.
2023-07-13 12:24:55 +09:00
Joe Savona
c16124f255 [rust] Helpers for arena allocating box/vec 2023-07-13 11:26:42 +09:00
Joe Savona
dbe1af601b [rust] Start of function expression support
Start of function expression support: 

* Basic structure for representing function expressions in the HIR 

* Printer support 

* swc -> estree -> hir conversion for function expression _bodies_. Dependencies 
and context are not handled yet.
2023-07-13 11:14:42 +09:00
Joe Savona
1d5d89b8be [js/rust] Improve constant propagation
Makes the same improvement to constant propagation in both the JS and Rust 
versions. The core algorithm only populates phi variables if all operands have a 
known value (no back edges) and all those values are the same: this allows us to 
propagate constants in most cases and simply punts on handling propagating 
values that are affected by loops. However, since we collapse if statements into 
gotos when the test condition is a constant, there can be cases where a phi that 
originally existed will be pruned out: 

``` 

// bb0 

let x1; 

if (true) { 

// bb1 

x2 = 1; 

} else { 

// bb2 

x3 = 2; // this block becomes unreachable 

} 

// bb3 

x4 = phi(bb1: x2, bb2: x3); // this phi will get pruned s.t. x4 = x2 = 1 

return x4; 

``` 

However, the algorithm doesn't prune phis until _after_ applying constants, so 
currently we would see this phi node w different inputs and not propagate a 
constant for the final usage of x, even though it will clearly be `1`. 

The change is to make constant propagation use fixpoint iteration, iterating so 
long as terminals changed on the previous iteration. If no terminals change the 
algorithm completes in a single pass, but if terminals do change then we update 
phis and continue. As you can see from the new test case this allows us to find 
arbitrary length sequences of values and terminals that can be pruned.
2023-07-13 09:53:36 +09:00
Joe Savona
8ada08f11e [rust] Port constant propagation
Ports constant propagation to Rust. The algorithm is broadly similar to the TS 
version, and most of the differences come from the slightly different HIR data 
model (operands are instruction indices not identifier ids). What this means is 
that the Constants map that we build up is really only used for variables that 
existed in the original program, and only comes into play with instructions like 
LoadLocal and StoreLocal. Other instructions such as Binary just look up their 
operands directly, ie they load the referenced instruction to check if both 
left/right are primitives. 

Note that with SSA form and the index-based operands we could actually get rid 
of StoreLocal/LoadLocal completely, which would further simplify constant 
propagation. However: 

* we'd need to add a Phi instruction kind, not a big deal but it diverges even 
more 

* more importantly, it makes it super hard to implement LeaveSSA 

That second point is a deal-breaker so unless someone has a great idea for how 
to exit SSA form without having Load/Stores, let's keep them.
2023-07-12 17:33:54 +09:00
Joe Savona
2fb14138d4 [rust] Port EliminateRedundantPhis
This is a nearly 1:1 port of EliminateRedundantPhis to Rust, the algorithm is 
identical and all differences are superficial. There are few things missing (an 
invariant instead of a panic in one place, recursing into function expressions) 
but the Rust version is still going to end up shorter despite keeping all the 
comments.
2023-07-12 14:54:49 +09:00
Joe Savona
e029e22a9a [rust] fix lints 2023-07-12 13:01:42 +09:00
Joe Savona
c7ccc5dd30 [rust] Initial port of EnterSSA
This is a first pass at porting EnterSSA to Rust. First pass in the sense that 
it's hard to fully test it, and also in the sense that we'll likely figure out 
even better ways to work w the HIR as we iterate. Oh and i didn't do recusing 
into function expressions yet, since we can't even represent function 
expressions yet, and that may require modifying the design a bit (though i have 
an idea that i think will work, which is for the Builder to have an optional 
parent. When we encounter a block with no predecessors, we check the parent. I 
_think_ this will make all the borrowing "just work"). 

A few notes: 

Rust's compilation model parallelizes and incrementally computes at the crate 
granularity, so builds are faster if we split up our code into more but smaller 
crates. Setting up a clean dependency graph can dramatically improve build 
performance too. For example, to run the `fixtures` tests we can build `estree` 
and `hir` in parallel, then once those build _all_ of our other crates can be 
built in parallel until we get to `fixtures` which depends on the everything 
else. The various passes don't have any build dependencies on each other so they 
can build in parallel. Hence the new code for SSA stuff is in a separate 
`hir-ssa` crate. We should similarly group other passes (approximately one crate 
per folder in the babel-plugin-react-forget/src/ directory, eg SSA, Inference, 
Optimization, etc). 

Second, shared mutable ownership can be modeled in Rust but requires wrappers 
such as `Rc<RefCell<>>`. It's generally more efficient and more idiomatic to 
rethink the data model and algorithm. For EnterSSA, the Builder object holds a 
reference into the HIR that it only ever reads, and the pass (which drives the 
builder) also holds a reference into the HIR, which it mutates. The previous PR 
split up Blocks and Instructions, and the value of that is more apparent in 
`enter_ssa()`. The Rust equivalent of the builder holds a _shared_ (immutable) 
reference to just the HIR's blocks, while the pass (driving the builder) holds a 
_unique_ (mutable) reference to just the HIR's instructions. This lets us keep 
the overall feel of the algorithm while keeping Rust happy. 

Also note that the other change — to making operands be InstrIx indices into the 
instructions array — means that the SSA logic is simpler. Most instructions 
don't have to be visited at all, since they don't deal with loads/stores. 
Terminals also don't need to be visited, since they reference instructions, not 
identifiers. The Phi concept seems to just work too. 

I also updated the printer to print predecessors and phis.
2023-07-12 12:38:13 +09:00
Joe Savona
e9d2822e92 [rust] New data model for HIR
This change is motivated by starting to explore porting EnterSSA to Rust. It's a 
good medium complexity pass and quickly demonstrates why a direct port of our 
existing data model and algorithms won't work so well. For examples just these 
first lines at the top of the transform create multiple references to the 
function body/blocks: 


58da89888e/forget/packages/babel-plugin-react-forget/src/SSA/EnterSSA.ts (L230-L231) 

But more generally it's always felt wrong that instructions have an LValue that 
isn't really used. So here i'm exploring making operands a newtype index into a 
single instructions array (shared for the entire function), and using different 
types for identifier references in SetLocal and LoadLocal. Incidentally this 
also declutters the printed HIR quite a bit. 

I'm not going to land this until i actually finish enter_ssa() and some other 
passes. We definitely need to balance fidelity to the existing code (to 
facilitate porting) with using idiomatic Rust (to facilitate porting in the 
sense of not fighting the borrow checker).
2023-07-11 16:59:08 +09:00
Joe Savona
f452ed2971 [rust] estree support for jsx 2023-07-11 14:08:46 +09:00
Joe Savona
61d7e6ae3d [js] optimize function expressions by default
This mode improves compilation and makes optimizations easier, let's make it the 
default. I previously confirmed that enabling this mode didn't affect output 
when synced internally, and I'll do that again before landing the PR.
2023-07-11 13:06:13 +09:00
Joe Savona
830127f680 [js] Remove InstrValue::ExpressionStatement
I added this as a quick workaround, since we didn't support unused 
logical/conditional expression statements. Now that we handle them we don't need 
InstructionValue::ExpressionStatement anymore. I found this when porting our 
lowering to Rust.
2023-07-11 11:52:33 +09:00
Joe Savona
bdd50d522d [rust] Update swc->estree->hir conversion for new estree
The previous PRs to make `estree` use codegen broke the swc->estree and 
estree->hir conversions. This PR updates those conversions so everything builds 
now.
2023-07-11 11:52:32 +09:00
Joe Savona
17ed6cc9a9 [rust] Custom deserialization for more precise errors
The overall goal of this workstream is to have a Rust representation of ESTree 
that we can use as the input and output of the compiler. In Rust environments we 
can convert between the native AST of SWC or OXC and ESTree, and when invoked 
from JavaScript we can serialize to/from ESTree-compliant JSON. Given that our 
first target is to plug into a JS-based compilation toolchain, we need to have a 
working serialization to/from ESTree JSON. The point of the codegen-based 
`estree` crate is to allow us to model estree as ergonomic, idiomatic Rust (to 
make consuming it in code easier) while also allowing us to serialize to/from 
spec-compliant ESTree. This PR flushes out one remaining piece. 

Updates our estree codegen to generate a custom `Deserialize` implementation 
instead of using the derived one from serde. ESTree has some enums whose 
variants are themselves enums, for example we have 

```rust 

enum ModuleItem { 

ImportExportDeclaration(ImportExportDeclaration), 

Statement(Statement), 

} 

enum ImportExportDeclaration { ... } 

enum Statement { ... } 

``` 

This sort of works with serde's derive implementation: you have to use the "tag" 
representation for the inner enums, and an "untagged" representation for the 
outer one (ModuleItem). The problem is that with an untagged representation, 
serde doesn't know what type of data it's expecting. All it can do is go one by 
one and try to parse the data as the first variant (eg ImportExportDeclaration) 
then the next one (Statement) and fail when it gets to the end of the list. If 
the data isn't valid for any reason, deserialization will fail with a "not a 
valid ModuleItem" error. That's true but not helpful, especially if you're 
developing estree, are confident that the input json is valid, and need to 
figure out where you messed up the definition. It's also not helpful as an 
end-user if you're not sure your input json is valid. 

So this PR updates our codegen to emit a custom derive implementation that is 
identical for both regular enums (like Statement) and recursive ones (like 
ModuleItem). We first extract the tag to know what type the value is, then 
deserialize exactly as that type. So in the above case, rather than have to 
first try parsing every ModuleItem as an ImportExportDeclaration and then fall 
through to statement, we just decode the tag (`type` in our case, for example 
say it's an "ForStatement"), then deserialize directly as that type (eg, as 
ForStatement), then wrap it in the enum variant (ModuleItem::Statement(...)). 
For recursive enums like ModuleItem we add an extra wrapper as necessary. 

The end result is that we get much more precise errors and deserialization is 
more efficient: we always decode just the tag, then as exactly that type. 

Note that our serialization is also not perfect right now, because we don't 
always emit the `type` key. Serde only emits it when a value appears in an enum. 
We can similarly generate custom serializers for all our types to always emit 
the tag. That will be straightforward when it's necessary. The current PR was 
more of a blocker, because it was really hard to figure out mistakes in the 
estree definition given the ambiguous errors. Thanks to this PR we now get 
precise errors along the lines of "unknown type `JSXElement`" which are easy to 
resolve.
2023-07-08 23:00:01 +09:00
Joe Savona
1abea6c49d [rust] allow complex types in estree codegen
Uses the `syn` crate, which can parse various Rust syntax forms, to parse the 
`type` field from json schema description. This allows us to describe complex 
types like `"type": "Vec<Option<ArrayElement>>"` directly, rather than requiring 
flags like nullable, plural, and nullable_item. The main flag that i'm keeping 
is "optional", which is used to indicate when the field itself (not the value) 
is optional.
2023-07-08 23:00:00 +09:00
Joe Savona
3983e882b8 [rust] estree codegen cleanup 2023-07-08 22:59:59 +09:00
Joe Savona
750b1e6adb [rust] Partial ES2015 support in estree codegen
Adds some parts of the ES2015 spec, such as imports and ForOfStatement. This is 
enough to get a few more fixtures compiling. The last one uses JSX which I 
haven't defined yet.
2023-07-08 22:59:58 +09:00
Joe Savona
0c1193ecd7 [rust] estree codegen for improved serialization
This is meant to replace the initial `estree` crate with a version that is 
generated from a JSON description of ESTree. The idea is to make it easy to 
experiment with slightly different representations to balance ergonomic usage of 
the data at runtime with serialization compatible with ESTree spec. The JSON 
schema looks like this (somewhat abbreviated): 

``` 

{ 

// Objects are struct types that don't have a `type` and can't appear as an enum 
variant 

objects: { 

Position: { 

line: {type: "NonZeroU32"}, 

column: {type: "u32"} 

}, 

... 

}, 

// Nodes are struct types with a `type` and which can appear as enum variants 
(statements, expressions, patterns, etc) 

nodes: { 

ArrayExpression: { 

elements: { 

type: "Expression", 

plural: true, 

nullable_item: true, 

} 

} 

... 

}, 

// Categories of nodes with multiple variants, represented as enums. 

// Can be recursive, eg ForInit can be VariableDeclaration or Expression, 

// where Expression is also an enum 

enums: { 

Expression: [ 

"ArrayExpression", 

... 

] 

}, 

// Simple enums which have a corresponding string value. Used primarily for 
operators (binary/unary/logical/etc) 

// but also for things like variable declaration kind (var/const/let) 

operators: { 

BinaryOperator: { 

Plus: "+", 

Instanceof: "instanceof", 

} 

} 

} 

``` 

The core estree files are now generated using Cargo's build script mechanism. 
Right now i only defined the types and fields from ES5, so i'll have to flush 
out the rest of the modern JS spec and extensions like JSX, TypeScript, and 
Flow. But already the for-statement example works, showing that this approach 
can handle complex cases such as unions of types or other unions (ForStatement 
initializer is tricky bc it can be a VariableDeclaration or an Expression - that 
works now!). 

This is still WIP a bit - now that the ESTree definition is more precise i can 
go back and clean up some other code (have to, because the swc -> estree 
conversion needs some tweaks now).
2023-07-08 22:59:56 +09:00
Joe Savona
a32a2baa73 [rust] Start of error handling
Until now i've freely used `panic!`, `unwrap()`, and friends for "error 
handling". This PR switches to consistently returning `Result` within the HIR 
builder, using a structured error representation that exploits helpers from 
`thiserror` and `miette` crates. Miette has a super graphical formatter for 
diagnostics as you can see in the screenshot (also see the 
[repo](https://docs.rs/miette/5.9.0/miette/index.html)). 

This is just a first pass and we'll need to flush out the error handing story 
more. Two obvious directions to go next: 

* Make HIR construction error-tolerant, so that it can find as many errors as 
possible at once rather than failing on the first error. We did this in Relay 
Compiler as well, and we can likely borrow some of its helpers. 

* Decouple from `miette`. It's very nice but less flexible than I'd like. We can 
define our own more generic diagnostic type that contains structured data, then 
have a generic conversion mechanism into a miette type so we can use their 
display logic. 

<img width="789" alt="Screenshot 2023-07-06 at 3 55 38 PM" 
src="https://github.com/facebook/react-forget/assets/6425824/e1f1ed4b-5188-4af5-9af4-8f6c5c345023">
2023-07-08 22:59:55 +09:00
Joe Savona
8176f25719 [rust] ForStatement support
Implements support for `ForStatement` from swc -> estree -> hir, flushing out 
more of the HIR representation and porting pieces from HIRBuilder as necessary.
2023-07-08 22:59:54 +09:00
Lauren Tan
58a8d0bdc7 [ez] Update gitignore for Rust 2023-07-06 16:25:18 -04:00
Sathya Gunasekaran
853d0b1b69 [fuzzer] Make fuzzer work with Forget
Remove v8 specific bits. It's still not very useful as we barely have any tests 
that can be used as input corpus.
2023-07-06 11:44:14 +05:30
Sathya Gunasekaran
31ec959e9b [fuzzer] Initial import of v8's fuzzer
Copied from  
https://chromium.googlesource.com/v8/v8/+/master/tools/clusterfuzz/js_fuzzer/
2023-07-06 11:44:14 +05:30
Joe Savona
1bf00e6b6e [rust] lint 2023-07-06 09:24:53 +09:00
Joe Savona
357e9de19b [rust] IfStatement support
Lowering for IfStatements, which includes porting some of the HIRBuilder helpers 
like enter() and reserve().
2023-07-06 09:24:52 +09:00
Joe Savona
c59ac387ae [rust] Assignment expressions 2023-07-06 09:24:51 +09:00
Joe Savona
d99da01698 [rust] basic VariableDeclaration support 2023-07-06 09:24:50 +09:00
Joe Savona
2eb7c897f3 [rust][ez] cleanup 2023-07-06 09:24:49 +09:00
Joe Savona
f6957e9a32 [rust] Partial handling of identifiers
Fundamentally this PR is about lowering identifiers during construction of HIR. 
For now i'm punting on context variables and assuming all variables are either 
global, module-scoped, or locals. The implementation involves a few pieces: 

* `estree::Identifer` gets extended with optional binding information. The idea 
is that _some_ name resolution mechanism will populate this. Eventually our own, 
but we can also borrow data from another source... 

* `estree-swc` now configures SWC's (possibly broken?) name resolution mechanism 
and sets the above binding data when translating identifiers from swc into our 
estree format. 

* hir `Builder` tracks identifiers based on `(name, BindingId)` pairs, and 
assigns a unique `hir::Identifier` instance for each pair. `Identifiers` are 
clone-able (shared). 

* Tangential: i updated the printer to handle more instruction variants, 
including the now-ported LoadGlobal instr. 

I don't love this but it's a start. Long-term we definitely should have our own 
name resolution mechanism which we run on the estree prior to lowering to HIR.
2023-07-06 09:24:48 +09:00
Joe Savona
08e51d3fd7 [rust] Pretty-printer for HIR
Implements a pretty-printer for the HIR and switches the fixture tests to use 
this instead of the debug format. It's much more readable now! 

Note that not all types are properly printed, I only implemented the 
instructions and terminals used in the example. For others we fall back to the 
Debug impl so we at least print something.
2023-07-06 09:24:47 +09:00
Joe Savona
d3b9948b5e [rust] Fixture tests for parsing and lowering
Adds a new `fixtures` crate intended for running end-to-end tests of the 
compiler. As we expand the compiler this will eventually match our JS fixture 
setup, where we have .js files as input and produce memoized JS output. 

For now, this does the following: 

* Parses with SWC (omg this was painful to setup) 

* Runs SWC's name resolution, which annotates the SWC ast in-place 

* Convert the SWC ast into our `estree` representation 

* Convert `estree` into `hir` for each top-level function declaration in the 
input program 

* Print the Rust `Debug` view of the resulting HIR 

As a next step i'll add a pretty-printer for the HIR to roughly match what we 
have in JS.
2023-07-06 09:24:46 +09:00
Joe Savona
1d2e7ee747 [rust] Use Rc<RefCell<>> for shared identifiers
Multiple Place instances can share a reference to a given Identifier in our JS 
implementation. For simplicity of the initial port I’m using Rc (for sharing) 
and RefCell (for runtime-checked mutability). This is the standard pattern for 
shared mutable references in Rust when you don’t need multi-threaded support. We 
don’t need HIR to be accessible by multiple threads so this is fine, if we do 
multiple threads it will be to parallelize compilation of separate functions. 

There are other idioms w less runtime overhead, such as Place holding an index 
into a separate vec of identifiers, but that would make the port much less 
straightforward.
2023-07-06 09:24:45 +09:00
Joe Savona
b1c8cb358b [rust] Start of lowering (BuildHIR)
Starts to port BuildHIR, in the Rust case this means the ESTree -> HIR 
conversion. This necessitated flushing out the Builder struct a bit more. Mostly 
the logic translates over very directly, and if anything it's cleaner because of 
the lack of noise dealing with TypeScript unsoundness for Babel typedefs and 
switch statements.
2023-07-06 09:24:44 +09:00
Joe Savona
2ac69bf7d9 [rust] Passes from HIRBuilder
This is a start to porting HIRBuilder, with a largely complete implementation of 
`build()`. Notably this includes all the passes which build() calls, and the 
helper functions those passes call in turn: 

```rust 

reverse_postorder_blocks(&mut hir); 

remove_unreachable_for_updates(&mut hir); 

remove_unreachable_fallthroughs(&mut hir); 

remove_unreachable_do_while_statements(&mut hir); 

mark_instruction_ids(&mut hir)?; 

mark_predecessors(&mut hir); 

``` 

This was pretty straightforward. I ran into one borrow checker issue with (iirc) 
`remove_unreachable_for_updates` where i needed to simultaneously hold a mutable 
reference into the HIR (to write the ForTerminal) and also get an immutable 
reference to check if the update block is reachable. The challenge is this: 

```rust 

for block in hir.blocks.values_mut() { 

^^^^^^^^^^^^^^^^^^^^^^^ hir borrowed mutably here 

if let TerminalValue::ForTerminal(terminal) = &mut block.terminal.value { 

if let Some(update) = terminal.update { 

if !hir.blocks.contains(&update) { 

^^^^^^^^^^ borrowed immutably here 

terminal.update = None; 

^^^^^^^^ mutable borrow still active here (and also bc of the loop) 

} 

} 

} 

} 

``` 

I quickly worked around this as we do in the other passes here by first building 
a set of the block ids contained in the function (so that the 
`hir.blocks.contains()` call becomes a call to the copied set of block ids). 

An alternative would be to add a helper function for mutable iteration which 
takes the desired value out of the data structure so that you can safely mutate 
it and reference the rest of the HIR. Usage might look like this: 

```rust 

hir.blocks.each_mut(|mut block, hir| { 

if let TerminalValue::ForTerminal(terminal) = &mut block.terminal.value { 

if let Some(update) = terminal.update { 

if !hir.blocks.contains(&update) { 

terminal.update = None; 

} 

} 

} 

}) 

``` 

The lambda would receive the current `block` as a mutable reference, and for the 
duration of the call `hir.blocks[block.id]` would be set to a sentinel value 
that would crash if accessed. Meanwhile, `hir` would be a readonly reference to 
the HIR, allowing the lambda to otherwise lookup information on the HIR but not 
mutate it. This seems...kinda fine? But also not immediately necessary as 
there's an easy and efficient-enough-workaround for the cases i've encountered 
so far.
2023-07-06 09:24:43 +09:00
Joe Savona
6c3792e8d5 [rust] Initial translation of HIR and related types
This is an initial translation of HIR (minus the ReactiveFunction bits). It's 
mostly a straightforward translation. A few differences: 

* Instead of Effect having an Unknown variant, we type `Place.effect: 
Option<Effect>`. Maybe i'll revert that but it seems right. 

* Terminal is divided into `Terminal { id: InstructionId, value: TerminalValue 
}` and `enum TerminalValue { ... }`, so that we can reference the terminal's id 
without caring about which kind of terminal it is. 

* Each instruction value variant gets a named struct, whereas in JS we had lots 
of anonymous InstructionValue variants. 

* Rust generators are still an unstable nightly feature and likely to change 
syntax enough that it seems safer not to rely on them. So 
`eachTerminalSuccessor()` returns a `Vec<BlockId>`. If the perf of this is bad 
enough we can switch to something like SmallVec (a popular crate) which stores a 
few elements inline on the stack to optimize for small vecs (which our case 
should be). 

Perhaps more importantly, what's _not different_: I kept the existing approach 
of BasicBlocks having a Vec of owned instruction instances, with 
InstructionValue variant operands as Places rather than indices into a shared 
instruction array. If this works out it will make porting straightforward. 

However there's one aspect of this that is incorrect: right now each `Place` 
owns its `Identifier`, whereas in JS the Identifier instances are shared mutable 
references. I'll definitely need to refactor the data structures to allow 
sharing Identifiers in some form, at which point we may want to change other 
things too.
2023-07-06 09:24:40 +09:00
Joe Savona
4c0122f4cf [rust] Small improvements plus snapshot testing for estree 2023-07-06 09:24:39 +09:00
Joe Savona
2b168ad673 [rust] SWC->ESTree conversion
Start of converting swc's representation into our estree format.
2023-07-06 09:24:38 +09:00
Joe Savona
4230eb63d8 [rust] ESTree representation with basic serialization support
Start of an `estree` crate for representing ESTree with serialization to/from 
ESTree JSON. To get the serialization to work quickly I took some shortcuts and 
uses a slightly less precise modeling. Longer-term we can write some custom 
serialization code and get more precise types (eg avoid the `ExpressionLike` 
enum in favor of proper Expression and ExpressionOrFoo types).
2023-07-06 09:24:37 +09:00
Joe Savona
292f07e19f [rust] SWC usage example
Basic example of parsing some code with SWC and then running name resolution.
2023-07-06 09:24:36 +09:00
Sathya Gunasekaran
d10885ae0e [snap] Blow up if testfilter is not found in filter mode
No more failing silently when testfilter file is not found.
2023-07-05 18:53:18 +05:30
Sathya Gunasekaran
4a8b36b8b1 [hir] Check for globals while lowering UpdateExpression
lowerIdentifierForAssignment does extra checks such as checking if globals are 
lvalues. 

UpdateExpression lowers into an assignment and previously we missed out on such 
checks.
2023-07-05 20:21:09 +05:30
Sathya Gunasekaran
8eca031508 [test] Add failing test for mutating global 2023-07-05 20:21:05 +05:30
Lauren Tan
6e6ecfc4d9 [eslint-plugin] Remove babel/traverse and babel/parser
These are unused since we switched over to hermes-parser
2023-07-03 12:25:37 -04:00
Lauren Tan
2a882d798b [eslint-plugin][ez] Remove unused scripts 2023-07-03 12:25:35 -04:00
Lauren Tan
c6b437b402 [eslint-plugin] Remove fbt dependencies
I incorrectly included these as deps, we were only using these to verify 
codegen. It seems fine to leave in but when I imported it internally vscode 
would error, so just remove it
2023-07-03 12:25:33 -04:00
Sathya Gunasekaran
cff435ca3d [plugin] Remove duplicate export statement 2023-07-03 15:51:05 +05:30
Sathya Gunasekaran
64637ac84c [playground] Refactor pragma parsing into separate function 2023-06-23 14:56:00 +05:30
Sathya Gunasekaran
3883eb9507 [playground] Make all options configurable 2023-06-23 14:55:59 +05:30
Lauren Tan
0892e305bc [eslint-plugin] Clean up error message
Omits some unncessary details from the error message surfaced in eslint
2023-06-28 11:43:56 -04:00
Lauren Tan
f850d3b265 Improve remaining InvalidReact error messages 2023-06-28 11:43:56 -04:00
Lauren Tan
dda59d093a Add InvalidJS error severity 2023-06-28 11:43:55 -04:00
Lauren Tan
3fb8937391 Add first example of error suggestion 2023-06-27 14:01:40 -04:00
Lauren Tan
448ed2b67b Refactor CompilerErrorDetails
- Made most static methods on CompilerError take a single options object   as an 
argument. With the exception of invariant which takes a condition and an options 
object. - Added a new `suggestions` field on CompilerErrorDetail, which we'll   
use to provide eslint suggestions - Updated eslint-plugin-react-forget to handle 
suggestions
2023-06-27 14:01:39 -04:00
Lauren Tan
c3d6789857 Reclassify existing errors
Most of these errors were incorrectly using InvalidInput as a catchall for 
rejecting code. I went through each one and manually updated them to be more 
accurate
2023-06-27 14:01:39 -04:00
Lauren Tan
6c4318e5c2 Add InvalidReact and InvalidConfig error severities 2023-06-27 14:01:38 -04:00
Sathya Gunasekaran
e762d5730b [test] Update stale test expectations 2023-06-23 14:51:38 +05:30
Sathya Gunasekaran
0897cc8a0f Add failing test for DropMemoCalls failing to DCE dep array
Found this while running Forget on the React tests. 

This isn't a high priority because the ESLint plugin would've caught this. But 
it'd be nice if either our validation rules caught this or if our compiler did 
correctly eliminate the dep array.
2023-06-20 14:58:36 +01:00
Sathya Gunasekaran
2ca614cb4f [playground] Sync playground flags to Store experiment flags 2023-06-23 14:36:56 +05:30
Sathya Gunasekaran
7caa18c4b9 [playground] Parse pragma in playground 2023-06-23 14:36:55 +05:30
Lauren Tan
dfb9e09984 [eslint-plugin][ez] Plugin options are const 2023-06-22 10:18:43 -04:00
Lauren Tan
9c033a481d [eslint-plugin] Only report on InvalidInput errors
Invariants aren't actionable by users, so we can omit reporting them in eslint 
as errors (while continuing to throw in the compiler itself)
2023-06-22 10:18:43 -04:00
Lauren Tan
e7e67f335a Use CompilerError.invariant consistently
This PR updates all current usages of invariant to CompilerError.invariant along 
with any available locs
2023-06-22 10:18:42 -04:00
Lauren Tan
e9faa5b90d SourceLocation can be null 2023-06-22 10:18:42 -04:00
Lauren Tan
3f2b346ba0 Make CompilerError.invariant assert the condition
This allows us to mimic the `invariant` api, which means you can just assert 
that something holds true after execution proceeds to the next line without 
throwing
2023-06-22 10:18:41 -04:00
Lauren Tan
6c425f6b36 Remove codeframe/nodepath options from CompilerError
These were effectively unused since we almost always passed in null when 
creating errors, so remove them and instead pass in a `loc` explicitly. The 
downside is that we no longer will see Babel codeframes in our test fixtures, 
which doesn't seem like a huge loss
2023-06-22 10:18:41 -04:00
Joe Savona
ba75c96077 Fix mutable range validation in PrintHIR
Fixes the mutable range validation in PrintHIR: when we checked the identifier 
scope's range we failed to allow end=start=0 cases which are also valid. 

While I was here, I created a separate assertion pass to check all ranges, that 
way we aren't relying on the printer to validate them. The pass is on for 
playground and tests, but disabled by default so it can't break internal apps.
2023-06-21 16:08:44 -07:00
Joe Savona
fa913b423e Distinguish internal invariant passes from user code validation
Distinguishes between two types of "validation" passes: 

- Passes which assert the validity of the HIR. These remain in HIR/ but are 
renamed "assertFoo". 

- Passes which validate that the user code is correct. These move to 
Validation/.
2023-06-20 15:16:57 -07:00
Joe Savona
bbb5840e73 Handle phi nodes for ValidateFrozenLambdas
Extends ValidateFrozenLambdas to find cases where a mutable lambda flows into a 
phi node and the phi is later frozen.
2023-06-20 15:16:56 -07:00
Joe Savona
6efd1f99d7 Make function name a string not an identifier
Fixes #1751. Function ids can be plain strings, and we can refer to them as 
globals rather than via a local identifier. In addition to the bug from $1751 
this also cleans up an existing todo.
2023-06-20 15:16:55 -07:00
Joe Savona
4a36b83796 Ref validation allows passing refs as props
Updates ValidateNoRefAccessInRender to allow passing refs to JSX, but continue 
disallowing passing ref values (so `ref` is okay but not `ref.current`)
2023-06-20 10:11:56 -07:00
Joe Savona
a9868cc2b1 Fix memoization of lambdas in @enableOptimizeFunctionExpr feature
This is a fix specifically for the `enableOptimizeFunctionExpression` feature 
(disabled by default). I tried running all of our fixtures with that flag on 
everywhere, and this was the only issue. It's actually extracted from another 
fixture which is more complicated, this is a distilled version. There were two 
bugs: 

* DCE was running after LeaveSSA when it needs to run before. Fixing the order 
means code inside the function expression stops getting removed. 

* In the feature flag, context variables share Identifier instances with the 
surrounding code, whereas they are distinct instances without the feature. This 
meant that mutable ranges from inside the function propagated outside the 
function, throwing off our inference. The fix was to reset the ranges of context 
variables after inferring the function expression's effects.
2023-06-16 14:42:19 -07:00
Joe Savona
04f4b50997 Integrate mutable context identifier inference, improve frozen function
inference 

Integrates the new inference to detect definitive mutations of context 
variables. This allows us to more precisely infer mutable function expressions 
and validate that they aren't passed where frozen values are expected.
2023-06-16 14:42:19 -07:00
Joe Savona
a2af8776a4 Add InferMutableContextVariables (unused)
New pass to infer context variables which are definitively mutated, 
distinguishing from context variables which _may_  have been mutated. See the 
code comment on the new pass for more details.
2023-06-16 14:42:18 -07:00
Joe Savona
59a1f2a5b0 AnalyzeFunction: ref values take priority over other mutations
Small tweak necessary for the subsequent PRs, refs and ref values take 
precedence over other mutations when computing the effect type of context 
variables. We always want to record ref access within a function as capture 
(since we have later validation) rather than a mutation. For now this has no 
impact, either order records a Capture. But it allows later diffs to make 
non-ref cases have other effects.
2023-06-16 14:42:17 -07:00
Joe Savona
278c7f56ba ValidateFrozenLambdas: visit terminals
The original version of the code wasn't checking return values. I missed this 
since my examples were passing functions _into_ the return value, as opposed to 
return the functions directly. This revealed some existing test fixtures that 
were technically invalid, but easy to fix by changing the return value.
2023-06-16 14:42:16 -07:00
Joe Savona
1db2619807 InferReferenceEffects: distinguish conditional mutations in HIR
In InferReferenceEffects, locations that are ConditionallyMutate are either 
recorded as a Mutate or Read, which means we lose the distinction btw 
conditional/unconditional mutation in later passes. This PR changes to remember 
that these places were conditionally mutable, used in later analysis.
2023-06-16 14:42:16 -07:00
Joe Savona
19976d5f83 [ez] Real location for function context values 2023-06-16 14:42:15 -07:00
Lauren Tan
f467774487 [babel-plugin][ez] Rename shouldCompile to shouldVisitNode 2023-06-20 12:30:07 -04:00
Lauren Tan
46fa83d854 [eslint-plugin] Disambiguate compiler errors without instanceof
Babel re-emits errors from plugins so the `instanceof` check was no longer 
working
2023-06-20 12:30:07 -04:00
Lauren Tan
2f0ab0ec9d [eslint-plugin] Temporarily disable validateFrozenLambdas 2023-06-20 12:30:06 -04:00
Lauren Tan
98633d10a6 Add a noEmit compiler option
Defaults to false, ie it runs the codegen pass. When enabled it will simply run 
all passes up to codegen and then skip over it. Naming of this option is copied 
from [TypeScript](https://www.typescriptlang.org/tsconfig#noEmit) which has the 
same named option that makes the compiler only perform typechecking. 

I'm adding this option primarily to get around some issues running the eslint 
plugin on Meta code. The plugin would error because Forget would report 
duplicate Babel AST nodes, which I presume would only occur during codegen. It 
should also make it a tiny bit faster to not run codegen, which is a nice plus.
2023-06-20 12:30:06 -04:00
Lauren Tan
79dbf6fc19 [eslint-plugin] Try using HermesParser 2023-06-20 12:30:05 -04:00
Lauren Tan
059365d726 [eslint-plugin] Temporarily only lint on 'use forget'
Temporarily enabling 'use forget' only mode for the linter to address #1751 so 
we can land the stack internally
2023-06-20 12:30:05 -04:00
Lauren Tan
5c0622af39 Add failing test for InferReferenceEffects bug 2023-06-20 12:30:04 -04:00
Lauren Tan
e378eed0f3 [eslint-plugin] Use babel plugin instead of manual traversal
This was causing some issues in the eslint plugin where the babel `hub` wasn't 
defined. afaik the hub is only setup when running the plugin as part of a babel 
pipeline, instead of a manual parse/traversal. We're using some of that infra 
for printing codeframes
2023-06-16 16:23:18 -04:00
Lauren Tan
a90b5371cc [eslint-plugin] babel-plugin-fbt is a dependency 2023-06-16 14:12:35 -04:00
Lauren Tan
daa23543ef [ci] Run all tests
CI will now run tests for all packages
2023-06-16 14:05:45 -04:00
Lauren Tan
0fc4c94ef1 Echo 'no tests' for packages without tests 2023-06-16 14:05:43 -04:00
Lauren Tan
a558b1536a [babel-plugin] buildFunctionDeclaration errors are todos
The existing errors thrown were marked as InvalidInput, which is now considered 
critical. This was causing an error in the sync since we had 2 occurrences of 
the errors being thrown. These are really todos and not invalid code.
2023-06-16 12:18:45 -04:00
Lauren Tan
501cf92f1e Unify UnsafeInput and InvalidInput 2023-06-15 15:13:37 -04:00
Lauren Tan
0d01d5f69c [eslint-plugin] Enable validation passes 2023-06-15 14:57:10 -04:00
Lauren Tan
0fdb1e5ac7 [eslint-plugin] Add test for invariants
Turning off this flag makes only critical errors throw, so TODO errors will no 
longer be surfaced by the plugin. The previously failing test for unsupported 
syntax is now valid.
2023-06-15 14:57:07 -04:00
Lauren Tan
87a39e1995 Mark InvalidInput errors as critical 2023-06-15 14:34:49 -04:00
Lauren Tan
b68c409a6b [eslint-plugin] Throw non CompilerErrors 2023-06-15 14:34:46 -04:00
Lauren Tan
fe91cd8ac1 [eslint-plugin] Parse flow 2023-06-15 14:34:43 -04:00
Lauren Tan
c402d8de76 [eslint-plugin] Fall back to context.getSourceCode()
Older versions of eslint use the deprecated method, so add a fallback in the 
event a modern version of eslint isn't available
2023-06-15 12:52:57 -04:00
Lauren Tan
c416fb16ca [make-read-only] Make tsconfig more consistent with other pkgs 2023-06-15 12:52:56 -04:00
Lauren Tan
6810ddbf56 [react-forget-runtime] Compile with typescript 2023-06-15 12:52:55 -04:00
Lauren Tan
b6c1ca9718 [build] yarn bundle:meta builds and uploads all packages
Some changes to our build infra to create a Meta bundle containing all packages. 
Needs corresponding changes in the internal upgrade commands
2023-06-15 12:52:55 -04:00
Joe Savona
aae9b01f91 Test cases for inferring mutability of function expressions (as values)
This PR contains just the test cases from #1717, showing some tricky cases 
around function expression mutability inference.
2023-06-15 09:32:30 -07:00
Joe Savona
8fcceb715c Rename feature flag for function expr optimization
This got lost in a rebase, just renaming for clarity.
2023-06-12 15:56:48 -07:00
Joe Savona
a21660de00 Fix calculation of nested function dependencies
We were incorrectly calculating the dependencies of nested lambdas, because the 
"component" scope was incorrectly set to the next closest parent rather than 
outermost React function (component/hook) being compiled.
2023-06-14 14:04:26 -07:00
Joe Savona
a8fff7cc5c Feature to optimize function expressions
This PR adds a new feature which enables additional validation/optimization of 
function expressions, gated by the `enableOptimizeFunctionExpressions` feature 
flag. When disabled, we actually revert the changes earlier in this stack, and 
do all our lowering of function expressions in AnalyzeFunctions. 

When the feature is enabled, we incrementally process function expressions in 
the various compilation stages, eg InferTypes infers into function expressions, 
ConstantPropagation propagates constants into function expressions, etc. Because 
this stage optimizes function expressions, in this mode codegen uses the HIR as 
the source rather than the original babel node. 

The feature is disabled by default so it has no impact on generated code. For 
now i've enabled the feature on just one test to demonstrate constant 
propagation into a function expression.
2023-06-08 18:05:41 -04:00
Joe Savona
4d7d2cf4dc Type inference across function expressions boundaries
Updates InferTypes to perform type inference across function boundaries. 
Specifically InferTypes is now responsible for driving type inference of 
function expressions (rather than deferring to AnalyzeFunctions to infer 
functions), and type inference now traverses into function expressions and 
infers types of free variables taking into account information from the outer 
context. This relies on the fact that identifier ids are consistent across 
function expression boundaries and that all free variables in functions are 
guaranteed to be effectively `const`, since we promote non-const variables used 
in function expressions to context variables.
2023-06-08 14:02:04 -04:00
Joe Savona
5775102b85 InferTypes traverses function expressions 2023-06-08 14:02:04 -04:00
Joe Savona
11d4ff430f ConstantPropagation traverses function expressions 2023-06-08 14:02:03 -04:00
Joe Savona
e39855d251 EliminateRedundantPhi traverses function expressions 2023-06-08 14:02:03 -04:00
Joe Savona
638e45c928 EnterSSA traverses function expressions 2023-06-08 14:02:02 -04:00
Joe Savona
c2c221a452 MergeConsecutiveBlocks traverses function expressions
Currently the process of lowering function expression bodies is deferred until 
AnalyzeFunctions. However, per the motivation of the previous PR, we'd like to 
be able to perform inference across function expression boundaries. To do that 
we need to use consistent identifier ids across function expression boundaries. 
This requires SSA conversion of function expressions at the same time as we 
enter SSA. 

To start, this PR moves MergeConsecutiveBlocks for function expressions into 
that phase.
2023-06-08 14:02:02 -04:00
Lauren Tan
3673272ca4 [eslint-plugin] Add basic test for unsupported syntax
Kinda works?
2023-06-07 15:41:22 -04:00
Mofei Zhang
246936ca57 [prettier] add expected prettierrc file 2023-06-08 14:32:22 -04:00
Mofei Zhang
166f47a41e [snap] Fix snap ignored paths 2023-06-08 14:32:22 -04:00
Joe Savona
8192cd253d Fix missing memoization of scope-less variable being reassigned 2023-06-07 22:15:59 -04:00
Lauren Tan
5673672ff9 Move playground to apps
Generally we should reserve the `packages` directory for packages that 

are needed to run the compiler, while `apps` is for frontends that might 

make use of the compiler (and optionally, related packages)
2023-06-07 14:40:42 -04:00
Joe Savona
bf631a0bb6 Change rules for context variable promotion
This PR updates the conditions for which variables are promoted to context 
variables. 

The previous rule was to promote any variable where the variable was reassigned 
in some function expression other than the function that declared the variable. 
Notably, this meant that we did not use context variables for variables which 
were captured in a function expression, but reassigned _outside_ a function 
expression. 

The new rule is more consistent: we promote any variable which is a) reassigned 
somewhere and b) referenced in some function expression outside of their 
declaring function. The implementation builds two sets of identifiers, one for 
each criteria, then takes the union of these two sets. 

## Motivation 

The motivation for this change is to unblock additional validations and 
optimizations of function expressions. It's currently difficult to translate 
metadata that we infer about identifiers outside of a function expression into 
metadata about the identifiers within a function expression — for example to 
infer types within function expression bodies based on type information outside, 
propagate constants into functions, infer reference effects, etc. 

After this change, the only free variables inside function expressions will be 
variables that are effectively `const` - never reassigned anywhere. Thus it will 
be safe to renumber those identifiers to match the outer context (during 
EnterSSA), making it trivial to map metadata from outside the function into the 
function. 

This change also more closely models the runtime representation — any variable 
referenced in a function, and reassigned somewhere, would have to be compiled 
(ie in a JS engine) to use a context variable.
2023-06-05 22:11:08 -04:00
Lauren Tan
4e22fa6451 Try to fix test262 in ci 2023-06-06 15:25:20 -04:00
Lauren Tan
5ebb7f1a8e Try to compile with Forget in eslint plugin
Nothing interesting for now, but it does seem to successfully run the compiler
2023-06-06 10:56:21 -04:00
Lauren Tan
359df9e85c Enable TypeScript in eslint-plugin-react-forget 2023-06-06 10:56:21 -04:00
Lauren Tan
5c1faa901e Add workspace command to run all tests 2023-06-06 10:56:20 -04:00
Lauren Tan
cd56c55e87 Basic noop rule for ReactForgetDiagnostics 2023-06-06 10:56:20 -04:00
Lauren Tan
f82de29e73 Bootstrap new eslint plugin
Just scaffolds a new eslint plugin
2023-06-06 10:56:20 -04:00
Joe Savona
04d6bad1ae [ez] Update completed todo comment
lol at how out of date this comment is :-)
2023-06-05 16:46:38 -04:00
Joe Savona
39d837d918 Update fixtures from workspace rebase
I updated these yesterday, looks like they got added back in the workspace PR. 
Rebases are hard, all good!
2023-06-05 14:16:14 -04:00
Joe Savona
66ca1c75cb Fix dropped constant declaration bug 2023-06-05 14:08:29 -04:00
Joe Savona
ad32811b64 [RFC] Improve validation against ref access in render
The goal of this PR is to improve ValidateNoRefAccessInRender to find function 
expressions which a) access refs and b) may be called during render. Currently 
we always allow ref access in any function expression, but that's obviously 
optimistic. 

For the approach, the observation is that we already have a system that tells us 
whether a function may get called — mutable range inference. So long as we 
consider a function "mutable", we'll infer a range for it, but the problem is 
that we don't currently view functions which depend on refs to be mutable. So 
here I'm doing ~~sort of~~ a hack to force function deps on refs to be treated 
as Effect.Capture. This is enough for the function to be considered mutable, for 
a mutable range to be assigned, and for us to detect that during ref validation. 

I don't love the hack, i'm open to other ideas!
2023-06-05 14:08:26 -04:00
Mofei Zhang
b160ee8522 [tests] Tests and patch for context variables
--- 

- [patch] Find context variables within FunctionDeclarations (previously 
missing) 

- [todo] Need to fix non-allocating values / variables being DCE'd 

One approach is to label everything referenced by a lambda as context variables. 
I couldn't come up with other examples that break without this change, so I 
wonder if something lighter / hackier works just as well. (My only hesitation is 
that we may end up losing out on potential optimizations for everything aliased 
to these variables).
2023-06-05 15:04:46 -04:00
Mofei Zhang
6d1d05ff91 [runtime] Update makeReadOnly runtime
--- 

(wip, waiting for feedback on workplace post) 

- remove calls to `isInROMode`, as we want to log all mutations after 'freezing' 
a value (both within and outside of render cycle) 

- add `source` parameter -- this is the function name of the parent component / 
hook
2023-06-05 14:46:59 -04:00
Mofei Zhang
5ba0a03e00 [config] Clean up codegen imports and gating
- Gating checks for debugging / profiling can be moved to within the 
instrumentation or makeReadOnly functions. This simplifies codegen and reduces 
code bloat. 

- Warn if importing conflicting identifiers 

- Aggregate imports from the same source
2023-06-05 14:46:59 -04:00
Mofei Zhang
a65e9cd1f1 [devx] emit calls to makeReadOnly for debugging
--- 

Emit calls to makeReadOnly for memoized values. 

```js 

function MyComponent() { 

let x; 

if (c_0) { 

x = // ... (recompute x) 

$[0] = __DEV__ ? makeReadOnly(x, "MyComponent") : x; 

} else { 

x = $[0] 

} 

} 

``` 

- import source / specifier should be configurable, as 

- we'll likely want to add gk gating to `makeReadOnly` itself to reduce codesize 
bloat 

- each Forget project needs different logging and filter configurations 

- codegen function name as an argument for easier debugging 

- only freeze memoized outputs
2023-06-05 14:46:58 -04:00
Lauren Tan
40d1459115 Scaffold workspaces
![image](https://github.com/facebook/react-forget/assets/1390709/bb27e33f-6a88-4b71-8ce2-aeb8db0372f7) 

This is an attempt to scaffold yarn workspaces into our repo with as minimal as 
possible changes to our current setup and directory structure. Essentially this 
PR moves current `src` into `packages/babel-plugin-react-forget` as a first step 
to keep everything working. Later on when we're ready we can split out a 
`react-compiler` package that is decoupled from Babel, but it's too early for 
that now
2023-06-05 13:35:39 -04:00
Joe Savona
3744930728 Fix last(?) order of evaluation bug
Not to get ahead of myself (sorry i had to), but i think this is the last order 
of evaluation bug. At least it's the last one we know of[1]. Per the previous 
PR, the issue is that constant propagation can copy the last value of a sequence 
expression to where the sequence is used, leaving the original sequence 
expression out of order after other instructions are moved around. We fix that 
here by explicitly skipping constant propagation for the last value of a 
sequence block. 

[1] There are some places where we _would_ have evaluation order bugs if we 
allowed arbitrary expressions, but we explicitly limit the expressions we allow 
in those places. For the curious: switch test case values and destructuring 
default values.
2023-06-04 21:41:27 -04:00
Joe Savona
1e0eb25cd0 Use sequence terminal, fix most remaining order-of-evaluation bugs
Changes the lowering for sequence expressions to use the new terminal. When 
converting to a ReactiveFunction, we convert these terminals into 
ReactiveSequenceValues, which nests the instructions and preserves order of 
evaluation in the output. 

The only catch is constant propagation — constant propagation breaks 
order-of-evaluation because it can effectively copy the final value of a 
sequence elsewhere, leaving the original sequence in the wrong place. I'll 
address that in a follow-up.
2023-06-04 21:26:01 -04:00
Joe Savona
cd115ec128 Scaffold for sequence terminal
I realized that we can use our value block system to fix _most_ of the remaining 
order-of-evaluation issues we had with sequence expressions. This PR adds a new 
SequenceTerminal to HIR; there is already a ReactiveFunction equivalent 
(ReactiveSequenceValue) that the next PR will convert this terminal into.
2023-06-04 21:25:59 -04:00
Joe Savona
e410815aeb Infer more primitive types
Handles three more cases: 

* Template literals 

* deletion (property/computed) 

* type casts 

Only the latter has an observable impact, though i added tests for deletion just 
in case and found a bug. For type casts, they're reasonably common internally 
for fixmes, so this PR will help to ensure we don't drop type information just 
because of a cast.
2023-06-04 20:27:24 -04:00
Joe Savona
084edadaa1 Distinguish error fixtures for invalid code
Renames some error fixtures for clarity, "error.invalid-*" are fixtures that are 
expected to fail for invalid input, where other "error.*" fixtures are basically 
todos. While i was here i clarified the error messages for invalid useMemo 
callbacks, and changes the error severity from Invariant to InvalidInput.
2023-06-04 11:41:42 -07:00
Joe Savona
e41a8d6a1e Validate destructuring assignment to globals
Fixes one more category of bug. For assignment expressions, we validating 
against redeclaring a global variable when the assignment target was an 
identifier, but not when the global was reassigned via destructuring. This PR 
adds a `lowerIdentifierForAssignment()` helper and uses it for assignment of all 
identifier variants, including destructuring.
2023-06-04 11:29:55 -07:00
Joe Savona
2ae3592d29 Fix/review bug repros, mark fixed
I reviewed the test cases we have marked as bugs ("_bug.*") and realized that 
several of them are already fixed — woohoo! Then one wasn't fixed _yet_: our 
type inference loses track of refs if you stash them inside an object/array. But 
that's why I added the ValidateNoRefAccessInRender pass, which i've updated to 
detect and reject these invalid cases. There are now only a few bugs left (more 
fixes coming).
2023-06-04 11:08:54 -07:00
Lauren Tan
6d6518161a Cleanup packages directory
Prepare to move to a yarn workspaces setup
2023-06-02 13:06:36 -04:00
Lauren Tan
479ef4045b Move entrypoint into its own directory 2023-06-02 13:06:35 -04:00
Lauren Tan
2eded946f8 Make CompilerEntrypoint a little more generic
These are still quite Babel specific, but the interface is slightly more 
generic: CompilerEntrypoint takes a non-Babel specific CompilerPass as an 
argument instead of passing Babel's PluginPass directly. 

In the future we can consider lowering the whole Program into HIR but that 
involves a significant lift in our representation and a small amount of new 
syntax to support (eg import statements), so this PR is the extent of this stack 
for now
2023-06-02 13:06:35 -04:00
Lauren Tan
b98ee34f45 Delegate BabelPlugin to CompilerEntrypoint
Moves the existing logic in BabelPlugin to its own files
2023-06-02 13:06:34 -04:00
Lauren Tan
9077ac1f27 Copy files from src/Babel to src/ 2023-06-02 13:06:34 -04:00
Joe Savona
7fddf42d06 [ez] Hooks dont escape, their args do 2023-05-31 16:05:07 -07:00
Joe Savona
0ae37f1568 Treat setState as non-reactive
Updates PruneNonReactiveDependencies to treat setState functions as 
non-reactive, since we know they have a stable identity. This is based on type 
inference and our recently added definitions for useState and its return type, 
so it's conservative and will only work when our inference can prove that the 
scope dependency has the SetState type. 

Note that this approach is simple and has limitations, notably the fact that the 
setState is non-reactive doesn't propagate. But it's simple, trivially correct, 
and already improves codegen somewhat, so i figured it's worth landing for now. 

## Test Plan 

Tested on internal app
2023-05-31 14:29:31 -07:00
Joe Savona
9c453ad26f Validate frozen lambdas are actually frozen
Adds validation to reject freezing mutable lambdas, since lambdas cannot _be_ 
frozen, they're either frozen or not. Example invalid code: 

```javascript 

function Component(props) { 

const x = {value: ""}; 

const onChange = (e) => { 

// MUTATION!!!!! 

x.value = e.target.value; 

setX(x); 

}; 

return <input value={x.value} onChange={onChange} />; 

} 

``` 

Note that there is a separate issue in which we are not detecting lambdas that 
would definitely modify immutable values. We may need to distinguish 
ConditionallyCapture (captures if the value is mutable, otherwise readonly) from 
Capture (definitely mutates) in order to make that case work. But already this 
validation helps prevent some invalid code.
2023-05-31 14:03:02 -07:00
Joe Savona
8e341294d0 [be] Upgrade to TypeScript 5.1
The functional changes in 5.1 don't seem that compelling but there are some perf 
improvements, supposedly.
2023-06-01 13:23:09 -07:00
Joe Savona
a5a86c302a Validate against ref access in render 2023-05-31 09:38:28 -07:00
Joe Savona
af94b075c0 Option to disable memoization
Adds a `removeAllMemoization` flag that runs the entire compiler pipeline but 
strips out all memoization. The intent is to be able to compare (in limited 
use-cases) the performance of an existing app with all memoization removed, vs 
the performance with manual memoization, vs the performance with Forget enabled. 

In terms of how this works: we already strip out useMemo/useCallback since 
Forget is more accurate. The new option adds an extra pass that strips out all 
reactive scopes. Collectively this leaves ~zero memoization within components 
(this does leave React.memo, but close enough).
2023-05-31 08:03:36 -07:00
Sathya Gunasekaran
793b69243f [hir] Track context refs when inferring reactive ids 2023-05-31 15:37:39 +01:00
Sathya Gunasekaran
b447c93edc [test] Add failing test for lambda turning value reactive 2023-05-31 15:37:35 +01:00
Sathya Gunasekaran
2c8f6888e6 [hir] Failing test for adding ref as dep incorrectly 2023-05-31 13:55:00 +01:00
Sathya Gunasekaran
91f41f5ee3 [hir] Don't track ref value as a valid dep 2023-05-31 13:54:59 +01:00
Sathya Gunasekaran
bedf0cc6c5 [hir] Don't track ref.current as a valid dep 2023-05-31 13:54:58 +01:00
Sathya Gunasekaran
1d536af4f7 [hir] Refactor checkValidDependencyId to accept a dep 2023-05-30 13:44:30 +01:00
Mofei Zhang
fc13557d30 [tests] remove jest fixture tests
--- 

Remove jest fixture tests in favor of snap runner. Main reasons: 

- maintaining feature flags and compatible behavior required syncing all changes 
to 3 files (`generateTestsFromFixtures`, `compiler-test`, and `compiler-worker`) 

- jest snapshot test file causes rebase conflicts on most rebases 

- speed 🙌 

$ time yarn test compiler-test 

(the extra test here is `has a consistent extension for input fixtures`) 

``` 

Test Suites: 1 passed, 1 total 

Tests:       37 skipped, 480 passed, 517 total 

Snapshots:   479 passed, 479 total 

Time:        27.668 s 

Ran all test suites matching /compiler-test/i. 

  Done in 43.18s. 

yarn test compiler-test  57.05s user 3.85s system 139% cpu 43.546 total 

``` 

$ time yarn snap 

``` 

478 Tests, 478 Passed, 0 Failed 

  Done in 13.12s. 

yarn snap  53.96s user 9.35s system 468% cpu 13.518 total 

``` 

Jest and snap should have the same set of features: 

- report test failures via exit status (used by Git Actions) 

- watch mode 

- breakpoints + `debugger` statements 

- note that `--sync` is not required for this 

- skip `todo.` prefixed fixtures 

- fixtures in nested directories e.g. `rules-of-hooks/testname.js` 

- filter mode (via editing `testfilter.txt`) 

- filter + debug mode 

(1) edit `testfilter.txt` to filter out all but one test 

(2) add `@debug` pragma to the first line of the test 

testfilter.txt 

```js 

// @only 

testfixture_basename1 

testfixture_basename2 

```
2023-05-26 13:18:38 -04:00
Mofei Zhang
71c9743e38 [snap] Fix vscode debugger attaching to forked proc
Turns out I just forgot to forward `process.env` when forking 😅 

`debugger` statements and breakpoints should work with both `--sync` and 
`--no-sync` (default) modes
2023-05-26 13:18:37 -04:00
Joe Savona
99257aa430 Enable more flags on playground 2023-05-23 16:55:10 -07:00
Joe Savona
91dcf24e67 Add test case for invalid lambdas
This is the example we discussed in our design sync. 

```javascript 

function Component(props) { 

const [x, setX] = useState({ value: "" }); 

const onChange = (e) => { 

// INVALID! should use copy-on-write and pass the new value 

x.value = e.target.value; 

setX(x); 

}; 

return <input value={x.value} onChange={onChange} />; 

} 

``` 

Here `onChange` is a mutable lambda, and it should be invalid to pass a mutable 
lambda where a frozen value is expected. This is because unlike other value 
types, you cannot freeze a lambda — the only choice is to not call it at all. 

Note that there is a harder case to catch: 

```js 

function Component(props) { 

const [x, setX] = useState({ value: "" }); 

const onChange = (e) => { 

// INVALID! should use copy-on-write and pass the new value 

x.value = e.target.value; 

setX(x); 

}; 

const x = constructAValueThatMaybeAliasesItsInput(onChange); 

return <input value={x.value} onChange={x.maybeGetTheLambdaBack()} />; 

} 

``` 

This case demonstrates how mutable lambdas can be captured and then accessed 
later — the analysis to catch this case is more sophisticated bc it involves 
inferring that `x` aliases a mutable lambda. But we also can't be sure that `x` 
does alias the lambda, so disallowing this code could prevent a lot of valid 
code from compiling. My hypothesis is that we should start with at least 
validating the example at the top, while allowing the second case for now.
2023-05-25 16:08:10 -07:00
Joe Savona
edbb6e2bdb Test case for Array.push on frozen array
Just making sure that we reject this
2023-05-25 16:00:35 -07:00
Joe Savona
3381ce7ea8 Array.prototype.join
Trivial
2023-05-25 15:57:09 -07:00
Joe Savona
8be5423e3b Type Array.prototype.map/filter
We can now type `Array.prototype.{map,filter}`: 

* The callee is ConditionallyMutable because, although the array itself is not 
modified, its items flow into the lambda and may be modified there. 

* The argument is ConditionallyMutable because it accepts both mutable and 
immutable lambdas. Mutate would disallow immutable lambdas (wrong), while Read 
would be incorrect for mutable lambdas since calling them triggers mutation.
2023-05-25 14:50:14 -07:00
Joe Savona
75a1972584 More tests for effect enforcement
Adds test cases to ensure we're correctly inferring mutative builtin operations 
— property store, computed property store, property deletion, and computed 
property deletion — as definite mutation and that we're rejecting inputs where 
these operations are used on immutable/frozen values.
2023-05-25 14:33:02 -07:00
Joe Savona
9766e19805 Add Effect.Mutate for known mutation
Adds back `Effect.Mutate`, and changes so that `Effect.ConditionallyMutate` 
never rejects frozen/immutable values, while `Effect.Mutate` _always_ rejects 
frozen/immutable values.
2023-05-25 13:54:35 -07:00
Joe Savona
40cf400f72 Rename Effect.Mutate -> ConditionallyMutate
We currently use `Effect.Mutate` both for places that _may_ mutate (ie untyped 
function calls) and for places that have known mutation (typed function calls, 
or operations like `delete x.y`). We then use a separate mechanism to decide 
whether to reject the input, with some call paths checking the effect and others 
not. 

This stack refactors this logic in InferReferenceEffects per our discussion, so 
that `Effect.ConditionallyMutate` is for "may or may not mutate" either because 
we're not 100% sure (untyped function) or because the mutation depends on the 
operand (ie, a callback arg that will be invoked and thus will mutate if the 
lambda is mutable, not mutate if the lambda is immutable). Later diffs add back 
`Effect.Mutate` as "definitely 100% mutating".
2023-05-25 13:41:09 -07:00
Joe Savona
37d044763b Define types for useState/useRef
Defines 4 new types: 

* Return type of `useState()`, which has properties "0" and "1" to allow us to 
infer the types when destructuring 

* Type of useState() set state function 

* Return type of `useRef()` so we know what is a ref 

* Type of ref.current, so we know what is a ref *value* 

Example: 

<img width="1670" alt="Screenshot 2023-05-24 at 9 59 37 AM" 
src="https://github.com/facebook/react-forget/assets/6425824/3ee7d04a-fda3-4b7b-89b7-d205d9a6fd0d">
2023-05-24 09:54:10 -07:00
Joe Savona
c87a1916f4 Infer property types for destructuring 2023-05-24 09:54:07 -07:00
Mofei Zhang
ecc730c587 [FeatureFlag] Toggle enableTreatHooksAsFunctions default to true
--- 

Toggle default to true, since this should be a no-op refactor. 

Tests: 

- test fixtures 

- ran on Store + - and saw no difference in compiled output 

- [diff](P744101621) with 
`enableTreatHooksAsFunctions=false` 

- [diff](P744105565) with 
`enableTreatHooksAsFunctions=true`
2023-05-23 16:11:38 -04:00
Mofei Zhang
08a8891245 Test: unknown hooks should not assert effects 2023-05-23 13:55:12 -04:00
Mofei Zhang
b76ee1ab32 [types] Consolidate Hook and Function types: Hooks are functions 2023-05-23 13:55:10 -04:00
Mofei Zhang
47e9a2b8a5 [FeatureFlag] add enableTreatHooksAsFunctions 2023-05-22 19:37:53 -04:00
Mofei Zhang
19cf39f50b [effects] Track return ValueKind for function signatures 2023-05-22 19:37:50 -04:00
Joe Savona
d456e4a78c Memoize based on inferred return type, not signature 2023-05-22 09:20:16 -07:00
Joe Savona
4ce22ebcfe Option to assume hooks follow the rules
Adds a new feature flag which tells the compiler to assume that hooks follow the 
Rules of React. Specifically, the idea that since any hook could be wrapped in a 
giant `useMemo()` call, all arguments to hooks have to be treated as if they're 
owned by React — and therefore become immutable — and that the return value of 
the hook is immutable. 

Our default is to assume that hooks break the rules, but in practice nearly 
every component follows them.
2023-05-19 11:05:04 -07:00
Joe Savona
7e10c51532 Put optimizations in this stack behind a feature flag
Wraps all the optimizations from this stack behind a feature flag so we can 
quickly disable if there are problems when integrating.
2023-05-18 16:20:28 -07:00
Joe Savona
94e2ae5893 Dont memoize calls that produce primitive values
Teaches InferReactiveScopeVariables that calls can produce primitives that don't 
need a scope.
2023-05-18 16:05:32 -07:00
Joe Savona
5008448d74 Rename function for clarity
Cleanup now that we use this for two things
2023-05-18 15:52:51 -07:00
Joe Savona
0322e4dbd4 InferReferenceEffects uses function types for CallExpression
Updates the InferReferenceEffects logic for CallExpression to work similarly to 
MethodCall, where we take into account the function signature (if present) when 
inferring the effects and return kind.
2023-05-18 15:52:11 -07:00
Joe Savona
c0a5d86902 Reorder similar cases in InferReferenceEffects
Moves the CallExpression case next to MethodCall to make it easier to compare
2023-05-18 15:52:07 -07:00
Joe Savona
a325eed472 Define Boolean/Number/String globals
Defines the `Boolean`, `String`, and `Number` global functions. This will be 
useful for allowing developers to wrap statements that produce a primitive in a 
way that Forget knows about in order to optimize better.
2023-05-18 15:52:03 -07:00
Joe Savona
46417a4ed7 Fix bug with partially memoized destructuring 2023-05-18 09:25:28 -07:00
Joe Savona
a933d5e307 Repro for bug with partially memoized destructuring 2023-05-18 09:25:24 -07:00
Joe Savona
3603ee00a6 Retain original structure of labeled blocks
Handles some edge-cases where we previously flattened away some of the structure 
of a labeled block, instead ensuring that we retain the original shape. See the 
output. 

## Test Plan 

Tested on the internal app we're focused on (w useMemo inlining enabled), it 
works fine.
2023-05-17 15:55:54 -07:00
Joe Savona
7831ac2189 Enable InlineUseMemo in playground 2023-05-17 15:55:54 -07:00
Joe Savona
f0b8cfbf25 More concise output for labeled blocks 2023-05-17 15:55:53 -07:00
Joe Savona
f30041b0f0 More useMemo inlining test cases
More test cases for useMemo inlining, including the problematic case we found 
internally and some similar tricky cases with labels.
2023-05-17 15:55:52 -07:00
Joe Savona
329809de81 Dramatically simplify InlineUseMemo
I realized a wayyyy simpler approach to inlining a lambda: wrap it in a labeled 
block. The transformation is roughly as follows: 

```javascript 

// Before 

const x = useMemo(() => { 

if (a) { 

return b; 

} 

return c; 

}, [a, b, c]); 

return x; 

// After 

let x; 

label: { 

if (a) { 

x = b; 

break label; 

} 

x = c; 

break label; 

} 

return x; 

``` 

The key to making this work is fixing up some edge cases in labeled blocks, 
hence the previous PRs.
2023-05-17 15:55:51 -07:00
Joe Savona
5ee23af1a6 Elide empty if alternate blocks in codegen 2023-05-17 15:55:51 -07:00
Joe Savona
84a356274a Stop shrinking HIR to preserve fallthrough nodes
This is part of a stack to fix some edge cases in inlining of useMemo closures. 
In this first step, I'm disabling `shrink()` in order to retain more information 
about the data flow. For example, 

``` 

label: if (cond) { 

break label; 

} 

return foo; 

``` 

Would previously have shrunk away the if body, making the IfTerminal.consequent 
point directly to the fallthrough block (w the return). Now we retain a separate 
block.
2023-05-17 15:55:50 -07:00
Mofei Zhang
f070cab870 [snap] add memoizeJSX flag
--- 

#1603 for snap
2023-05-17 14:39:29 -04:00
Mofei Zhang
8b38651986 [snap] Add jest test capabilities to snap
- delete output files when we detect input files are deleted 

- enable test fixtures in nested directories 

- exit with error code when we detect failures 

Note that the test failure on this PR is expected and will be fixed by #1608 (or 
happy to abandon that PR and fold the changes)
2023-05-17 14:39:28 -04:00
Mofei Zhang
7ba638ef8d [tests] delete stale .expect files
--- 

Looks like we delete `.js` files but missed `.expect` files. Jest probably 
didn't catch this because the basename of the fixtures had duplicates (in 
rule-of-hooks)
2023-05-17 14:39:28 -04:00
Mofei Zhang
0b697d1903 [babel plugin] Add codegen for useRenderCounter 2023-05-17 14:39:27 -04:00
Mofei Zhang
bceb786c5a [runtime] useRenderCounter
--- 

(`react-forget-runtime` package seems to be synced to -.) 

RFC: useRenderCounter hook: 

- tracks # renders (increments on render path) 

- exposes a global renderCounterRegistry (counting # renders in alive / mounted 
components) 

Next PR will modify BabelPlugin to add codegen 

```js 

// Similar to how we're currently importing `isForgetEnabled` 

import {isInstrumentForgetEnabled_Secret} from "ReactForgetFeatureFlag"; 

// ... 

function Component_uncompiled(props) { 

if (isInstrumentForgetEnabled_Secret) { 

useRenderCounter(); 

} 

// ... 

} 

function Component_forget(props) { 

if (isInstrumentForgetEnabled_Secret) { 

useRenderCounter(); 

} 

// ... 

} 

```
2023-05-17 14:39:26 -04:00
Sathya Gunasekaran
dc603bb2fa [babel] Add isDev flag and put logging behind it 2023-05-17 18:38:08 +01:00
Joe Savona
2a80ba544e Move DisableJsxMemoization-test into main compiler test w pragma
I originally created a separate test for the mode with JSX memoization disabled, 
but we can merge this into the main compiler-test and enable the feature with a 
pragma.
2023-05-15 12:12:53 -07:00
mofeiZ
8bf1ec97a4 Add flag back for inlineUseMemo
Reverts #1502, but flips test flags (e.g. `inlineUseMemo` by default, unless a 
test specifies `@inlineUseMemo false`. I figured this add less thrash for test 
fixtures, but happy to just do a clean revert (or remove the pragma altogether 
and always pass `inlineUseMemo: true`)
2023-05-15 14:27:26 -04:00
Sathya Gunasekaran
f57ed15a33 [Babel] Log errors to stdout, not stderr
Writing to stderr causes buck to fail compilation.. which is not we want. Let's 
log to stdout only for now.
2023-05-15 14:45:26 +01:00
Sathya Gunasekaran
41a897797d [Babel] Format errors for console logging 2023-05-15 14:45:26 +01:00
Sathya Gunasekaran
5c8195aec7 [prettier] Fix linting 2023-05-15 14:43:39 +01:00
Mofei Zhang
dda3e61347 [test cases] Add failing / bug tests for destructuring assignment 2023-05-12 12:48:18 -04:00
Mofei Zhang
fe95b5df90 [hir] add DeclareContext (2/n)
--- 

In `lower`, we now ensure that all context variables are declared by a 
`DeclareContext` instruction. `DeclareContext` always produces a `let` 
declaration, and `StoreContext` is always a reassign. There are a few reasons we 
need `DeclareContext`: 

- DeclareLocal assumes it is storing to a SSA-fied identifier (which always 
stores an immutable primitive). This does not fit context variables. 

- Without DeclareContext, we need custom logic in some passes to initialize 
identifier / context state (e.g. `MutableRange`, ValueKind, etc) for the 
`StoreContext` that declares the context. 

This PR stack models context variables as concrete identifiers (with references 
to context variables modeled by `Place` referencing the context variable 
identifier). @josephsavona pointed out that this is abusing the notion of 
Identifier/Place, as context variables are essentially interior properties of a 
ContextEnvironment. Since we are not modeling `ContextEnvironment` implicitly or 
explicitly, all inference for context variables is essentially pointer analysis.
2023-05-12 12:48:17 -04:00
Mofei Zhang
314a5cfca5 [hir] Add Load/StoreContext (1/n)
--- 

This PR adds LoadContext and StoreContext to handle reading and writing to 
context variables. 

A context variable is any variable that is declared within a Forget-compiled 
function and reassigned within a closure. Conceptually, we want to treat these 
variables as attributes of a `EnvironmentContext` variable (as most javascript 
VMs do). 

- context variables currently do not participate in type inference (i.e. we do 
not produce type equations for loads from context variables). In the future, we 
can try typing this as `Phi(assignment1Type, assignment2Type, ...)`. 

- context variables are always treated as `Effect.Mutable`. 

- context variables do not participate in SSA, or certain optimizing passes 
(e.g. dead code elimination, constant propagation, etc). 

There is some still follow ups: 

- From my understanding, we should introduce a `DeclareContext` instruction. 

- currently, declaring a context variable (without initializing it) is broken. 
This is because the declaration lowers to `DeclareLocal`, which assumes it is 
storing to a SSA-fied identifier. 

```js 

let x; 

x = 4; 

() => { x = {}; }; 

``` 

- DeclareContext will also make some initialization logic easier. In this PR, I 
added some hack-y code to handle initializing effects / mutable ranges / other 
inference state for the first StoreContext. 

- Handle or bail on stores to context variables through destructuring assignment 

- 

~~Next PR:~~ 

- ~~Change closures to track reassigned identifiers (to extend mutable range of 
primitives)~~
2023-05-12 12:17:05 -04:00
Joe Savona
f5bdf462b2 Enable hooks validation on playground, make deguggable
Enables hooks validation in playground. Also adds a tab to show the output of 
validation (in case it passes) with the inferred post dominator tree. We can use 
this to debug the dominator in case of false negatives. 

<img width="1724" alt="Screenshot 2023-05-11 at 11 07 08 AM" 
src="https://github.com/facebook/react-forget/assets/6425824/8f7ae472-8415-4899-aedf-c8f26094ebfe">
2023-05-11 13:39:59 -06:00
Joe Savona
e649bf6429 Fixtures from ESLint rule
Incorporates the fixtures from eslint-plugin-react-hooks using a script, so that 
we can easily update them in the future. For each fixture we run the compiler 
with and without hooks validation first so that we know if the fixture is 
expected to pass — we have some false positives and false negatives that i can 
work through. For example we accidentally think that `userFetch()` is a hook, 
oops. Fixtures that should pass but error, or that should error but pass, are 
marked as `todo.<name>` or `todo.error.<name>`. 

While i was here i added the ability to have fixtures in subdirectories for 
grouping purposes.
2023-05-11 13:39:58 -06:00
Mofei Zhang
d1e044b81a [deps] Add DeclareLocal to ReactiveScope decls
--- 

Try to fix bug from #1589: 

> If a declaration for an immutable identifier (i.e. one that is not later 
re-assigned, since undefined is a primitive) is sandwiched between mutations, we 
currently do not record it as an output or hoist it out of the reactive scope. 

One simple fix is to add all declared (and later referenced) identifiers as 
declarations of a reactive scope. This has some undesired effects (e.g. 
additional instructions + memo cache slots), but in practice, this shouldn't be 
happening often. 

Alternatively, we could 1.) add a pass to hoist declarations, 2.) account for 
this in constant propagation, or 3.) add a bailout
2023-05-11 16:24:37 -04:00
Mofei Zhang
a2169d894a [test case] Test for DeclareLocal instruction
--- 

Record incorrect output. 

If a declaration for an immutable identifier (i.e. one that is not later 
re-assigned, since `undefined` is a primitive) is sandwiched between mutations, 
we currently do not record it as an output or hoist it out of the reactive 
scope.
2023-05-11 16:24:36 -04:00
mofeiZ
77c535ce25 [snap] Make watch mode go fast(er)
While optimizing per @josephsavona's suggestions in #1592, I noticed that we 
were clearing quite a few require cache entries. 

As of this PR, `Object.keys(require.cache)` holds 

- 1258 entries total 

- 67 files compiled from Forget source code (this is what 
`ts.createWatchCompilerHost` modifies) 

- 1120 babel source files (from node_modules) 

When working on watch mode, I'm almost always making changes to Forget source or 
test fixture files. It's a bit faster to just clear those entries (assuming that 
babel has no global state we need to invalidate). 

On my computer, re-running tests in watch mode (triggered by source code 
changes) takes: 

|   |  All tests  | One test (filter) | 

|-- |--------|----------| 

| current | 4.7s  | 1.8s  | 

| this PR  |  1.8s  | 0.1s  |
2023-05-11 14:58:51 -04:00
Mofei Zhang
5e80821aa2 [effects] Allow capture-store effects in rvals
--- 

Some typed functions need to annotate callees or arguments as `Effect.Store`. 
This PR modifies alias analysis (`InferAliasForStores`) to account for this
2023-05-10 21:55:56 -04:00
Mofei Zhang
f3d4d16918 [lower] Bail out on computed lvalues in destructure
--- 

Currently, we're silently producing incorrect output - 

```js 

// source 

function Component(props) { 

const computedKey = props.key; 

const { [computedKey]: x } = props.val; 

return x; 

} 

// compiles to 

function Component(props) { 

const { computedKey: x } = props.val; 

return x; 

} 

```
2023-05-09 14:38:15 -04:00
mofeiZ
04cee98166 [snap] patch bug in clearing require cache
Snap currently has a bug in which the require cache is not correctly cleared 
when running in filter mode (#tests < 2 * #workers). 

- We're currently clearing all entries in the require cache of worker threads, 
including `jest-worker` and `snap/dist/...` files 

- jest-worker seems to `require` these files on every dispatch (i.e. 
`worker.compile` seems to call `require(`compiler-worker`).compile`)
2023-05-11 13:53:06 -04:00
Joe Savona
5de02b881b Support unused conditional/logical expressions assigned to variable
I noticed some instances of this error when running forget on an internal 
product. I previously fixed the case if a logical/conditional used only for side 
effects (not assigned to a variable) but the new cases were assigned to an 
unused variable. I double-checked and we’ve actually fixed all the steps after 
these invariants so we can just remove them and support these cases.
2023-05-09 10:58:51 -07:00
Joe Savona
50c3bd9d98 Fix capturedRefs collection for lambdas
When we calculate the dependencies of a FunctionExpression we were only adding 
new items if the binding identifier had not been seen yet. That is correct for 
`capturedIds` since its the set of identifiers, but incorrect for `capturedRefs` 
since its an array of all the distinct places. This meant that if a function 
expression referenced multiple properties of the same binding, we'd only record 
the first one. We now correctly record all of them.
2023-05-09 10:38:56 -07:00
Joe Savona
90109b3572 Clean up dominator/post-dominator impl
Tidies up the implementation a bit, splitting the single function and class into 
distinct computeDominatorTree() and computePostDominatorTree() functions and 
helper classes.
2023-05-08 15:35:48 -07:00
Joe Savona
597e70d69f Improve eslint derived fixtures 2023-05-08 14:10:07 -07:00
Joe Savona
8920fc0b2f Dont count throw as an exit node (React semantics)
React will retry or abort components that throw (depending on a few conditions), 
so from React's perspective a `throw` statement is not a normal exit node. Thus 
the Rules of Hooks really have a caveat: the set of hooks that are called _in an 
execution that returns successfully_ must be consistent. Examples such as the 
following are therefore allowed: 

```javascript 

function Component(props) { 

if (props.cond) { 

throw new Error(...); 

} 

useHook(); 

} 

``` 

By modeling `throw` as an exit node, we rejected cases such as this. This diff 
changes to not model throws as exit nodes. #1584  changes this to make it an 
option, since some cases will want to consider throw as an exit node.
2023-05-08 13:58:46 -07:00
Joe Savona
7dedc6cc72 Fix test for hook name to match lint rule 2023-05-08 12:14:30 -07:00
Joe Savona
fd007be0d0 Fixtures from ESLint rule
Incorporates the fixtures from eslint-plugin-react-hooks using a script, so that 
we can easily update them in the future. For each fixture we run the compiler 
with and without hooks validation first so that we know if the fixture is 
expected to pass — we have some false positives and false negatives that i can 
work through. For example we accidentally think that `userFetch()` is a hook, 
oops. Fixtures that should pass but error, or that should error but pass, are 
marked as `todo.<name>` or `todo.error.<name>`. 

While i was here i added the ability to have fixtures in subdirectories for 
grouping purposes.
2023-05-08 12:10:43 -07:00
Joe Savona
6bbe3111c0 ValidateUnconditionalHooks pass using dominators
See the code comments for more, but the basic idea here is that we use the post 
dominator tree to find the set of basic blocks which are guaranteed reachable in 
each function. Those are the only blocks where it is safe to call hooks, and we 
error for hook calls in any other blocks.
2023-05-08 08:55:14 -07:00
Joe Savona
2ddd00dbb1 Implement dominator/post-dominator tree calculation
Implements an efficient algorithm for computing the dominator (or post 
dominator) tree of a CFG, following 
https://www.cs.rice.edu/~keith/Embed/dom.pdf. This is used/tested in the next PR 
to validate that hooks are called unconditionally. 

note: I clean up the implementation quite a bit late in the stack in #1584
2023-05-08 08:55:13 -07:00
Joe Savona
5b0f566941 Example of "unnecessary" memoization of lambdas
I noticed this while demoing Forget to React Org alum Christoph Nakazawa — in 
array.map calls (and other APIs that take a lambda as input) we sometimes end up 
memoizing the lambda. It's technically correct since the function _could_ return 
the lambda, and then we'd need it to be memoized. It's tricky because array.map 
is often called on nested objects, where even if we had type inference on the 
outer value we wouldn't know for sure that the inner property is an Array and 
not some other data type with a custom .map. For example in 
`data.feedback.comments.edges.map(edge => ...)`, even if we knew that `data` was 
an Object, we wouldn't know that data.feedback.comments.edges is an Array 
without cross-file type knowledge. 

But it's definitely wasteful to memoize these lambdas, so we should brainstorm 
options. One option that stands out right away: if the lambda has zero 
dependencies, then we could lift it out to module scope and refer to it by name.
2023-05-10 23:09:17 -06:00
Mofei Zhang
11cee99968 [snap runner][easy] reverse diff order 2023-05-08 14:58:32 -04:00
Joe Savona
2332155161 ValidateHooksUsage
Adds a validation pass to check that the only thing you can do with hooks is 
call them. A follow-up PR (still early WIP) will check the other aspect of the 
rules of hooks, that they are not called conditionally. That's a more involved 
algorithm.
2023-05-04 13:33:43 -07:00
Joe Savona
f75cf6fe38 Fix fbt (again) 2023-05-04 15:19:41 -07:00
Mofei Zhang
407e7b0c62 [snap runner] debug mode 2023-05-03 14:50:27 -04:00
Mofei Zhang
7e990419c1 [snap tester] Handle unexpected errors by failing fixture 2023-05-03 14:50:25 -04:00
Joe Savona
253b05f28d Remove restriction on optional nesting
We previously disallowed OptionalMemberExpression inside a normal 
MemberExpression, eg `(a?.b).c`. The new representation handles this case 
correctly so we can remove the restriction.
2023-05-03 17:10:31 -07:00
Joe Savona
7ecee6a091 Support complex computed properties in OptionalMemberExpression
Our previous lowering for OptionalMemberExpression reordered the evaluation of 
properties, such that we had to restrict the allowed properties to those that 
were safe for reordering. With the new representation we preserve order of 
evaluation, so we can relax the restriction. This unblocks a few cases in an 
internal product.
2023-05-03 17:10:28 -07:00
Joe Savona
e192930cd4 Remove unnecessary optional property on loads
Now that _all_ optional expression types use the new representation, the 
optionality of all PropertyLoad and ComputedLoad is modeled via control flow (in 
HIR) and the structure of OptionalExpression (in ReactiveFunction). Thus we no 
longer need the `optional` properties on these load instructions — they're 
optional if they're part of an OptionalExpression.
2023-05-03 17:10:27 -07:00
Joe Savona
a43fc2bcf3 Consistently use new lowering for OptionalMemberExpression
Earlier PRs in the stack change the way we lower OptionalMemberExpression, but 
only when they ultimately appear inside some OptionalCallExpression. This PR 
ensures that _all_ OptionalMemberExpressions get the new lowering. Note that one 
test case has what is arguably a regression, but the new behavior is also 
reasonable: if we see both `a.b?.c` and `a.b.c.` as dependencies of a scope, we 
previously inferred `a.b.c` as the dependency, but we now infer `a.b` as the 
dependency. This isn't as optimal as what we had before, but it also seems good 
enough for now. Also note that some cases are improved: `foo(a.b?.c)` would 
previously have taken `a.b` as a dependency, we now take the full value of 
`a.b?.c` as a dependency - more precise. 

So overall i'm inclined to land and follow-up on the one regression, since the 
overall model is more cohesive.
2023-05-03 17:10:26 -07:00
Sathya Gunasekaran
e5c07ad3e1 [babel] Skip compilation if there's no function name 2023-05-03 14:32:58 +01:00
Joe Savona
404f627c2c Improve conditional dependency tracking for optional member expr inside optional
call 

When we traverse an OptionalExpression in PropagateScopeDependencies, we 
previously considered the entire value to be optional. With the changes in this 
stack to more accurately model OptionalMemberExpression, the `object` portion of 
an OptionalMemberExpression is now evaluated within the OptionalExpression. This 
PR refines the handling of OptionalExpression accordingly, so that we only treat 
the optional portion as conditional.
2023-05-02 16:49:54 -07:00
Joe Savona
a1902b263d Rename OptionalCall -> Optional for clarity
The previous OptionalCall terminal and reactive value kinds are now used not 
just for optional calls, but for optional member expressions that appear within 
an optional call. This PR renames those data types to OptionalTerminal and 
OptionalExpression for clarity.
2023-05-02 16:31:31 -07:00
Joe Savona
c46157b00b More accurately model OptionalMemberExpression
Extends the new modeling of the previous diff to OptionalMemberExpression. In an 
example such as `a?.b?.c`, we now model that not only is the `.c` conditional, 
but our control flow graph accurately reflects the fact that the `.c` is only 
evaluated if `a.b` exists. Previously we knew it was conditional but the CFG 
allowed a path from a being null through to evaluation of `.c`.
2023-05-02 15:44:06 -07:00
Joe Savona
73a203c199 More accurately model nested OptionalCallExpression
More accurately models nested OptionalCallExpression. Consider: 

```javascript 

a?.(b)?.(c) 

``` 

Our previous representation modeled it such that we treated the second function 
call as if it would be called regardless of whether `a` existed or not. We knew 
that the second call was conditional, so our test output was correct, but the 
control-flow graph didn't faithfully model the semantics. That bothered me. 

The new representation correctly models the control flow, and the fact that if 
`a` is null/undefined execution immediately aborts (not reaching the second call 
at all, nor the evaluation of its args), and evaluates the whole outer 
OptionalCallExpression to `undefined`. 

Note that nested optional member expressions still have the previous model — 
that's next to address.
2023-05-02 15:00:12 -07:00
Joe Savona
9a12e8ba58 Support OptionalCallExpression as LHS of LogicalExpression
A common idiom is to map over some possibly-missing list of items from a data 
payload and fall back to an empty array: 

```javascript 

const renderedItems = data?.items?.map(renderItem) ?? []; 

``` 

The way we were lowering OptionalCallExpression meant that in this case, we'd 
end up with an OptionalCallTerminal as the terminal of the logical expression's 
test block, which violates our internal invariant. Logical test blocks must end 
in a Branch! This PR fixes the immediate issue, which is that the callee - in 
this case `data?.items?.map` — was being lowered prior to the 
OptionalCallTerminal instead of inside its test block. Changing that fixes the 
shape of the IR and makes this example work. 

As part of investigating this I realized that the way I originally handled 
lowering of optional call isn't quite right. The difference isn't observable 
unless we did more sophisticated DCE but we don't correctly model the fact that 
if `data.items` is null that the `map()` call won't occur. That is technically 
fine bc we do model the fact that the `map()` call is conditional, and notably 
its arguments are only conditional dependencies. So it's good enough. But in a 
follow-up I'll change to model the fact that `data.items` is null, that the map 
call isn't reachable at all.
2023-05-02 15:00:11 -07:00
Joe Savona
8292c65d84 Add missing traverseValue()
I don't think this one matters but just in case, we should always call 
traverseValue() when overriding visitValue().
2023-05-02 11:07:12 -07:00
Joe Savona
dd6a9d6a03 Dont memoize hook calls in logical expressions
Fix the previous bug — this was a simple oversight, where FlattenScopesWithHooks 
overrode `visitValue()` but failed to call `traverseValue()`. This meant that 
when we reached compound expressions such as LogicalExpressions that we didn't 
traverse into their nested values, and didn't see the hooks hidden there.
2023-05-02 11:01:37 -07:00
Joe Savona
bfb84eed3b Repro for memoized hook within logical expression
Repro of a bug in which we incorrect memoize hook calls that are inside logical 
expressions (though the bug could occur for ternaries, optional calls, and 
sequence expressions too).
2023-05-02 10:47:48 -07:00
Joe Savona
c0b0119356 Start of architecture doc
This is an attempt to get down some of the principles and goals that we've had 
partially written down, partially just thoroughly discussed amongst the team. 
It's rough draft quality but better than nothing, and gives us someplace to add 
to.
2023-05-02 09:33:19 -07:00
Joe Savona
5196fbd92b Visit switch test case as scope dependency
This can't be tested yet - we only support simple, safely re-orderable values as 
case test values - but it will easy to overlook later so i'm adding now.
2023-05-01 08:51:58 -07:00
Joe Savona
41d99ffcb9 PropagateScopeDeps uses visitor infra
PropagateScopeDependencies is one of the few places we don't use the new visitor 
infra for traversing ReactiveFunction. Or rather it _was_! 

Note that there's a bit less value here than in other places since we have to 
handle each terminal variant with custom logic, but at least it's more 
consistent with the rest of the codebase now.
2023-04-28 17:14:02 -07:00
Joe Savona
1df89e992a Remove unnecessary temporary in tests
We previously didn't support ternaries whose value was unused, so we had an 
extraneous temporary and console.log call to ensure the value counted as used. 
We now special-case ternary/conditional expressions which are in an 
ExpressionStatement to not prune them, so the temporary and log are now 
unnecessary.
2023-04-27 10:43:43 -07:00
Lauren Tan
d2f33dd524 Retain locations when using babel cloneNode in lambdas
It turns out the third parameter to `cloneNode` is ["If the third parameter is 
true, the cloned nodes exclude location 
properties."](c060e5e3d5/packages/babel-types/src/clone/cloneNode.ts (L35-L39)) 
strips away locations if its true, so to fix simply change this to false
2023-04-27 16:25:41 -04:00
Lauren Tan
d5dee80933 Failing test for fbt issue
Specifically, the fbt plugin would error on a `null` loc as it [relies on it in 
one of its 
utils](6a23a5374a/packages/babel-plugin-fbt/src/getNamespacedArgs.js (L82-L84)), 
implying a missing `loc` somewhere in our pipeline after codegen
2023-04-27 16:25:41 -04:00
Lauren Tan
3eed52b49b Only debug with @debug pragma
You don't always want to debug all the passes when you use @only, so split it 
out into its own pragma
2023-04-27 16:16:50 -04:00
Joe Savona
b69d70664c Console methods are readonly
Defines common `console` methods to tell the compiler that they take readonly 
args. This ensures that things like `console.log()` aren't accidentally viewed 
as a mutation. Previously the pattern of "build object, then log it after 
mutation is done" would have grouped the console.log as part of the mutation and 
the log only would fire if the value got reconstructed. Now we know the log 
isn't mutating, and the log will happen regardless of whether the value is 
rebuilt or cached.
2023-04-27 10:34:31 -07:00
Joe Savona
2ddbbd4735 Exhaustive switches for some terminal handling
Updates two points in the compiler that were easy to miss when adding new 
terminals: 

* HIRBuilder's `removeUnreachableFallthroughs()` nulls out unreachable 
fallthroughs, but this had a non-exhaustive `if` statement. It now uses a helper 
function which internally has an exhaustive switch. 

* LeaveSSA needs to schedule block fallthroughs, but had a non-exhaustive `if` 
statement. It also uses a helper function which internally has an exhaustive 
switch. 

cc @poteto since you ran into this (ie the compiler not alerting you to update 
these places) w your diffs.
2023-04-26 16:34:26 -07:00
Joe Savona
afe2f5c810 Support useMemo w named functions
Supports useMemo invocations where the function is not an inline function, such 
as `useMemo(someFunction, [])`.
2023-04-26 18:16:28 -07:00
Sathya Gunasekaran
1cccd6a815 [hir] Clone babel nodes rather than reusing them
There's state such as scope info sticking on to these nodes so reusing them can 
cause issues if a pass later on just uses scopes these directly.
2023-04-27 17:35:30 +01:00
Lauren Tan
4a8c90a77c [babel] Remove unsafe calls to path.stop()
Discovered this in a recent attempt at syncing Forget to Meta, it seems 

that calling path.stop() is unsafe as it appears to have strange 

behavior in plugins that come after. This resulted in `import type 

{...}` not being compiled away in the post-babel output which isn't 

valid JS syntax. Removing the `stop()` calls fixes it 

Test plan: made these changes locally, synced my local changes to Meta and reran 
- in simulator and observe that it now runs and doesn't throw a syntax error
2023-04-26 17:16:34 -04:00
Lauren Tan
b1eaf88c61 [babel] Ensure only adding import specifier to non-namespace
Missed this in the previous PR 

Test plan: P706162189 (some babel errors) before this PR, P706255523 has no 
errors
2023-04-26 15:16:21 -04:00
Joe Savona
e5f4b3008f Rename things for clarity in PropagateScopeDeps 2023-04-26 11:34:47 -07:00
Joe Savona
2aa3a28481 test case for debugger stmt in reactive scope 2023-04-26 11:34:46 -07:00
Joe Savona
5b9992075c Driveby optimization of DCE
Lift a constant evaluation out of a loop
2023-04-26 11:27:41 -07:00
Joe Savona
d4b8df5441 Address code review 2023-04-26 11:27:40 -07:00
Joe Savona
e98823f933 Generalize #1521 for PropertyLoad
This is a more general version of the change from #1521. That PR ensured that 
LoadLocal temporaries accessed outside the instruction's scope are correctly 
promoted. However, we have a similar pattern with PropertyLoad. 

This PR adds a general mechanism for handling these type of indirections: any 
LoadLocal/PropertyLoad temporary accessed when it's defining scope is not active 
will be promoted to a declaration of the defining scope. Notably, we do this in 
a way that ensures that the dependencies are preserved, ie that we correctly 
view the operand of LoadLocal/PropertyLoad as a dependency of the current scope.
2023-04-26 11:27:39 -07:00
Joe Savona
dd5069162a This page left intentionally blank
Supports EmptyStatement nodes by ignoring them. Note no tests because 
format-on-save clears away any empty statements and there is, rather 
frustratingly, no way to tell VSCode not to format on save for a specific file 
via the file contents itself or project configuration (at least, not that i can 
find).
2023-04-26 11:27:38 -07:00
Joe Savona
8e7b68506f You get a debugger and you get a debugger and you get a
Adds support for DebuggerStatement.
2023-04-26 11:27:37 -07:00
Joe Savona
1f51b10ac9 Fix unused logical/condition via ExpressionStatement instr
Uses the new ExpressionStatement instruction to ensure that logical and 
conditional expressions are never pruned. This addresses an issue where we were 
unable to construct a ReactiveFunction for unused logical/conditional bc there 
wasn't a single Identifier assigned in both branches. The ExpressionStatement 
ensures that the result is used, that we don't prune the phi, and that both 
branches have a single assignment target. 

In theory we could be more sophisticated with DCE and still prune these 
instructions if their operands are also safe to prune, but in practice you're 
only like to have a logical/conditional as an expression statement (in the 
source) if it's for side effects.
2023-04-26 11:27:36 -07:00
Joe Savona
2cb1c72305 Scaffolding for ExpressionStatement instruction kind
Adds an `ExpressionStatement` instruction variant to model values that are 
otherwise "unused" but which we don't want to remove. The next diff changes 
BuildHIR to use this where appropriate.
2023-04-26 11:27:35 -07:00
Joe Savona
894cf6e37b First-class representation of builtin jsx tags
We previously represented JsxExpressions using builtin tags - `<div>`, `<b>` etc 
- by lowering the tag name to a Primitive with the string name of the tag. 
However, by lowering into an independent value, it was possible that the lowered 
tag name could be grouped into a different memo slot, such that we ended up with 
output like: 

```javascript 

let t0; 

if (c_1) { 

... 

t0 = "div" 

... 

} else { ... } 

return <t0>{children}</t0> 

``` 

This is obviously wrong. It's also wrong to rename `t0` -> `T0`, because React 
treats that as a custom component, not a builtin. The right thing is to 
explicitly model builtin components, which this PR does by making 
`JsxExpression.tag` be a union of Place | BuiltinTag.
2023-04-26 11:27:35 -07:00
Joe Savona
9aea37ff5a Promote temporaries used in JSX to uppercase names
Ensures that temporaries used in JsxExpression tags are named with a capital 
letter so that they are treated as custom components rather than builtins.
2023-04-26 11:27:34 -07:00
Lauren Tan
5f2f76cbcd Fix test262
We [renamed the 
export](6f001f54b0/forget/src/Babel/RunReactForgetBabelPlugin.ts (L22)) 
of runReactForgetBabelPlugin so the test262 preprocessor was erroring
2023-04-26 14:12:18 -04:00
Mofei Zhang
5ed297fa74 [snap tester] Support @only / @skip with special file
--- 

Changes: 

- Added `testfilter.txt` 

``` 

// @only 

call 

capture-param-mutate 

jsx-spread 

``` 

or 

``` 

// @skip 

call 

error.todo-kitchensink 

``` 

- grouped all commands under `--mode` 

```js 

// runs all tests 

yarn snap 

// runs all tests and updates fixtures 

yarn snap --mode update 

// runs only tests that pass `testfilter.txt` 

yarn snap --mode filter 

// run in watch mode 

yarn snap --mode watch 

``` 

- in watch mode, toggle between running all tests or filtered tests 

``` 

386 Tests, 386 Passed, 0 Failed 

Completed in 4994 ms 

Current mode = NORMAL, run all test fixtures. 

Waiting for input or file changes... 

u     - update all fixtures 

f     - toggle (turn on) filter mode 

q     - quit 

[any] - rerun tests 

> f 

PASS  call 

PASS  capture_mutate-across-fns 

PASS  timers 

3 Tests, 3 Passed, 0 Failed 

Completed in 39 ms 

Current mode = FILTER, filter test fixtures by "testfilter.txt" 

Waiting for input or file changes... 

u     - update all fixtures 

f     - toggle (turn off) filter mode 

q     - quit 

[any] - rerun tests 

``` 

--- 

- `runner.ts` is pretty large now, happy to split it up into multiple files 

- I'd also like to refactor `watch` to make its shared state and control flow 
explicit
2023-04-25 14:36:06 -04:00
Mofei Zhang
bf518c2b2c [snap tester] watch mode: ignore changes from test updates
A bit of a hack - 

We currently trigger test runs when we detect changes in the test fixtures 
directory. This trigger is also hit when we run `snap` in update mode, since 
updating performs file writes. 

This PR will ignore subscription changes (callbacks) that trigger within 5 
seconds of the last update. 

It seems difficult to be more granular with a timestamp, since `@parcel/watcher` 
doesn't give us the file change timestamp and (from my understanding), other 
promises and tasks can be queued to run between the update and callback.
2023-04-25 14:15:25 -04:00
Mofei Zhang
a8db941fb7 [snap tester] Handle interrupts by forking runner
Run snap tester on a forked node process so runs can be interrupted. (Currently, 
`Ctrl+C` is not handled until after all test fixtures finish compiling). This 
*feels* a bit heavy-handed, but main.ts is pretty small and doesn't do much 
other than listen for input / signals. Would love feedback here since I haven't 
really worked with nodejs / JS cli tools before 

- main.ts 

new file that spawns forked runner 

- pipe stdin/out/err to and from the child runner process, details described in 
comments. 

- listens for exit event of child 

- runner.ts 

- added logic to listen for interrupts and clean up (not really familiar with 
how file and tsc watchers are implemented, so we try to call 'close' on them 
just in case they need to release locks / do other cleanup)
2023-04-24 19:09:40 -04:00
Mofei Zhang
4cf86028c0 [snap tester] Patch runner for sync mode
--- 

`yarn snap --sync` currently fails on `error.file-has-non-critical-errors`. This 
is because we're relying on a globally overwritten `console.error` function to 
report non-fatal errors. However, executing `Promise.all(...)` on a single 
nodejs thread will interleave calls to `run` (which is an async function).
2023-04-24 15:52:54 -04:00
Mofei Zhang
46752845eb [snap tester] QoL: help messages, cmd line validation, ts errors
--- 

Next PRs: skip / only tests, pretty diffing 

This PR: 

1. Add help messages: 

``` 

$ node packages/snap/dist/runner.js --help 

Options: 

--version         Show version number                                [boolean] 

--sync            Run compiler in main thread (instead of using worker threads 

or subprocesses). Defaults to false. 

[boolean] [default: true] 

--worker-threads  Run compiler in worker threads (instead of subprocesses). 

Defaults to true.                  [boolean] [default: true] 

--watch           Run in watch mode. Defaults to false (single run). 

[boolean] [default: false] 

--update          Run in update mode. Update mode only affects the first run, 

subsequent runs (in watch mode) require typing `u` to 

update. Defaults to false.        [boolean] [default: false] 

--help            Show help                                          [boolean] 

  Done in 0.62s. 

``` 

``` 

... 

386 Tests, 386 Passed, 0 Failed 

Completed in 4434 ms 

Waiting for input or file changes... 

u     - update fixtures 

q     - quit 

[any] - rerun tests 

``` 

2. Surface typescript diagnostics; skip test fixtures if source code has errors 

``` 

$ node packages/snap/dist/runner.js 

src/Optimization/ConstantPropagation.ts:87:3 - error TS1434: Unexpected keyword 
or identifier. 

src/Optimization/ConstantPropagation.ts:87:3 - error TS2304: Cannot find name 
'lt'. 

src/Optimization/ConstantPropagation.ts:87:6 - error TS2552: Cannot find name 
'hasChanges'. Did you mean 'onhashchange'? 

src/Optimization/ConstantPropagation.ts:135:11 - error TS2552: Cannot find name 
'hasChanges'. Did you mean 'onhashchange'? 

src/Optimization/ConstantPropagation.ts:155:10 - error TS2552: Cannot find name 
'hasChanges'. Did you mean 'onhashchange'? 

Compilation failed (5 errors). 

Found errors in Forget source code, skipping test fixtures. 

  Done in 10.73s. 

``` 

``` 

Compiling... 

src/Optimization/ConstantPropagation.ts:86:39 - error TS2552: Cannot find name 
'HIRFunctin'. Did you mean 'HIRFunction'? 

Compilation failed (1 error). 

Test: Found errors in Forget source code, skipping test fixtures. 

Waiting for input or file changes... 

u     - update fixtures 

q     - quit 

[any] - rerun tests 

```
2023-04-24 15:52:52 -04:00
Lauren Tan
a6769df6b1 [babel] Alias unstable_useMemoCache to useMemoCache
DevTools relies on built-in hook names at their call site to be unprefixed in 

order to correctly track them. This PR updates our Babel plugin to: 

- Check if there are any existing import declarations of `import { /* ... /* } 
from 'react';` 

- If true, we add the specifier `unstable_useMemoCache as useMemoCache` 

- Otherwise, we synthesize a new import declaration 

- In Codegen we now emit `useMemoCache(n)` rather than 
`React.unstable_useMemoCache(n)`
2023-04-26 13:40:22 -04:00
Joe Savona
32dccd48d2 [hir] Remove flag for inlineUseMemo 2023-04-21 13:43:32 -07:00
Lauren Tan
b40de89805 Run snap in ci 2023-04-21 15:22:56 -04:00
Lauren Tan
2b75c7915b Add @panicOnBailout support to snap
Test plan: tests pass with snap
2023-04-21 15:22:56 -04:00
Lauren Tan
b184d8567c [be] Add snap to yarn commands 2023-04-21 15:22:56 -04:00
Joe Savona
aa3edc8130 Cleanup unused wasm-based DOT diagram renderer
I forgot to remove this when we removed the diagram output. This brings test 
time from 6s -> 5s on my machine, still much slower than the new test runner in 
#1486.
2023-04-21 08:03:38 -07:00
Joe Savona
4a67d539dc Prune scopes wo own outputs
#1507 Ensured that declarations of reactive scopes were propagated to parent 
reactive scopes as necessary to ensure that those declarations would be 
available at the appropriate block scope. This meant that some scopes that were 
previously pruned would no longer be pruned. Specifically, an outer scope wo any 
declarations, but which contained a nested scope _with_ a propagated 
declaration, would now end up with non-empty declarations and not be pruned. 

This PR changes to track the declaring scope of each declaration, so we still 
prune scopes that don't have any of their own declarations.
2023-04-21 08:03:37 -07:00
Joe Savona
cb72726e38 Cleanup Stack impl
Originally I defined `Stack` as an interface to ensure both Node and Empty 
variants would have an identical API. But exporting an interface allows a 
developer to define other implementations, when we really want to ensure that a 
Stack is precisely a Node or Empty instance. This PR changes to exporting a 
union of `Stack = Node | Empty`, and makes the interface private to the module.
2023-04-21 08:03:37 -07:00
Joe Savona
f4896b45b2 Fix temporaries accessed outside of their defining scope
Fixed a bug identified in repro cases earlier in the stack. The case is where 
some later value is composed of several values, say A and B, where A is an 
identifier that is reassigned within B. Also, the mutable range of B surrounds 
the evaluation of A. In this case, the reference to A gets lowered to a 
temporary (say a t0 = LoadLocal A), and that temporary is created within the 
reactive scope for B. 

PropagateScopeDependencies bypasses LoadLocal indirections, and considers the 
reference to the temporary (t0) as if it was a reference to the identifier (A). 
That breaks the whole reason we lower Identifiers to temporaries - to preserve 
evaluation order. 

This PR fixes the bug by promoting temporaries to names values if they are 
referenced outside their defining scope. So, the reference to t0 stays a 
reference to t0, which correctly preserves the value of A at the right point in 
time. 

This is all much easier to see in the new test case.
2023-04-20 18:52:39 -07:00
Joe Savona
e9abc41ea3 Fix evaluation order for JSX element tags
When lowering a JSX element we were correctly lowering to a temporary in all but 
one case: the common case of an identifier. That is fine in practice but breaks 
in the presence of the tag identifier being reassigned in the props/children. 
This PR fixes to always lower the tag to a temporary.
2023-04-20 16:48:34 -07:00
Joe Savona
db378d39f3 Constant propagation for globals
This PR updates ConstantPropagation to support propagating global references: 

```javascript 

// Before 

const x = Math; 

foo(x); 

// After 

foo(Math); 

``` 

This is a generally useful optimization but also helps with a subset of cases 
around JSX element tags, which are frequently globals.
2023-04-20 16:48:30 -07:00
Joe Savona
5c9614785c [codegen] Create new temporary map when entering reactive scopes
During codegen, when we now cache and restore the temporary values map as we 
enter and exit the scope. This ensures that any temporaries within the reactive 
scope are only visible within that scope, and not to subsequent code. In a 
subsequent PR this surfaces a bug with temporaries not correctly exported from a 
reactive scope.
2023-04-20 16:48:26 -07:00
Joe Savona
c72452895a [codegen] validate that temporary values are set
In codegen when we lower an operand we check to see if we have an already 
lowered value for it (stored in `cx.temp`). Currently we silently handle missing 
values by emitting a raw identifier, but this is really an error. This PR adds 
validation, which uncovered a few places where we legitimately won't have a 
value - things like function parameters that got swapped for temporaries bc of 
destructuring. We populate those as `null` values now, and fail if a temporary 
had a truly missing value.
2023-04-20 16:48:22 -07:00
Lauren Tan
8bab0bca3c [babel] Compile individual components
Updates the Babel plugin so that we can individually compile components and skip 
over ones that have non-critical errors
2023-04-20 16:21:40 -04:00
Lauren Tan
c228d63f03 [babel] Add panicOnBailout option
Adds an option to always throw errors regardless of severity (default, ie the 
status quo), or when the flag is disabled, only critical errors will be thrown. 
Any error that isn't considered a critical error (see 
`CompilerError.isCritical()`) since it might indicate that the compiler is 
buggy, while non-critical errors will result in that file being skipped for 
compilation, but otherwise continue compiling other files
2023-04-20 16:21:40 -04:00
Joe Savona
275c755715 Repro for jsx lowering issues
* JSX tag value temporaries getting promoted due to being sandwiched inside the 
mutation of some other item 

* Incorrect order-of-evaluation for jsx tag relative to props/children
2023-04-20 09:29:37 -07:00
Joe Savona
3aa6196e30 Starting point for a new, parallel test runner to replace our hacked Jest
snapshotting 

As demo'd on our sync. This is meant as a replacement for `yarn test` just for 
fixtures. On my machine, a test run from a steady state of Jest watch mode takes 
6 seconds. With this script, it takes ~800ms. 

Workflow: make edits in `packages/snap/`, then `yarn build` in that directory to 
build the test runner. 

To run tests, `node packages/snap/dist/runner.js` from the main forget/ 
directory to run tests. Pass `--watch` for watch mode, `--update` to update 
snapshots. Note that this _only_ updates .expect.md files, it does not update 
Jest's `__snapshots__` directory (this is intentional, our use of Jest snapshots 
is a hack that this script is meant to replace). 

When running in watch mode, ctrl-c or q will quit, 'u' will update snapshots, 
and any other key will re-run tests. Tests will re-run on changes to the source 
code (after an incremental TS rebuild) and on changes to the fixtures. 

Main things that are missing: 

* Don't run tests if TS compilation had errors (the errors should be logged to 
console already, but we need to wire that up so that we abort tests if there are 
errors) 

* Actually delete stale files in update mode (there is some commented-out code 
to double-check and then uncomment) 

* Improve diff view: just print the diffed segments, not the whole file diff. 
See the API at https://github.com/facebook/jest/tree/main/packages/jest-diff 
(right now we're using `diff()` but should probably use one of the lower-level 
functions) 

* Properly parse/validate args with `yargs`, add a help option, etc 

* In watch mode, when tests finish print a list of commands so users know what 
they can do (like Jest does) 

* Support skipping tests and selecting a single test to focus. Suggestions: 

* Name files with `skip.` to skip 

* Allow passing a test name to the script to run only tests matching that 
pattern, eg `node runner.js -p <pattern>` 

* In watch mode, support typing 'p' to prompt the user for a pattern. After 
that, support typing 'a' to clear the pattern and run all tests.
2023-04-19 14:07:06 -07:00
Joe Savona
a0777212df Propagate scope declarations to parent scopes
Fix for bug demonstrated in #1506. When we add variables as output of their 
defining scope, we need to propagate this information upwards to all parent 
scopes which are not current active.
2023-04-18 21:27:11 -07:00
Joe Savona
fdbfac8f82 Repro for bug with memo vars at wrong block scope
Minimal(ish) repro of a bug we saw internally, where an output of a nested 
reactive scope is defined at the wrong block scope, and so later references to 
that value are invalid. 

The simplified structure is: 

``` 

scope0 inputs=[] outputs=[] { 

scope1 inputs=[] outputs=[t0] { 

t0 = ... 

} 

} 

t0 

``` 

Note that `t0` correctly appears as an output of the inner scope1, but not as an 
output of the outer scope0. We need to propagate outputs upward as necessary to 
ensure they are available at the right block scope: in this case, that would add 
`t0` as an output of scope0. 

An earlier version of PropagateScopeDependencies did this but it looks like it 
got lost along the way (not a big deal)
2023-04-18 16:10:00 -07:00
Joe Savona
746c1dbed5 Add common (Relay) hooks to playground environment
This aligns the playground configuration with our internal compiler 
configuration to make it easier to repro compilation issues on playground. There 
is a bug that doesn't repro right now and i suspect it's because of different 
hooks being configured. 

Test plan: 

Before: playground output has no obvious bugs, but is different than internal 
compilation output where the bug occurs 

After: playground output matches internal compilation output w the bug
2023-04-18 15:29:17 -07:00
Joe Savona
de89ed32a2 Use implicit return (undefined)
Making ReturnTerminal.value non-nullable broke our optimization to elide final 
value-less return statements. We now check if a return value is explicitly 
`undefined` and elide the value in this case, which then also propagates to 
allow removing the final `return` statement of a function if the value is 
missing.
2023-04-18 11:12:27 -07:00
Joe Savona
b10f60f2b9 Cleanup from non-nullable return value
Now that ReturnTerminal.value is non-nullable we can make 
ReactiveReturnTerminal.value also non-nullable, and remove a bunch of null 
checks.
2023-04-18 11:12:23 -07:00
Lauren Tan
0ddcaafd7c [babel] Inject React import if Forget compiled the function
Updates our Babel plugin to add an import to React if we succesfully compiled a 
function and cached one or more values. For now this logic is entirely in the 
Babel plugin itself, but long term we should move this into codegen and teach 
Forget to start the pipeline by lowering the whole Program node to an HIR (which 
would also allow us to understand imports and other module scope values being 
used). Right now it's not possible to add in codegen (without some ugly code) as 
a file containing multiple components would result in duplicate imports being 
generated
2023-04-17 17:49:17 -04:00
Sathya Gunasekaran
7c5e5eb30c [hir] Make return.value non nullable 2023-04-17 21:11:44 +01:00
Sathya Gunasekaran
42459f579f [hir] Return undefined explicitly if there's no return value
Future passes like inlineUseMemo assume there's a return value so let's create 
one instead of implicitly returning undefined.
2023-04-17 21:11:43 +01:00
Sathya Gunasekaran
0e34c39c58 [test] Add failing test for empty return in useMemo callback 2023-04-17 21:11:42 +01:00
Sathya Gunasekaran
00d4a69459 [hir] Refactor useMemo inlining
Previously useMemo inlining created a new StoreLocal assignment (not 
reassignment!) instruction for every return value. This breaks when the return 
is inside a block (like an if-block) as the  scope is tied to the block. 

For example: ``` let x = useMemo(() => {   if (...) {     return { ... };   } }) 
``` would become: ``` if (...) {   const temp = { ... }; } const x = temp; ``` 

This PR instead changes the inlining to declare a temporary in the function 
prologue and then reassign values to it when replacing return statements. 

``` let x = useMemo(() => {   if (...) {     return { ... };   } }) ``` 

becomes 

``` let temp; if (...) {   temp = { ... }; } const x = temp; ```
2023-04-17 20:59:35 +01:00
Sathya Gunasekaran
31f6dd8070 [test] Add failing test showing useMemo bug 2023-04-17 20:59:35 +01:00
Sathya Gunasekaran
34bca247d6 [hir] Terminate function within BuildHIR
All the block building logic is encapsulated in BuildHIR.
2023-04-11 15:40:08 +01:00
Sathya Gunasekaran
c42cd3f558 [hir] Refactor HIRBuilder.build to use HIRBuilder.terminate
It's a bit weird  that HIRBuilder is generating terminals, a follow up PR will 
remove this entirely from HIRBuilder. This is a first step towards that.
2023-04-11 15:37:20 +01:00
Sathya Gunasekaran
7678a52c9c [hir] Add invariant to check if useMemo callback is async or a generator 2023-04-11 15:06:53 +01:00
Sathya Gunasekaran
fd252e42ab [hir] Add invariant to check if useMemo callback accepts args 2023-04-11 15:00:30 +01:00
Sathya Gunasekaran
74f5e26e00 [be][test] Remove hir-test 2023-04-11 13:41:33 +01:00
Sathya Gunasekaran
2d8b36467c [hir] Put useMemo inlining behind a flag
It's still a WIP so disable behind a flag for now.
2023-04-11 13:41:33 +01:00
Joe Savona
67b0cf8a8c Comments from #1484 2023-04-10 11:06:10 -07:00
Joe Savona
601eb2a23a Inline useMemo callbacks to allow improved memoization
This is a simplified version of #1454. The goal of this PR is to inline the 
contents of `useMemo()` callbacks, rather than just immediately invoke the 
lambda. Turning useMemo() into an IIFE works, but it means that we can't 
optimize within the lambda block. Our investigations showed that there's a lot 
of room to optimize at a finer granularity than manually written useMemo calls. 
For example, one product instance had a useMemo that created a list of child JSX 
elements. Most of those elements only relied on a single variable (`a`), but a 
few relied on a second variable (`b). Thus _all_ elements were invalidated 
whenever `b` changed. If Forget retains the original lambda, we have no choice 
but to keep that (coarse) granularity for memoization. When we inline, we can 
optimize to make e.g. individual JSX elements depend on their precise 
dependencies. 

The rough idea is: 

* Keep track of all function expressions 

* When we find a useMemo, lookup its function expression, and add its CFG to the 
main function (the previous PR ensures that BlockIds won't collide) 

* Replace any return statements with a StoreLocal to save the result and a Goto 
to the code following the useMemo call. 

* Then we run the usual set of passes to patch the HIR back up again. 

Example: 

```javascript 

// Before 

function Component(props) { 

const x = useMemo(() => { 

if (props.cond) { 

return null; 

} 

return foo(props.x); 

}, [props.x]); 

return x + props.y; 

} 

// Intended - **before** memoization 

function Component(props) { 

let x; 

if (props.cond) { 

x = null; 

} else { 

x = foo(props.x); 

} 

return x + props.y; 

} 

```
2023-04-07 16:34:52 -07:00
Joe Savona
f7f7a88e19 Example suboptimal memoization with non-inlined useMemo 2023-04-07 16:34:51 -07:00
Joe Savona
6122f21393 Definition for useContext 2023-04-06 11:46:57 -07:00
Joe Savona
f51e2a6bf4 Enforce terminal has a .loc (and .id)
This PR ensures (via a static assertion function) that all terminal variants 
have a SourceLocation, and adds locations to the variants which didn't have it 
before. This also adds a static assertion that terminals have an InstructionId, 
though we already relied on that so it was checked via usage.
2023-04-06 15:50:43 -07:00
Joe Savona
5622c0ee91 Use single name resolver for nested functions
This is a prerequisite to inlining `useMemo()` lambdas so that we can better 
optimize them. Nested functions are evaluated with a fresh HIRBuilder, which 
means that they currently have their own `bindings` object for mapping 
identifier instances to IdentifierIds. This means that identifier ids in a 
closure are _always_ different that those outside the closure, even when they 
refer to the same identifier: 

``` 

function Component(props) { 

props; // becomes e.g. props$1 

const onClick = () => { 

props // becomes e.g. props$2 

}; 

} 

``` 

For useMemo inlining this is problematic because we've lost the association that 
these identifiers actually refer to the same thing. This PR changes that, 
sharing the name resolution data structure between the top-level function and 
any nested function expressions.
2023-04-06 15:25:30 -07:00
Lauren Tan
fcfb66914a [Babel] Skip files that contain one or more disables of React eslint rules
To unblock internal experimentation, for now let's just skip over compiling any 
file that contains one or more disables of React's eslint rules, and log that. 
This is a little coarse in the sense that we could skip over just functions that 
contain the comments, but Babel doesn't provide an easy way to traverse comments 
afaict so this is the simplest solution. I did check our internal repo and noted 
that there was only one disable of exhaustive-hooks in that entire directory in 
one file, so this should be fine. 

Notably we are not throwing any errors if we detect these violations as we don't 
want to fail the build, we just want to skip them for now.
2023-04-06 19:14:08 -04:00
Lauren Tan
2eae7dc6b9 [be] Simplify yarn test
Updates our scripts to only hash and clean up`dist` when bundling for Meta, 
since we don't need to do that for tests.
2023-04-06 19:10:21 -04:00
Joe Savona
9c1f8a962c Make CompilerError.reason a static string
While running the latest Forget build on www I noticed that a lot of the 
bailouts were special-cases where we used interpolation in the error `reason` 
string to provide more context for debugging. This is a pretty cool result, 
because it means that we actually support nearly all the common syntax (at least 
based on a sample of the codebase). But it makes our tools for aggregating 
errors break down a bit. 

This PR adds a new, nullable `description` property to CompilerErrorDetail, and 
manually updates to ensure that we always pass a static `reason` and only use 
interpolation in the `description`. This will allow our aggregation tools to 
group by the reason.
2023-04-06 08:51:31 -07:00
Joe Savona
702f43eb1e Optimize away extraneous blocks
In codegen we can flatten away extraneous BlockStatements if they turn out not 
to need a label.
2023-04-05 16:37:26 -07:00
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](207595e04e/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
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
Joe Savona
c6d1181ba3 Refactor DCE internal state into a class
Tidying up the implementation to make the next PR's changes easier to follow.
2023-03-15 13:20:26 -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
Joe Savona
957d8f1d44 Remove incomplete try/catch support 2023-03-15 15:36:17 -07:00
Joe Savona
5bc2c807c3 Support array pattern params 2023-03-15 15:33:14 -07:00
Joe Savona
b69afe0506 More precise error for canonical reassignment in a value block
These examples previously errored all the way in codegen, when we detected that 
a value block (eg a `while` test expression) was declaring a new variable. We 
now detect this in LeaveSSA and error. The actual fix is a bit tricky, we'd need 
to add a new declaration in the nearest block scope (or selectively not DCE the 
declaration if its reassigned in just this way).
2023-03-15 09:39:23 -07:00
Joe Savona
9200ad027d Throw CompilerError (todo) for unused conditional/logical
If a logical or conditional expression is unused, then a phi node isn't created 
for the identifier it assigns to. Then when we leave SSA form the two branches 
will assign to separate values, and we aren't sure which identifier to use as 
the lvalue of the resulting ReactiveInstruction (remember that 
logicals/conditionals decompose into control flow in HIR, but are a single 
compound instruction in ReactiveFunction). If the two sides don't assign to the 
same location, it could be because of a bug in the compiler or because the value 
wasn't used. Ideally we'd represent this explicitly, but for now i'm just making 
this a TODO since most logicals/conditionals should have their value used.
2023-03-15 09:39:23 -07:00
Joe Savona
7537df3d4e Support assignment expressions in value blocks
Enables support for assignment expressions in value blocks (which includes in 
loop init/test/update blocks). This was pretty straightforward, the main changes 
are: 

* During PropagateScopeDependencies, we currently record scope reassignments 
based on `Identifier` object identity. In the case where a variable is 
reassigned in multiple control-flow paths of a value block, however, there can 
be multiple object identities. So we now de-dupe reassignments based on 
identifier id. 

* MergeOverlappingScopes now treats value blocks as regular blocks, allowing it 
to correctly merge scopes from the value with other scopes from the outer block. 

Otherwise this is mostly just lots of tests. Note that there is an outstanding 
todo, which is that we currently error for ternaries and logicals whose value is 
unused (eg `cond ? (x = 1) : null`). I'll address that in a follow-up.
2023-03-14 20:02:05 -07:00
Joe Savona
59cd1ca569 LeaveSSA: consistently rename identifiers even for value block phis
This PR clarifies the logic for adjust mutable ranges of phis and their operands 
during LeaveSSA. Previously we had logic in several places to determine 
whether/how to extend the ranges of each phi and its operands: this occurred 
while traversing reassignmentPhis (in 2+ places) and rewritePhis, as well as in 
rewritePlace(). 

This was kind of a band-aid to make things work, but the logic was imprecise. 
The actual rules are as follows: 

If there is a back-edge, or the phi id is unnamed, then were extend the ranges 
of the phi and its operands to min(starts) and max(ends). This ensures that the 
operands are computed as one unit, ie put into a single reactive scope. For 
loops this is necessary because...looping! For unnamed values this is necessary 
because of the way we collapse logical and ternary expressions back to a 
hierarchical ReactiveFunction — we need to make sure the final mutable range 
extends from the start of the final instruction up to the end of the 
logical/ternaries value blocks. 

Otherwise this is a phi where operands come from predecessors and are named. If 
the phi is mutated later, then we have to extend the end of each operand's range 
to account for the fact that they can be mutated later. Else, we leave the 
operands alone. 

Behavior doesn't change, but we consolidate all of the mutable range logic in 
one place.
2023-03-14 15:24:38 -07:00
Joe Savona
23e0ce02a6 Make JSX memoization optional (on by default)
Makes JSX memoized by default again, but adds an option to disable memoization 
of JSX. Also adds a new test and fixtures directory to test the opt-in 
no-jsx-memoization behavior.
2023-03-13 13:25:09 -07:00
Mofei Zhang
9465cb2100 [hir] Move Types to a separate file
--- 

Move types around in preparation for next PR #1335. No changes in impl, types, 
or functions.
2023-03-13 14:52:15 -04:00
Mofei Zhang
b3086817d7 [tests][hir-typer] Add hir-tests fixture
--- 

Currently, we run type inference passes early in the pipeline and do not check 
inference output in any tests, test fixtures, or verifier passes. In fact, the 
only ways to view inferred types are (1) locally add a test fixture with`@only` 
and inspect console logs or (2) scroll to the relevant section on a playground 
example. 

However, inferred types and effects significantly affect the output of later 
passes (Alias / MutableRange analysis, InferReactiveIdentifiers, etc), and we 
have already found some bugs due to incorrect inference (e.g. #1274). 

This PR add the `typer-tests` fixture with the following goals 

1. Record relevant current compiler type + effect inference output. 

2. Have relatively stable output (with respect to changes in HIR and PrintHIR). 

- we try to achieve this by annotating the source code.
2023-03-13 14:52:15 -04:00
Mofei Zhang
03f3e541ce [tests][hir-typer] Add hir-tests fixture
--- 

Currently, we run type inference passes early in the pipeline and do not check 
inference output in any tests, test fixtures, or verifier passes. In fact, the 
only ways to view inferred types are (1) locally add a test fixture with`@only` 
and inspect console logs or (2) scroll to the relevant section on a playground 
example. 

However, inferred types and effects significantly affect the output of later 
passes (Alias / MutableRange analysis, InferReactiveIdentifiers, etc), and we 
have already found some bugs due to incorrect inference (e.g. #1274). 

This PR add the `typer-tests` fixture with the following goals 

1. Record relevant current compiler type + effect inference output. 

2. Have relatively stable output (with respect to changes in HIR and PrintHIR). 

- we try to achieve this by annotating the source code.
2023-03-13 14:49:53 -04:00
Mofei Zhang
814a21c97b [prettier] Ignore flow files in incremental formatting 2023-03-13 14:49:52 -04:00
Lauren Tan
e90d058bee [babel] Remove prettier from plugin
I think we added this for easier debugging but isn't strictly needed. Remove 
since this was causing some issues internally.
2023-03-13 14:32:32 -04:00
Joe Savona
d376cf1e37 Memoize arrays/objects created with destructuring spread
We were treating Destructuring as if it could never allocate and therefore 
didn't have to be memoized. That's only true if there are no rest spreads 
though. This PR teaches the compiler to treat rest spreads differently for 
scoping and memoization 

purposes, fixing the newly added test case and some existing bugs.
2023-03-09 12:44:30 -08:00
Joe Savona
fcbfca69c9 More tests
More tests, in particular demonstrating that we don't memoize destructured rest 
elements propery — yet! Follow-up fixes.
2023-03-09 12:44:26 -08:00
Joe Savona
58a1cd872e Be explicit about lvalues (to distinguish their memo level later)
This is prep for #1345, which distinguishes different memoization levels per 
lvalue
2023-03-09 12:44:22 -08:00
Joe Savona
9048f59237 Prune scopes whose values dont escape
Adds a new pass that uses escape analysis and React-specific heuristics to tune 
the amount of memoization applied. Specifically, the pass ensures that we only 
memoize: 

* Values which escape (are directly returned or transitively aliased by a 
returned value) 

* ...and that are not JSX elements 

* OR values which are _dependencies_ of scopes that produce an escaping value. 

The latter case is necessary to avoid breaking memoization of an escaping value 
bc a scope happened to have a non-escaping dependency. 

## Algorithm 

1. First we build up a graph, a mapping of IdentifierId to a node describing all 
the scopes and inputs involved in creating that identifier. Individual nodes are 
marked as definitely aliased, conditionally aliased, or unaliased: 

a. Arrays, objects, function calls all produce a new value and are always marked 
as aliased 

b. Conditional and logical expressions (and a few others) are conditinally 
aliased, depending on whether their result value is aliased. 

c. JSX is always unaliased (though its props children may be) 

2. The same pass which builds the graph also stores the set of returned 
identifiers 

3. We traverse the graph starting from the returned identifiers and mark 
reachable dependencies as escaping, based on the combination of the parent 
node's type and its children (eg a conditional node with an aliased dep promotes 
to aliased). 

4. Finally we prune scopes whose outputs weren't marked.
2023-03-09 09:51:43 -08:00
Lauren Tan
faa8eef0a8 [be] Fix remaining lints and enable lint in CI 2023-03-10 16:21:01 -05:00
Joe Savona
7813cfa52c Fix EliminateRedundantPhi for cascading eliminated phis
This was the actual bug. When EliminateRedundantPhis eliminates a phi, it has to 
rewrite downstream usages of the phi id to the single operand id. We were 
correctly doing that in all but one place. When we iterate _downstream phis_, we 
were looking up the operands against the rewrite table, but not updating the phi 
operands themselves to the rewritten value. 

This fixes the bug, and incidentally fixes a test that has been broken for a 
while and nagging at me.
2023-03-08 21:10:07 -08:00
Joe Savona
1fb0aed668 Fix mapInstructionOperands() visitor
Found while debugging the previous issue: `mapInstructionOperands()` should not 
look at lvalues. The previous version was causing us to create extra phi nodes, 
which interestingly weren't the actual problem behind the "SSA" bug, but sure 
looked like it at first.
2023-03-08 20:49:32 -08:00
Joe Savona
ff855b1d34 Repro case for product bug
Simplified version of the example. As I eventually uncovered in #1342, the issue 
is cascading redundant phis not getting rewritten.
2023-03-08 20:49:28 -08:00
Joe Savona
6d4b0c61b3 Export ValueKind and Effect
We need to be able to reference these to teach the compiler about custom hooks 
like Relay.
2023-03-08 15:56:25 -08:00
Joe Savona
f985d6cdba PropertyDelete/ComputedDelete instructions 2023-03-08 12:04:27 -08:00
Lauren Tan
0363648178 [babel] Fix export ordering
Turns out hoisting doesn't work with export declarations, so fix ordering such 
that the export always comes after the synthesized test declaration.
2023-03-08 11:42:57 -05:00
Sathya Gunasekaran
42c04fdcc7 [test] Add test to show Forget's inference is run inside lambda
JSX reads the value of `z` and does not mutate it so `z` can be independently 
memoized.
2023-03-08 00:16:00 +00:00
Sathya Gunasekaran
8695ea05c3 [test] Add test for capturing a ref before renaming
The renaming does not affect the lambda correctly.
2023-03-08 00:15:57 +00:00
Sathya Gunasekaran
80ab2f25a6 [test] Add test for capturing a renamed ref
The ref isn't renamed inside the lambda, needs to be fixed.
2023-03-08 00:15:54 +00:00
Sathya Gunasekaran
d155d2dd81 [test] Add test for lambda that conditionally captures ref
Conditionally captured refs are correctly added to dependency list of the 
lambda.
2023-03-08 00:15:51 +00:00
Sathya Gunasekaran
1e8b6b51d3 [test] Add test for lambda that shadows ref
There is no ref captured as it is shadowed and the lambda does not depend on the 
ref.
2023-03-08 00:15:48 +00: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
f3c662845b Support string literal keys for object expressions
For ObjectExpression, we now support computed keys where the key is a string 
literal.
2023-03-06 14:51:18 -08:00
Joe Savona
a9d6d2d95a [rfc] always create collections even if elements have errors
BuildHIR currently propagates UnsupportedNodes for collection types where the 
element itself can fail (for example object expressions where the key may not be 
valid). However, given that we currently abort compilation after the first 
failing pass (and will probably do so for quite a while) I think we can simplify 
and just always return the collection. Note that I already did this for 
destructuring. I'm open to leaving the code as-is if you prefer, though.
2023-03-06 14:43:58 -08:00
Joe Savona
b793fe424d Separate eachLValue/eachOperand visitors
This PR starts to clean up our handling of lvalues and rvalues by adding new 
`eachInstructionLValues()` and `mapInstructionLValues()` helpers. Now, 
`eachInstructionOperand()` and `mapInstructionOperands()` only visit true 
rvalues, and the new passes must be used to visit lvalues. This allows us to 
remove the special-casing for StoreLocal and Destructure in most of the passes.
2023-03-06 14:33:52 -08:00
Sathya Gunasekaran
cf03bac12a [be] Move skipped test to kitchensink 2023-03-07 16:21:34 +00:00
Sathya Gunasekaran
8477195c74 [babel] Re-export compiled functions 2023-03-07 16:08:16 +00:00
Sathya Gunasekaran
39898c1858 [babel] Add gating module support
Instead of replacing original function with compiled code, this adds an option 
to append the code and switch between the two based on an `isForgetEnabled` test 
condition that's imported from the specified gatingModule.
2023-03-06 19:54:00 +00:00
Sathya Gunasekaran
2ede182f5e [test] Enable gatingModule in tests 2023-03-06 19:53:57 +00:00
Sathya Gunasekaran
c642de322c [test] Enable "use forget" directive in tests 2023-03-06 19:53:54 +00:00
Sathya Gunasekaran
8c24fc2be2 [pipeline] Type the result as t.FunctionDeclaration
A more narrower type as we only accept FunctionDeclaration as inputs
2023-03-06 19:53:52 +00:00
Sathya Gunasekaran
3cd31a11fb [babel] Add option to specify a gatingModule 2023-03-06 19:53:49 +00:00
Sathya Gunasekaran
637c2e68f1 [be][babel] Move directive checking to separate function 2023-03-06 19:53:46 +00:00
mofeiZ
0e1bba87ea [be][cleanup] Split primitive tests to individual fixtures
Followup to #1273 

No changes, just moving test functions to their own fixture files.
2023-03-06 18:25:53 -05:00
Mofei Zhang
8be45e7d4a [rhir][optim] Preserve conditional deps when propagating reactive scopes
--- 

**This PR slightly changes the semantics of ReactiveScopeDependencies**. 
Previously, reading a ReactiveScopeDependency is guaranteed to preserve the 
`nullthrows` semantics of its own declarations (not that of its inner scopes). 
This does not affect the overall correctness properties, since we already hoist 
reading of conditional dependencies (and thus may throw earlier than the 
original source). 

E.g. we already do not preserve *where* the nullthrows occurs. 

```javascript 

function Component(props) { 

// throws here, before print(x) 

const c_0 = props.a.b !== $[0]; 

let x; 

if (c_0) { 

x = {}; 

print(x); 

if (...) mutate1(x, props.a.b); 

mutate2(x, props.a.b); 

// ... 

``` 

### Summary 

This is an optimization, not a correctness property. 

When propagating reactive dependencies of an inner scope up to its parent, we 
want to *retain information about conditional dependencies* -- not the derived 
unconditional dependencies. This helps us produce more granular dependencies in 
the parent scope. 

Current implementation: 

```javascript 

const innerScopeDeps = innerScope.depTree.deriveMinimalUnconditionalDeps(); 

for (const dep of innerScopeDeps) { 

currentScope.depTree.addDep(dep); 

} 

``` 

New implementation: 

```javascript 

// union of a tree takes union of each node 

currentScope.depTree = currentScope.depTree.union(innerScope.depTree); 

``` 

### Example 

In the below example: 

- `scope @1` has a conditional dependency of `props.a.b`, but that reduces to 
the unconditional dependency `props` 

- `scope @0` itself has a unconditional dependency of `props.a.b` 

- Currently, Forget joins the derived / reduced dependencies of inner scopes, 
which adds `props` as unconditional dependency of `scope @0` 

- With this change, Forget joins the property trees and retains info about 
conditional deps, which adds `props.a.b` as a conditional dep of `scope @0`. 

```javascript 

// scope @0 (deps=[???] decls=[x, y]) 

let y = {}; 

// scope @1 (deps=[props] decls=[x]) 

let x = {}; 

if (foo) mutate1(x, props.a.b); 

mutate2(y, props.a.b); 

``` 

### Followup 

We currently keep track of properties unconditionally accessed per 
ReactiveBlock. Eventually we want to keep track of properties unconditionally 
accessed across blocks (as according to control flow). 

Consider the following code, in which sibling scopes 0 and 1 are sequentially 
executed. In this case, we can safely add props.a.b as a dependency of scope 1. 

```javascript 

// scope@0 (deps=[props.a.b], decls=[x]) 

let x = { a: foo(props.a.b) }; 

// scope@1 (deps=[???], decls=[y]) 

let y = {}; 

if (...) { 

mutate(y, props.a.b); 

} 

```
2023-03-06 16:33:08 -05:00
Mofei Zhang
e2698a3124 [rhir] Add printDeps for debugging reactive dependency trees 2023-03-06 16:33:07 -05:00
Mofei Zhang
e68ea941e6 [rhir] Promote conditional dependencies accessed by every cfg path
--- 

Implementation details summarized in comments. 

Overall, we want to calculate a `ReactiveDependencyTree` for every conditional 
block. If we know that conditional blocks are exhaustive (e.g. all CFG paths 
calculates a tree), we can take `intersection(depsFromEachBlock)` and add this 
to the parent Reactive + conditional scope `parentDeps = union(parentDeps, 
intersection(...))`. 

We use trees instead of individual deps here because we can still derive 
unconditional accesses. 

e.g. 

``` 

let x = {}; 

// props.a is an unconditional access here 

if (foo(other)) { 

x.a = props.a.b; 

} else { 

x.b = props.a.c; 

} 

```
2023-03-06 16:33:07 -05:00
Mofei Zhang
25bfe728aa [rhir] move DeriveMinimalDependencies to its own file
--- 

Small refactor of reactive dependency logic, no behavioral change. 

- Moves ReactiveDependencyTree logic into `DeriveMinimalDependencies`. 

- this moves hides most helper functions + types 🥳 

- made `ReactiveDependencyTree` a class 

- Changes `#dependencies` type: `Set<ReactiveScopeDep>` -> 
`ReactiveDependencyTree` 

- instead of collecting all dependencies into a tree in the end, we now eagerly 
join dependencies into the tree on `visitDep` 

- this is needed for the next PR in the stack, which relies on incremental 
merging
2023-03-06 16:33:06 -05:00
Joe Savona
5b11372901 Optimize DCE for destructuring
In #1287 i implemented basic handling for destructuring in DCE: if any of the 
pattern values are used, we retained the whole instruction as-is. However, 
ideally we could prune out unused elements from the pattern. There are pretty 
simple rules: 

* ArrayPattern we can eliminate unused elements from the end. 

* ObjectPattern we can eliminate any unused element, but only if there is no 
rest element.
2023-03-03 17:16:39 -08:00
Joe Savona
8233e8f95a All calls to lowerExpression lower to a unique temporary
This is more followup toward deleting Instruction.lvalue. The previous PR 
ensured that all Instruction.lvalue identifiers are only ever assigned to once. 
Now we ensure that `lowerExpression()` is _only_ called via 
`lowerExpressionToTemporary()`, ie we now always lower every single expression 
to a temporary. 

This will make it easier to make lvalue a part of the InstructionValue instead 
of the instruction itself, in follow-up PRs.
2023-03-03 17:09:27 -08:00
Joe Savona
fe2d179a61 Value block reassignment uses StoreLocal
As part of removing Instruction.lvalue we need to ensure that it is only used to 
represent that instruction's value — the InstructionKind should always be Const. 
The one place where we violated this was for value blocks, specifically 
ConditionalExpression and LogicalExpression. For both of those, we generate a 
single temporary place to represent the expression result. Then the consequent 
and alternate branch ended in a `LoadLocal` that reassigned that temporary (in 
the lvalue) to the result of that branch. 

This PR changes to use StoreLocal instead, and updates the recently added 
validation pass to ensure that all identifiers that appear in an 
Instruction.lvalue are only ever assigned once.
2023-03-03 17:09:26 -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
Sathya Gunasekaran
d24f3fdad9 [test] Add failing test of incorrect typing of captured ref
The typer doesn't understand that it needs to ignore captured references ("read" 
refs are ok, but not "mutated" refs) to be conservative.
2023-03-03 17:33:44 +00:00
Sathya Gunasekaran
ec2a179572 [be] Move phi aliasing to InferAliasForPhis
InferMutableRangesForAlias is about extending the mutable ranges, not for 
updating the alias sets. Let's refactor this into a separate pass. 

InferMutableRangesForAlias was iterating over alias sets and not the HIR so this 
refactor isn't costing us any additional perf cost (in terms of an extra 
iteration over the HIR).
2023-03-03 13:54:52 +00:00
Sathya Gunasekaran
1fffc842bc [hir] Don't redefine context refs in EnterSSA
Context references are special since reassignments need to update the original 
reference, not define a new place.
2023-03-03 13:35:04 +00:00
Sathya Gunasekaran
1e5e75746d [hir] Handle LHS of AssignmentExpression specially
Babel has a bug where it doesn't visit the LHS of an AssignmentExpression if 
it's an Identifier. Work around it by explicitly visiting it.
2023-03-03 13:35:04 +00:00
Sathya Gunasekaran
482ae7aa7c [hir] Refactor Babel expression visitor to separate function
We'll need to recursively call it in the future.
2023-03-03 13:35:03 +00:00
Sathya Gunasekaran
99eb48db07 [hir] Skip only MemberExpressions
Identifiers don't need skipping anyways, so this doesn't affect the existing 
behavior. 

In the future, we will special case handling of LHS of AssignmentExpression 
which will require us to not skip the RHS.
2023-03-03 13:35:03 +00:00
Sathya Gunasekaran
f17f4dcf0b [hir] Treat values escaping into a contextref as Mutate 2023-03-03 13:35:02 +00:00
Sathya Gunasekaran
f002682322 [hir] Treat reassignment as mutation for context refs
The mutable range difference for assignment is just 1 which is something we 
usually don't track as we care about mutation and not assignment. 

But this isn't true for context refs whose (re) assignment is actually a 
mutation.
2023-03-03 13:35:02 +00:00
Sathya Gunasekaran
24fbeddcea [test] Fix test to have initial value
Having undefined as the initial value makes this a primitive. There's a separate 
bug where we need to remove type inference for captured refs that get mutated 
but that's secondary -- we're currently not even marking the Identifier LHS as a 
captured ref. Fix the test to repro this bug for now.
2023-03-03 13:35:01 +00:00
Sathya Gunasekaran
9683771861 [packages] Add Forget version of benchmark 2023-03-02 11:06:00 +00:00
Sathya Gunasekaran
63fd00ab27 [hir] Remove unreachable fallthroughs after merging blocks
Fixes https://github.com/facebook/react-forget/issues/1141
2023-03-03 14:08:52 +00:00
Lauren Tan
0ad0f8cf43 [rhir] Visit DoWhile's test as a conditional dependency
Missed this in the prior PR!
2023-03-03 00:31:37 -05:00
Tianyu Yao
de6444b4f5 Add a logger option for logging compiler errors from Babel
Add a `logger` option so compiler errors are surfaced  in our metrics collection 
pipeline. 

We can probably later merge the global `log` function with it.
2023-03-02 16:59:09 -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
Sathya Gunasekaran
08c1eda7ff [hir] Simplify depRoot map
Use Identifier as a key, which lets us simplify lookup to no longer require an 
index access.
2023-03-01 16:48:54 +00:00
Lauren Tan
7a6a0e5f72 [be] Fix various eslints 2023-02-28 19:19:38 -05:00
Lauren Tan
f28eef3dca [eslint] Disable no-constant-condition and no-fallthrough 2023-02-28 19:19:37 -05:00
Lauren Tan
323a70d181 [playground] Fix compiler errors being cut off 2023-02-28 19:19:36 -05:00
Mofei Zhang
1fdbcfe162 [rhir] Add dependencies produced by active (incomplete) scopes
--- 

> If this operand is used in a scope, has a dynamic value, and was defined 
before this scope, then its a dependency of the scope. 

> (from current comments in PropagateScopeDependencies::visitDependency) 

A reactive scope can take a dependency from a definition produced by an 
incomplete parent scope. Our tests previously did not cover this, since most 
object types aliased together and remained mutable throughout a ReactiveScope. 

e.g. our tests did not have 

``` 

scope @0 (deps=...,  declarations=[x, y]) { 

x = {}; 

// define a reactive, immutable value that is not aliased to become mutable 

const immutableVal = ...; 

scope @1 (deps=immutableVal, declarations=[y]) { 

y = read(immutableVal) 

} 

mutateX(x, ...); 

} 

``` 

We should not add a dependency if it is produced in exactly the same scope as 
the one it is used. It is safe (and correct) to depend on values produced by a 
parent scope. 

--- 

Note that we still should check for whether a defining scope is active to 
determine whether it should be added as a output of that scope 
([src](b608ab20d5/forget/src/ReactiveScopes/PropagateScopeDependencies.ts (L469-L478))). 

Access of an identifier produced by a parent scope (i.e. adding a variable 
defined by a scope's parent as its own dependency) does not require adding that 
identifier to the parent's `declarations`, since that identifier is already 
valid to access via identifier binding rules.
2023-02-28 16:36:01 -05:00
Mofei Zhang
b532465ce2 [inference] Capturing an immutable value should be a read
--- 

Following #1216: 

If a value is known to be immutable, then it doesn't need to be considered 
'captured' since no mutation should occur. 

Couldn't figure out a unit test in which this specific fix matters, but we need 
this to fix test output of #1273 

cc. @gsathya, would love some feedback / eyes on this. This makes sense for 
Primitives in particular (which are always read / copied in rval position), but 
I'm not as familiar with edge cases for other immutable values especially around 
lambdas.
2023-02-28 16:36:00 -05:00
Mofei Zhang
474c38c573 [rhir][tests] Added tests for primitives as dependencies
--- 

Our current compiler has specific logic for determining what can be a reactive 
value / reactive dependency. 

Currently, all of the following affect whether an identifier is a reactive: 

- **alias analysis** (applicable to objects) 

- **data + control flow** (whether any other reactive identifiers is used in 
determining it) 

- **reactive scopes** (we generalize and say anything produced by a block with 
reactive dependencies must be non-stable and reactive) 

- this is not true in the case of const primitives, but an overestimate is safe 

- whether the **scope that declares this identifier** is ~~currently active~~ 
the same scope in which it is used (fixed by #1275) 

(since a scope cannot be dependent on itself) 

These conditions are complex. We end up inferring most identifiers as `mutable` 
and `object` types, which have different stability and aliasing properties from 
primitives. As a result, we're missing some cases in our existing test coverage. 

Test case output is fixed by #1274 and #1275 

--- 

(This can be separated from the stack below, which implements conditional 
dependencies. Happy to merge that first and open this as a new stack if that 
produces a significantly better Git PR history.)
2023-02-28 16:35:59 -05:00
Mofei Zhang
6b129b59ed [rhir] Refactor ReactiveScopeDependency, conditional dependencies (2/2)
--- 

See comment block in `PropagateScopeDependencies` and added test case 
`reduce-reactive-conditional-dependencies` for correctness properties / 
dependency merging logic.
2023-02-28 16:35:59 -05:00
Mofei Zhang
4005f862bd [rhir] Refactor ReactiveScopeDependency, unconditional dependencies (1/2)
--- 

See comment block in `PropagateScopeDependencies` and added test case 
`reduce-reactive-unconditional-dependencies` for correctness properties / 
dependency merging logic.
2023-02-27 13:38:23 -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
Mofei Zhang
f335e7d4d9 [rhir] Patch: ordering of overlapping input dependencies does not matter
--- 

Patch and simplify logic around merging overlapping reactive dependencies. 

Added `reduce-reactive-unconditional-deps` test fixtures, which tries to cover 
all cases of merging unconditional dependencies (to a minimal dependencies set). 
Please let me know if I missed any
2023-02-27 13:38:02 -05:00
Lauren Tan
8bc8d67aee [be] Fix unused vars in BuildHIR 2023-02-27 17:12:41 -05:00
Lauren Tan
e4e1af7236 [eslint] Ignore _ prefixed unused variables 2023-02-27 17:12:38 -05:00
mofeiZ
60137a8b64 [rhir][cleanup] remove DeclKind (replaced by pruneNonReactiveDependencies)
DeclKind is no longer read / needed due to pruneNonReactiveDependencies pass.
2023-02-27 11:04:38 -05:00
Joe Savona
1dfaf8a94b Lower all operands to temporaries
This PR changes BuildHIR to lower all operands to temporaries. Example: 

```javascript 

// Input 

a + b; 

// Previous Lowering 

Const t0 = BinaryOperation Place(a) "+" Place(b) 

// New Lowering 

Const t0 = Place(a); 

Const t1 = Place(b); 

BinaryOperation Place(t0) "+" Place(t1) 

``` 

This is necessary to ensure we're always referring to the correct version of a 
variable, even in the case of reassignment mid-expression. For example, we 
previously evaluated `let x=1; x + (x = 2) + x` incorrectly to 6 because we 
lowered the `x = 2` prior to the binary operators. We now lowers each instance 
of x to a temporary, ensuring they refer to the correct SSA version of the 
variable, and produce the correct result (5). 

Note that with this change, the _only_ place a variable can appear as an 
operator is when the InstructionValue is a raw identifier. This was already the 
case for globals (as of the LoadGlobal instruction). All other instruction value 
variants will only ever receive temporaries as arguments. 

This necessitated a few changes to our inference: 

* The logic to extend the range of phi operands (if the phi is mutated) was 
previously in LeaveSSA, but that was actually too late. The introduction of 
lowering to temporaries help discover failing cases, which I fixed earlier in 
the stack by moving the logic to extend the range of phi operands into the 
InferMutableRanges fixpoint loop. 

* PropagateScopeDependencies now has to track variable reassignments in addition 
to tracking property accesses 

* AnalyzeFunctions now has to track variable reassignments in addition to 
tracking property accesses 

* InferReactiveIdentifiers now needs a fixpoint iteration, because identifiers 
don't directly appear together in the same instruction anymore (such that we can 
directly propagate the reactivity between them). Instead, we'll first see that 
the temporaries are reactive, and have to propagate that back to the identifiers 
the temporaries were loaded from. 

Overall while this does introduce a bit more complexity, it also makes the 
compiler more robust. As with the phi example illustrates, there are legitimate 
inputs that can create similar indirections to that introduced by lowering 
identifiers to temporaries. 

Note that there’s a theme to the changes here: several analysis passes need to 
map an operand back to its identifier value. Ideally our HIR structure would 
directly support looking up the value for a temporary. For example, if operands 
were references to eg the index of the instruction that produced them. Because 
we don’t have such a representation yet (it would fall out naturally if we were 
writing in Rust), we have to do some bookkeeping. The key takeaway here is that 
this bookkeeping is incidental complexity given our current representation, not 
fundamental complexity of the algorithm.
2023-02-22 15:53:23 -08:00
Joe Savona
256071460d [destructuring] Cleanup InstructionValue type definition 2023-02-17 15:30:42 -08:00
Joe Savona
1d0aa64b24 Make it easier to debug lambdas 2023-02-22 14:07:33 -08:00
Joe Savona
8da443b673 Print HIR of function expressions for debugging
Example output: 

``` 

bb0 (block): 

[1] Const mutate $9$ = read a$8 

[2] Const store x$10:TObject$ = Object { a: read $9 } 

[3] Const mutate $11:TObject$ = capture x$10:TObject 

[4] Const store $12[4:6]:TFunction$ = Function @deps[read $11:TObject]: 

bb0 (block): 

[1] Const mutate q$7$ = capture x$6 

[2] Const mutate $8$ = capture q$7 

[3] Const mutate $9$ = PropertyLoad read $8.b 

[4] Const store $10[4:6]:TFunction$ = Function @deps[read $9]: 

bb0 (block): 

[1] Const mutate $7:TPrimitive$ = 1 

[2] Const mutate $8[2:4]$ = capture q$6[0:4] 

[3] Const store $9$ = PropertyStore mutate $8[2:4].b = read $7:TPrimitive 

[4] Return 

[5] Const mutate $11$ = Call mutate $10[4:6]:TFunction() 

[6] Return 

[5] Const mutate $13$ = Call mutate $12[4:6]:TFunction() 

[6] Const mutate $14:TObject$ = capture x$10:TObject 

[7] Return freeze $14:TObject 

```
2023-02-22 13:03:00 -08:00
Joe Savona
bae2cb5f89 Remove now-unnecessary mutable range extension in LeaveSSA
This is no longer necessary now that we extend phi operands' ranges in 
InferMutableRanges
2023-02-21 15:29:30 -08:00
Joe Savona
9a25b5a12a Fix previous examples, alias across phis
Within the InferMutableRanges fixpoint iteration, we need to alias phi operands 
with the phi id if the phi id is later mutated.
2023-02-21 15:29:30 -08:00
Joe Savona
79eb250187 Bug repro for unobserved aliased mutation w phi
I found this while working to ensure that we always lower all operands to 
temporaries. This works: 

```javascript 

// the whole computation of x is memoized in one block, bc of the mutation after 
the phi 

let x; 

if (cond) { 

x = someObj(); 

} else { 

x = someObj(); 

} 

mutate(x); 

``` 

However, if you alias either of the operands, we lose the mutation: 

```javascript 

let x; 

if (cond) { 

const y = someObj(); // OOPS this gets independently memoized 

x = y; 

} else { 

x = someObj(); 

} 

mutate(x); 

``` 

The core issue is that InferMutableRanges does not take into account mutation of 
phis. ~~My first thought is that we need an additional, outer fixpoint iteration 
loop to flow mutation back "up" to phi operands~~ 

edit: there was a much easier fix, we need to alias phi operands and phi id 
within the existing fixpoint iteration. See follow-up PR which fixes.
2023-02-21 08:29:37 -08:00
Joe Savona
fcdcd6038f Constant propagation converts computed access to static property where possible 2023-02-17 12:32:30 -08:00
Joe Savona
2b3902f043 Bailout on assigning to module-scope variables 2023-02-17 12:18:17 -08:00
Joe Savona
a2ebea4cde Bailout on assigning to globals 2023-02-17 10:02:56 -08:00
Joe Savona
ac9212f24e Make globals configurable; populate a reasonable default list
This is a precursor to validating that all identifiers are defined - we need to 
know about gobals and module declarations, so this PR adds the ability to 
configure a Set<string> of defined globals. The default list is inspired by the 
globals that prepack defines, which just comes from the spec definition.
2023-02-17 09:47:59 -08:00
Joe Savona
6b654f306c Construct LoadGlobal; consume hook info from types
Updates BuildHIR to produce LoadGlobal instructions for references to globals. 
Note that this breaks our previous strategy of finding hook calls: that relied 
on looking at the callee of a CallExpression and checking its name, which relied 
on the callee not being lowered to a temporary. By lowering the name (eg 
`useState`) to a temporary first, we now no longer see the name at the callsite. 

Thankfully @gsathya solved this for us already by teaching type inference about 
hooks, and more generally implementing type inference. I updated this so that we 
infer the type of a LoadGlobal if the name is a hook: the type inference picks 
this up and propagates the type forward correctly. So now, all places that 
needed to check for a hook can just look at the type and everything works. 

This is much more robust than before - you can now reassign a hook to a local 
variable and we'll still detect that when you call it, you're calling a hook.
2023-02-17 09:47:58 -08: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
0c72eed413 Pass environment options through babel plugin
Updates the babel plugin so that environment options — including custom hook 
definitions — can be passed in through the plugin: 

* Renames `CompilerFlags` => `PluginOptions` since they are specific to the 
babel plugin, and are no longer just flags. 

* Moves the definition of `useFreeze()` out of the builtin hook list and instead 
passes it when our unit tests configure the plugin.
2023-02-16 14:24:15 -08:00
Joe Savona
8810076bc6 Lookup hook declarations on environment
Changes from calling the global parseHookCall() function to looking up the hook 
declaration on the environment.
2023-02-16 14:06:17 -08:00
Joe Savona
cee25928bf Rename Environment => State for infer reference effects for clarity
InferReferenceEffects needs to be able to pass around the function's 
Environment, but there is already a local class with that name. It's confusing 
to have two "environment" concepts in one file, so this PR renames that local 
class to the more appropriate `InferenceState` and renames local variables and 
updates comments accordingly.
2023-02-16 14:06:16 -08:00
Joe Savona
eece73262e Start of making globals and hook declarations configurable
Some refactoring to allow the environment options to be passed in from the 
outside.
2023-02-16 14:06:15 -08:00
Joe Savona
158c9e4fc1 [be] Extract Environment to a separate file
Precursor to making the environment configurable
2023-02-16 10:47:27 -08:00
Joe Savona
530231712f [be] Extract hooks helpers to separate file
This is a precursor to allowing the set of custom hooks (and their behavior) to 
be configurable.
2023-02-16 10:38:06 -08:00
Joe Savona
b05fc6a945 Repro of tagged template as hook arg 2023-02-16 10:21:53 -08:00
Joe Savona
26e9e14d85 Fix typo 2023-02-16 09:04:45 -08:00
Joe Savona
849198da9d Pass cache size to useMemoCache() 2023-02-16 08:57:42 -08:00
Joe Savona
93775440a6 Minimal repros of product patterns 2023-02-16 08:57:38 -08:00
Joe Savona
fe5ca23384 Fix PrintHIR for TaggedTemplateExpression 2023-02-16 08:57:34 -08:00
Joe Savona
e45f69154f Fix dropped temporary in value block 2023-02-16 08:57:30 -08:00
Joe Savona
e504e1212a [be] Import from index instead of direct path 2023-02-16 08:57:26 -08:00
mofeiZ
55ca5fc26c [playground] add flow parsing
I don't think there is a monaco config to parse flow (for IDE integration), but 
now we should be able to parse flow for the Forget compiler. 

Since typescript and flow has different parsing rules, playground will only use 
flow parser if `// @flow` is at the top of the file. 

Test: 
[link](https://react-forget-playground-j7nd7j8cv-fbopensource.vercel.app/#eyJzb3VyY2UiOiIvLyBAZmxvd1xuZnVuY3Rpb24gQ29tcG9uZW50KHByb3BzKSB7XG4gIGxldCB4ID0gKHByb3BzOiBudW1iZXIpO1xuICByZXR1cm4geDtcbn1cbiJ9)
2023-02-16 11:54:27 -05:00
Lauren Tan
0d0d3a4038 Ensure stable variable names after leaving SSA
With this PR we now no longer emit copy instructions during LeaveSSA, and 
restore the original identifier name.
2023-02-15 15:22:03 -05:00
Lauren Tan
d1fd0449ee [be] Linter fixes for LeaveSSA 2023-02-15 15:22:01 -05:00
Sathya Gunasekaran
06e5681198 [hir] Use receiver of CallExpression as dep 2023-02-15 15:03:29 +00:00
Joe Savona
008cebd633 [facepalm] Fix bug w missing deps
While reviewing @poteto's PR I noticed that there were some cases of missing 
dependencies. I tracked it down to a bug I introduced 
[here](5b827eb85c (r100646304)). 
Decl.id is meant to be the id of the instruction that declares the variable. We 
then test to see if a dependency is later than that. If the Decl.id is 
incorrectly too high, then we miss some dependencies thinking they aren't 
defined yet.
2023-02-14 16:22:58 -08:00
Joe Savona
b7b0118afa InferReactiveIdentifiers handles Capture effect 2023-02-14 15:48:52 -08:00
Joe Savona
9493e13709 [be] Use switch for exhaustiveness 2023-02-14 15:48:49 -08:00
Joe Savona
eda7788c48 InferReactiveIdentifiers uses IdentifierId
This is to help prep for @poteto's renaming PR. To make that PR work we 
generally need to use IdentifierId to distinguish "the same identifier" rather 
than Identifier object identity.
2023-02-14 15:44:46 -08:00
Joe Savona
490c204dcf Move logic for making reactive scope decls all reactive
InferReactiveIdentifiers has some extra logic to find identifiers declared in 
the same scope, and promote non-reactive identifiers to reactive if they appear 
inside a reactive scope (reactive scope == scope with one or more (reactive) 
dependencies). Even though the identifier alone might not be technically 
reactive (have no reactive inputs), it can get re-recreated if the scope 
re-evaluates. 

We can now do this during PruneNonReactiveDependencies as we exit out of each 
scope.
2023-02-14 15:27:52 -08:00
Joe Savona
b1ee356805 InferReactiveIdentifiers: fixpoint iteration is now unnecessary
I removed fixpoint iteration and all tests pass, which matches my intuition that 
it's really that we need strictly two passes. Removing to simplify and for 
performance (avoid unnecessary extra visits of the ast)
2023-02-14 15:24:01 -08:00
Joe Savona
2b47cac5fd Create a separate pass to prune non-reactive dependencies
The fact that InferReactiveIdentifiers is integrated directly into 
PropagateScopeDependencies has made the latter pretty tricky to debug at times. 
If a dependency is missing, we have to introspect and figure out if that's 
because it was somehow inferred as non-reactive. This PR creates a new 
PruneNonReactiveDependencies pass to separate out these phases.
2023-02-14 14:09:53 -08:00
Sathya Gunasekaran
fb8f293c32 [hir] Add a DropMemoCall pass
This drops the memo hook calls from the IR
2023-02-14 23:07:19 +00:00
Sathya Gunasekaran
e0562bbd51 [typer] Type hook callee as Hook type 2023-02-14 23:07:18 +00:00
Sathya Gunasekaran
d0f1a98144 [typer] Introduce a Hook type 2023-02-14 23:07:17 +00:00
Sathya Gunasekaran
1dfd51cdb0 [hir] Add name field to Hook 2023-02-14 23:07:17 +00:00
Sathya Gunasekaran
a65bf197d7 [hir] Add Memo hooks 2023-02-14 23:07:16 +00:00
Sathya Gunasekaran
12ca0ba61b [hir] Simplify HookKind 2023-02-14 23:07:16 +00:00
Sathya Gunasekaran
81b23f9242 [typer][be] Add helper for checking type.kind 2023-02-14 19:10:37 +00:00
Joe Savona
5673588be4 [be] Tidy up some mutableRange logic 2023-02-13 15:26:07 -08:00
Joe Savona
225fe0835c Optimize DCE to visit CFG only once when there are no loops
Optimizes dead code elimination. Currently it keeps iterating the control flow 
graph until no new usages have been discovered, which accounts for usages across 
loops. However, when there are no loops it's sufficient to iterate the CFG 
exactly once.
2023-02-13 13:59:44 -08:00
Joe Savona
6b67f597a6 Capturing a frozen value is a Read
If a value is known to be frozen (or potentially frozen), then it doesn't need 
to be considered 'captured' since no mutation can occur via aliasing.
2023-02-14 09:33:23 -08:00
Lauren Tan
759a7e027b Fix incorrectly recording declarations in reassignments in
PropagateScopeDependencies 

This was incorrectly added in #1190, oops!
2023-02-13 16:52:08 -05: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
Sathya Gunasekaran
31d8aad40e [hir] Check if defined before looking up ValueKind
This handles globals now without throwing
2023-02-13 16:32:50 +00:00
Sathya Gunasekaran
7277897405 [test] Add test for broken lambda capturing
SSA redefines context refs which breaks our inference. The correct fix here is 
to not overwrite context refs in EnterSSA.
2023-02-10 17:59:59 +00:00
Sathya Gunasekaran
b56f413e99 [hir] Mark ArrayExpression capturing a context ref as a context ref
A context ref capture is transitive.
2023-02-10 17:59:58 +00:00
Sathya Gunasekaran
343ebb47bd [hir] Mark ObjectExpression capturing a context ref as a context ref
A context ref capture is transitive.
2023-02-10 17:59:57 +00:00
Sathya Gunasekaran
fa57551dd5 [hir] Introduce ValueKind.Context
This tracks whether a value is a context ref or generated from a context ref. 

This lets us track mutations to context refs and treat it separately as we want 
this to be more conservative than our existing inference. 

ValueKind.Context is exactly like ValueKind.Mutable but is more conservative.
2023-02-10 17:59:56 +00:00
Sathya Gunasekaran
2a9fab001a [be] Fix test
'e' does not exist, use 'd' instead
2023-02-10 17:26:25 +00:00
Sathya Gunasekaran
199e284eda [hir] Mark Identifiers aliased as Effect.Capture 2023-02-10 17:26:21 +00:00
Sathya Gunasekaran
40a85432ed [hir] Move inference logic to Env.reference 2023-02-10 17:26:17 +00:00
Joe Savona
6877ac6500 Fix update assignment on computed memberexpression 2023-02-10 08:59:15 -08:00
Joe Savona
5746d5b07f Temporary workaround for emitting temporaries multiple times
This is a temporary fix for the issue we discovered on our first integration, 
where destructuring of a function return value is emitting the function call 
multiple times: 

```javascript 

// Input 

const [x, setX] = useState(null); 

// Output 

const x = useState(null)[0]; 

const setX = useState(null)[1]; 

``` 

The reason this happens is that we lower `useState(null)` to a temporary, and 
then generate a ComputedLoad for each of x and setX. Codegen doesn't emit 
temporaries eagerly - it assumes they are going to be used exactly once and it 
re-emits the value each time the temporary is used. Hence why the 
`useState(null)` part gets duplicated in the output. 

Right now destructuring is the only place i'm aware of where we reuse 
temporaries this way. And we do want to change codegen to preserve destructuring 
in the output to correctly handle array patterns. However, that's a more 
involved change. For now, this PR is a stopgap. During the pass where we promote 
temporaries used in scopes to named variables, we now check to see if those 
temporaries are used multiple times and promote them. 

The above example would then generate something like 

```javascript 

const t0 = useState(null); 

const x = t0[0]; 

const setX = t0[1]; 

``` 

This is still incorrect (it assumes t0 is an array), but it's more likely to 
work in practice. I'll revert this change once we correctly handle 
destructuring.
2023-02-09 17:22:54 -08:00
Joe Savona
dd228c9ed0 [be] non-blocking ESLint config, fixes
Configures typescript-eslint for the project with an initial configuration that 
starts with their recommended rules, and adds/disables a few (generally either 
disabling warnings or promoting them to errors). The new `yarn lint` command is 
not hooked up to CI yet, so for now this is something we can opt-in to running 
locally. If you have some free time, help get us down to zero errors! 

My general philosophy for linting, which I propose we follow, is that lints 
should be very high-signal: 

* Error, don't warn. If it's worth mentioning it's worth fixing. 

* Enable rules that consistently identify real problems. If we frequently would 
have to disable the rule due to false positives, it isn't high-signal. 

* Enable rules that help improve consistent style (to avoid code review about 
style rather than substance).
2023-02-09 11:58:06 -08:00
Joe Savona
8eb5feb847 [be] Cleanup BuildHIR 2023-02-09 11:41:04 -08:00
Joe Savona
41f52b73c2 Change reference effects for hooks
I realized we hadn't updated InferReferenceEffects to match our latest thinking 
on hooks. Specifically, we will default to assuming that hooks can mutate their 
arguments and return mutable values — this works with our model since we don't 
treat hooks specially for reactive scope construction. Ie, first we figure out 
what variables construct together, then we create scopes, then we prune scopes 
that contain hooks. So changing the reference effects for hooks "just works". 

Note that it is helpful for our unit tests to have an example hook that we know 
_does_ freeze its input and return a frozen value, so i've temporarily added 
`useFreeze()` to the list of defined hooks. That is meant as a stopgap: the 
right solution is to allow some way to tell the compiler about specific custom 
hooks and their semantics.
2023-02-08 14:24:14 -08:00
Joe Savona
7f60f32118 Dont memoize scopes with hook calls 2023-02-08 14:07:01 -08:00
Joe Savona
c7e3bc4d41 Visitor extension for transforming ReactiveFunction
Adds a subclass of ReactiveFunctionVisitor, ReactiveFunctionTransform, which 
makes it easier to write passes that change the shape of a ReactiveFunction. The 
two use-cases converted so far are both flattening away certain categories of 
reactive scopes — this will make it easier to add a similar pass to prune scopes 
that contain hook calls.
2023-02-08 14:07:00 -08:00
Sathya Gunasekaran
394666118d [hir] Lower function expressions into HIR 2023-02-08 17:45:33 +00:00
Sathya Gunasekaran
662b8d2ff3 [hir] Add lvalue effect for PropertyLoad and ComputedLoad 2023-02-08 16:01:36 +00: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
e7f4eae619 Reset canonicalId mutable range if not mutated after creation 2023-02-08 10:26:31 -05:00
Lauren Tan
6f661f3b79 Extend mutable ranges if a phi is mutated after creation 2023-02-08 10:26:29 -05:00
Lauren Tan
d43a5014f4 Rename ReactiveScope.outputs to ReactiveScope.declarations 2023-02-08 10:26:27 -05:00
Lauren Tan
1809ffd06d Add new test cases for SSA
Add these as a separate commit so we can see how later PRs in the stack change 
them
2023-02-08 10:26:26 -05:00
Lauren Tan
5888cbdd9b Don't throw on invalid compiler flags 2023-02-08 10:26:24 -05:00
Sathya Gunasekaran
24b28ae257 [λ] Remove broken support for identifiers defined after use
This also adds some book keeping to throw if ever define identifiers after use 
to make sure we don't generate incorrect code.
2023-02-07 18:25:55 +00:00
Sathya Gunasekaran
d1dbb00790 [hir] Throw if Effect.Store is not handled 2023-02-07 16:59:37 +00:00
Sathya Gunasekaran
3df1ae66c0 [λ] Use Effect.Capture for mutating deps
Leverage Effect.Capture to differentiate between mutating and non mutating deps.
2023-02-07 16:59:36 +00:00
Sathya Gunasekaran
85c92bcfbe [aliasing] Introduce Effect.Capture
Effect.Capture is very similar to Effect.Read, but the only difference is that 
this reference is stored somewhere via a Effect.Store. 

Previously, any operand associated with a Effect.Store in the same instruction 
would get aliased -- so there was no need to explicitly differentiate between a 
"normal read" and "read that gets stored". 

This difference is now explicit with FunctionExpression where every dependency 
is "read" but only a few are "captured" for store (and mutation). In a follow up 
PR, the mutating deps will have an Effect.Capture to differentiate from the 
other non mutating deps (Effect.Read).
2023-02-07 16:59:35 +00:00
Sathya Gunasekaran
4e07b8a869 [hir] Do not alias Identifier as Store
Identifiers are never stored (Effect.Store), they are always mutated 
(Effect.Mutate) and aliased in the InferAlias pass.
2023-02-07 16:59:35 +00:00
Lauren Tan
195f473cb4 s/useMemoCache/unstable_useMemoCache
uMC is still prefixed with unstable in React 
(653dd2348c/packages/react/src/React.js (L143))
2023-02-07 11:40:06 -05:00
Sathya Gunasekaran
1f7cd2ff21 [hir] Remove unnecessary null check 2023-02-07 15:08:38 +00:00
Lauren Tan
9cac0b0068 Ensure Forget runs first if specified in a Babel config
This commit adds a new Program visitor to our Babel plugin which then calls our 
FunctionDeclaration visitor. Babel does some "smart" merging of plugin passes so 
so even if plugin A is inserted prior to plugin B, if A does not have a Program 
visitor and B does, B will run first. 

Note that we also can't use Forget inside of a Babel preset as plugins run 
_before_ presets (https://babeljs.io/docs/en/plugins/#plugin-ordering).
2023-02-07 11:11:58 -05:00
Lauren Tan
e159844b0d Add generated code to errors thrown by BabelPlugin
If we've generated an erroneous AST (eg duplicate declarations), this lets us 
observe what the code looked like to aid debugging
2023-02-06 19:03:54 -05:00
Lauren Tan
0d2653dbd4 Use parent of AssigmentExpressions when bailing out
For some reason the expression itself wont print the codeframe, but its parent 
will, so print that instead
2023-02-06 17:32:30 -05:00
Joe Savona
382226ab31 Add missing return statements to fixtures 2023-02-06 14:09:15 -08:00
Sathya Gunasekaran
500cfddc3a [λ] Remove unused params in FunctionExpression 2023-02-06 14:03:13 +00:00
Lauren Tan
cd108cd430 Add enableOnlyOnUseForgetDirective flag
When this flag is enabled, Forget will only compile function declarations opted 
in via the `'use forget'` directive. By default this is false. 

Tested internally, see related diffs
2023-02-06 16:47:35 -05:00
Joe Savona
b009626431 Improve DCE to handle unused loop phis
Improves DCE, using fixpoint iteration to detect values that are updated across 
loops but otherwise never read. There is still some further optimization we can 
do (the dce-loop case could optimize out `y`), but this seems like plenty for 
now. 

Actually, we probably have to add some return statements to our fixtures before 
landing this, because otherwise most of the code goes away.
2023-02-03 15:46:00 -08:00
Joe Savona
19e21b73a7 Initial pass at dead code elimination
This is a first pass at DCE without having read any literate on the subject, so 
lemme know if there's a better approach. That said the algorithm is: 

* Keep a `Set<Identifier>` of identifiers that are used (and whose constructing 
logic cannot be removed). 

* Do a first RPO iteration of all block's phis. Any phi operand that 
participates in a loop is preemptively marked as "used" even if it isn't 
strictly used somewhere. This step is necessary bc these operands may otherwise 
not be used. 

* Do a second post-order iteration of all blocks, including iterating first 
their terminals, then reverse iteration of instructions, then their phis. Mark 
the operands of each as used as we encounter them, and prune instructions whose 
lvalue is never used. 

For now I was conservative about which types of instructions can be pruned. For 
example, call instructions are never pruned, even if the result of the call is 
never used. 

However one catch is that we currently prune instructions that cause values to 
become frozen. We had planned to add runtime calls (in dev) to freeze values for 
runtime enforcement, and if we want to do that we can always add these 
instructions back (or replace them with explicit freeze calls). 

There are a few potential next steps but we should discuss whether they're worth 
it: 

* Use fixpoint iteration to find exactly which operands are actually used. This 
would allow us to to prune cases such as `let x = 0; while (...) { x += 1 }` eg 
where there's a phi but the result is never used. Such cases should be rare in 
practice though. 

* Eliminate more types of instructions, eg eliminate function calls that don't 
have any mutable arguments.
2023-02-03 14:55:49 -08:00
Joe Savona
812e0ce701 [valueblocks] Join blocks instead of replacing operands (fix edge case)
There's a bug in the HIR->ReactiveFunction conversion for certain categories of 
compound value blocks where we replace operands (which must be a Place) with a 
ReactiveValue. This approach worked in practice for lots of cases so I thought a 
type coercion was safe, but then I found a case where this assumption breaks 
(see new test). 

The updated logic fixes the bug and is simpler. When a value block gets split up 
(because there was a nested value block), instead of replacing the earlier value 
in the later instructions, we append the instructions together. This can result 
in some extra nesting (which if we wanted we could flatten away) but ensures 
that we maintain type-safety.
2023-02-03 14:16:17 -08:00
Joe Savona
cc33497c44 [valueblocks] Add InstrId to SeqExperession value
A SequenceExpression currently doesn't store the InstructionId that produced its 
final `.value`. This PR adds that instruction id, which is then used in the next 
PR as we compose SequenceExpressions.
2023-02-03 14:16:17 -08:00
Joe Savona
29a95d2dea [be] Use generator in InferTypes, extract helper
Just small things I noticed when looking at InferTypes. I was thinking about how 
we'd adjust this pass to account for hooks, i'll probably pause that for now but 
putting this up in case you like the changes. If not no big deal!
2023-02-03 12:59:13 -08:00
Mofei Zhang
e30a9e9258 [hir syntax] Handle TemplateLiteral syntax 2023-02-03 15:44:37 -05:00
Mofei Zhang
34595b84c8 [syntax] Followup: clean up InferReactiveIdentifiers 2023-02-03 15:44:36 -05:00
Sathya Gunasekaran
4b55399fb8 [λ] Use context identifiers in inner functions
This threads through context identifiers to inner functions and also uses them 
as dependencies if they are mutated in the inner function.
2023-02-03 17:42:07 +00: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
Joe Savona
34a7b58334 Restrict switch case test values until they use value blocks
We currently lower switch case test values within the wrong scope: the test 
value really should be a value block rather than a `Place`. Until then, this PR 
adds a bailout for complex test values: we allow primitives and identifiers 
which should cover most real-world use-cases.
2023-02-02 09:09:25 -08:00
Sathya Gunasekaran
dc65e7c5a9 [aliasing] Make InferAliasForStores use the visitor infra
No need for InferAliasForStores to know about the semantics of each instruction 
anymore. It's just a simple pass that iterates over every operand and lvalue. 

The FunctionExpression is special cased because it's slightly different but I 
have a follow up that removes this special casing.
2023-02-02 17:41:35 +00:00
Sathya Gunasekaran
40f304392e [aliasing] Alias object loads to it's lvalue as well
This doesn't change codegen as the lvalue is unused but it lets us make this 
pass be semantically the same across all instructions -- "alias lvalue and 
operands of an instr".
2023-02-02 17:41:31 +00:00
Sathya Gunasekaran
018899d778 [aliasing] Make aliasing account for MutableRange.end being exclusive 2023-02-02 17:41:27 +00:00
Sathya Gunasekaran
98f67c407b [aliasing] Alias ComputedLoad operands 2023-02-02 15:45:16 +00:00
Sathya Gunasekaran
5e1929c002 [aliasing] Alias ComputedStore operands 2023-02-02 15:45:12 +00:00
Joe Savona
de929fcc40 Partial support for update expressions
Adds limited support for UpdateExpressions (`x++`). We now support the postfix 
form (`x++` ok, `++x` is a todo) and only when the argument is an identifier. We 
can relax these restrictions with more work, but this PR should be sufficient 
for the examples we've seen so far.
2023-02-02 07:53:24 -08:00
Sathya Gunasekaran
7a92b2fe5d [be] Cleanup logic that matches mutatedId and mutatedDep 2023-02-02 14:05:37 +00:00
Sathya Gunasekaran
12274f30b9 [be] Inline buildMutatedDeps 2023-02-02 14:05:36 +00:00
Sathya Gunasekaran
aa2403e068 [λ] Run analyseFunctions on inner func exprs
Previously we would skip out on calling analyseFunctions on inner function  
exprs so we missed out on catching aliased mutation in a different scope.
2023-02-02 14:05:35 +00:00
Sathya Gunasekaran
0522d5233b [λ] Store and define captured bindings as context
This lets us treat global references differently from captured references.
2023-02-02 14:05:34 +00: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
Lauren Tan
1baf22a421 [playground] Fix error wrapping for realsies
CSS is hard (jk i'm just terrible at it now)
2023-02-01 14:14:45 -05:00
Lauren Tan
8c9d1b85be Failing test from - - 2023-02-01 13:46:20 -05:00
Lauren Tan
f53318d0ba [playground] Fix errors not wrapping 2023-02-01 13:17:04 -05:00
Joe Savona
c2dda028fd Improve debugging for - errors 2023-02-01 09:42:46 -08: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
df07522b89 [be] Move Result into Utils/
The `Utils/` folder is meant to be a catch-all for code that doesn't have an 
obvious home in other directories.
2023-01-31 16:14:23 -08:00
Lauren Tan
e8650951ea [playground] Add diff view toggle
Quick hack to show a diff view comparing previous pass to current. Default is 
the output view 

![Screenshot 2023-01-31 at 5 37 09 
PM](https://user-images.githubusercontent.com/1390709/215899637-672d578a-6fb9-4580-ae87-ae0be0878a59.png) 

![Screenshot 2023-01-31 at 5 37 12 
PM](https://user-images.githubusercontent.com/1390709/215899640-7aff3c06-fc67-476a-82f9-59d65b4f9ac4.png)
2023-01-31 17:37:22 -05:00
mofeiZ
5fbfce6d3b [test case] Failing assignment inside of nested if statement
``` 

function useBar(props) { 

let z; 

if (props.a) { 

if (props.b) { 

z = baz(); 

} 

} 

return z; 

} 

``` 

Currently fails with 

``` 

InvariantViolation: A phi cannot have two operands initialized before its 
declaration 

```
2023-01-31 16:53:06 -05: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
50f300bc92 [valueblocks] Update some passes to use the new visitor
Updates some passes from ReactiveScopes/ to use the visitor added in the 
previous PR. The +124/-354 line count on this diff tells the story  — the new 
visitor avoids a lot of boilerplate and helps focus on the logic not the 
traversal. 

Note that there are a few passes which transform the function such as 
adding/removing scopes. A follow-up will extend the visitor to support that and 
convert the remaining passes.
2023-01-31 13:39:38 -08:00
Joe Savona
400bf4588c [valueblocks] More general visitor for ReactiveFunctions
This adds a truly general-purpose visitor pattern for ReactiveFunction, modeled 
on what's worked well in Relay Compiler. All types of node that have children 
get a visitFoo/traverseFoo pair of functions. By default the visitFoo() function 
delegates to the traverseFoo() function, but the visit variant is meant to be 
overridden and can delegate to the traverseFoo() variant — this gives you 
precise control so that you can save/restore state before/after traversing 
children. 

Probably the only interesting thing is that visitLValue() does not call 
visitPlace() by default though it technically could. So far that is making sense 
in the passes i converted. 

Note that once all passes are updated to use this, i'll delete the other visitor 
helpers for ReactiveFunction.
2023-01-31 13:39:37 -08:00
Joe Savona
149353badb [valueblocks] Support sequence expressions (comma operator) 2023-01-31 13:39:37 -08:00
Joe Savona
d6505572cb [valueblocks] Disallow AssignmentExpression in value blocks (temporarily)
There's a bug with assignment expression in normal value blocks due to LeaveSSA. 
Until that's resolved i'm temporarily distinguishing "loop" blocks and "value" 
blocks, and disallowing assignment expressions in value blocks specifically.
2023-01-31 13:39:36 -08:00
Joe Savona
8c13c91f27 [valueblocks] ternary tests showing assignment bug
A ternary that reassigns the same variable in both branches causes some 
weirdness with LeaveSSA, this PR just adds the failing case.
2023-01-31 13:39:36 -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
dc7ac6fea4 [valueblocks] Scaffolding for ternary terminals
Core representation for terminals, without the lowering and conversion logic.
2023-01-31 13:39:34 -08:00
Joe Savona
aca0e27012 [valueblocks] split working test and aliasing bug case 2023-01-31 13:39:34 -08:00
Joe Savona
096482e5cb print mutable ranges on every place
useful for debugging
2023-01-31 13:39:33 -08:00
Joe Savona
95642c4421 [valueblocks] Printing for LogicalValue/SequenceValue 2023-01-31 13:39:33 -08:00
Joe Savona
86ffcde8ed [valueblocks] Handle compound RHS for logicals
The previous PR handled the case where the LHS of a logical was compound, but 
didn't handle compound RHS values. This is fixed now.
2023-01-31 13:39:32 -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
Joe Savona
bb8ae86c24 [valueblocks] ReactiveFunction repr for logical expressions
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.
2023-01-31 13:39:31 -08:00
Joe Savona
c6a066af83 [valueblocks] Lower logical expressions into logical terminals
Changes the lowering for LogicalExpression to use the new 'logical' terminal. 
Whereas before we tried to more directly model the semantics of `??` by 
generating an `if (<lhs> != null)`, we now generate a branch terminal that looks 
at the lhs. 

The HIR -> ReactiveFunction construction for logical and branch terminals are 
placeholders while I refactor the ReactiveFunction representation to support 
value blocks.
2023-01-31 13:39:31 -08:00
Joe Savona
b1548100d1 [be] Clarify type of value block final value before change
Makes the type of the final value of a ValueBlock more precise, prior to 
changing it
2023-01-31 13:39:30 -08:00
Lauren Tan
e48b615598 [playground] Fix header when horizontal scrolling
Fixes a minor annoyance 

Before 

![Screenshot 2023-01-31 at 10 39 06 
AM](https://user-images.githubusercontent.com/1390709/215806108-f3b084d3-c693-404b-a9b3-785d0f15c332.png) 

After 

![Screenshot 2023-01-31 at 10 33 15 
AM](https://user-images.githubusercontent.com/1390709/215804818-c94a5905-e278-4380-af44-6ab34ac95ff3.png)
2023-01-31 10:36:31 -05:00
Lauren Tan
3b4f41b58d [ez] Make dev mode more obvious in playground
It's easy to confuse your local browser tab with main, this modifies a 

few colors and labels to make it more obvious 

![Screenshot 2023-01-30 at 5 05 18 
PM](https://user-images.githubusercontent.com/1390709/215606089-78e5c874-263e-46e9-a99d-3d863bdc4432.png)
2023-01-30 17:03:59 -05:00
Lauren Tan
213ed2d6d0 [ez] Wrap output cols to prevent nested h-scroll
![Screenshot 2023-01-30 at 1 03 46 
PM](https://user-images.githubusercontent.com/1390709/215558010-49d3ddf4-3dee-418e-9595-790e720bad22.png)
2023-01-30 13:04:39 -05:00
Sathya Gunasekaran
04fb507876 [hir] Remove broken support for ForOf, ForIn, DoWhile 2023-01-30 19:12:43 +00:00
Lauren Tan
019f069778 Fix not being able to scroll horizontally 2023-01-30 11:37:28 -05:00
Lauren Tan
f0d64064a9 Support partial compilation in playground
This adds support in the playground for best effort compilation: if any 

passes have errors, we'll try to render as many successful tabs leading 

up to the pass with an error. 

Also contains some styling updates 

![Screenshot 2023-01-27 at 2 18 06 
PM](https://user-images.githubusercontent.com/1390709/215222635-59b42555-f1f3-4fc0-b1cd-3959946df6da.png) 

![Screenshot 2023-01-27 at 2 18 54 
PM](https://user-images.githubusercontent.com/1390709/215222636-4c0275fd-a95b-4f0f-946a-258ad8b27722.png) 

![Screenshot 2023-01-27 at 5 03 02 
PM](https://user-images.githubusercontent.com/1390709/215222637-92a8e459-173a-41ae-be9f-0cc0d54479d0.png)
2023-01-30 10:36:35 -05:00
Lauren Tan
be4058057c Bailout TaggedTemplateExpressions instead of invariant 2023-01-30 10:36:34 -05:00
Lauren Tan
545e53c428 Use CompilerErrors to record errors
This moves the bailout recording mechanism into a separate CompilerErrors class 
instead of repurposing HIRBuilder. This is to allow other passes to also record 
errors instead of immediately throwing.
2023-01-30 10:29:14 -05:00
Sathya Gunasekaran
c803558b2a [hir] Parse object pattern params 2023-01-26 16:40:08 -05:00
Sathya Gunasekaran
41e8088e8c [hir] Try to resolve a dep into a property load only if it's a temporary. 2023-01-26 16:40:07 -05:00
Sathya Gunasekaran
978215eae6 [test] Add test that resolves property loads to expanded places incorrectly
Ideally this should happen only for temporary places containing property loads.
2023-01-26 13:38:55 -05:00
Lauren Tan
98cb9eff92 Fix playground tabs always showing the same HIR or reactive function
Previously we were storing a pointer to the HIR or ReactiveFunction 

prior to printing, so when we printed them it would always print the 

results of the last pass. This commit changes it so we print them to 

strings when iterating through the compiler pipeline so each snapshot is 

correctly preserved 

![Screenshot 2023-01-26 at 4 34 23 
PM](https://user-images.githubusercontent.com/1390709/214955701-67f4da1e-b12c-432a-ba47-1a29afd91312.png)
2023-01-26 16:33:56 -05:00
Lauren Tan
d44538ad96 Stop passing NodePaths to CompilerError
This was causing issues in various places where errors would be stringified. 
Because the inner detail objects would contain a NodePath with circular 
structures this would cause a JSON.stringify error in code outside of our 
control. This change makes it so we always print the codeframe from the NodePath 
and then passing the string.
2023-01-26 13:58:56 -05:00
Joe Savona
4ab8cc6ae9 [valueblocks] Use branch terminal for if/for test blocks
Updates `while` and `for` to use the new branch terminal as their test block 
terminal (IfTerminal is now only meant for if statements).
2023-01-25 17:05:23 -05:00
Joe Savona
f88fa006e7 [valueblocks] Scaffolding for branch terminal
Scaffolding for a new 'branch' terminal, which is the lower-level form of 'if' 
meant for use in value blocks. See design at 
https://fb.quip.com/b7N6AjkDcl2a#temp:C:bAO904b0cf06f154de4a7287af6d
2023-01-25 17:05:22 -05:00
Joe Savona
1014727d41 [valueblocks] Specify block type at creation
We need to know the kind of each block (regular or value). Rather than specify 
the kind when closing the block — when we've lost context about why the block 
was created — it's simpler and more accurate to specify the kind when 
creating/reserving the block.
2023-01-25 17:05:22 -05:00
Lauren Tan
840bb6fec2 Add colors to debug logging 2023-01-25 16:15:12 -05:00
Sathya Gunasekaran
e6b4988b1e [hir] Infer UnaryExpression as non allocating 2023-01-25 11:11:16 -05:00
Sathya Gunasekaran
9eb9071aed [typer] Add type inference for unary expr 2023-01-25 11:11:16 -05:00
mofeiZ
9bd67099d7 [prettier] detect changed files from relative dir
Previously, `yarn prettier` didn't write changed files since `glob` produced 
paths relative to cwd and `git diff --name-only` produced paths relative to git 
root directory. 

This changes `git diff --name-only` to `git diff --name-only --relative`
2023-01-25 15:16:39 -05:00
Sathya Gunasekaran
f83ff2feda [hir] Parse UnaryExpression 2023-01-25 11:11:16 -05:00
mofeiZ
b00963ffde [playground] Handle invariant violation errors
playground currently hard crashes when we hit an invariant violation 

<img width="1104" alt="image" 
src="https://user-images.githubusercontent.com/34200447/214615036-dbe307d8-132d-4186-a8ab-6fcaf69801d1.png"> 

[playground 
link](https://0xeac7-forget.vercel.app/#eyJzb3VyY2UiOiJcbmZ1bmN0aW9uIHVzZUJhcihwcm9wcykge1xuICBwcm9wcyA9IG51bGw7XG4gIGxldCB5LCB6O1xuXG4gIGlmIChwcm9wcy54KSB7XG4gICAgbGV0IHkgPSB1c2VGb28ocHJvcHMuYS5iKTtcbiAgICB3aGlsZSAoYmFyKCkpIHtcbiAgICAgIGlmIChwcm9wcy55KSB7XG4gICAgICAgIHogPSBiYXooKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICByZXR1cm4gW3ksIHpdXG59In0=) 

This should fix that
2023-01-25 11:23:09 -05:00
Lauren Tan
47da50dfe8 [ez] Fix typo 2023-01-25 10:20:53 -05:00
mofeiZ
62b7526929 [hir] Handle OptionalMemberExpression syntax
(Implemented as per discussions with @gsathya ) 

Handle OptionalMemberExpression by adding an 'optional' flag to `PropertyLoad` 
instruction, which is set during `BuildHIR` and read during 
`CodegenReactiveFunction`.
2023-01-25 10:16:21 -05:00
Sathya Gunasekaran
79d53f9117 [hir] Parse and codegen TaggedTemplateExpression
Basic support to parse relay fragments
2023-01-24 16:41:00 -05:00
Lauren Tan
cd45341920 yarn install 2023-01-24 14:05:14 -05:00
Lauren Tan
9d935db383 Render error markers in playground
Additionally with this commit we now support multiple top level 

functions 

![Screenshot 2023-01-24 at 12 01 34 
PM](https://user-images.githubusercontent.com/1390709/214359278-7160b612-094d-4498-918d-944d30d61366.png) 

![Screenshot 2023-01-24 at 12 01 21 
PM](https://user-images.githubusercontent.com/1390709/214359275-d776c416-badb-4989-8952-dec1b1f3f7ed.png)
2023-01-24 12:44:51 -05:00
Lauren Tan
109d7ba980 Refactor CompilerError
This commit repurposes CompilerError to represent an aggregate of error details 
accumulated during HIR lowering. It also fixes the playground to correctly 
render errors again.
2023-01-24 12:44:51 -05:00
Lauren Tan
3da8d52a0f Consistency pass for error messages in BuildHIR 2023-01-24 12:44:51 -05:00
Sathya Gunasekaran
8f169574c7 [test262] Set language as typescript 2023-01-24 11:02:54 -05:00
Sathya Gunasekaran
8041b0b0d5 [λ] Parse and codegen concise arrow expressions 2023-01-24 10:47:49 -05:00
Sathya Gunasekaran
9aed0fc6b1 [babel] Add flow support
Check if there's a pragma to toggle flow parsing
2023-01-24 10:21:25 -05:00
Sathya Gunasekaran
19dd29707b [λ] Fix mutatedDep collection in AnalyseFunctions
Make sure to check free variables as well for mutations. Not just object 
properties.
2023-01-23 15:46:35 -05:00
Sathya Gunasekaran
ee3a88f20f [λ] Parse and codegen arrow function exprs 2023-01-23 17:39:37 -05:00
mofeiZ
3ba90a7f9c Inline hasNode check (hack)
www currently on an older version of babel
2023-01-23 16:08:01 -05:00
mofeiZ
16169bec07 Redo patch prettier
reverts / redos #1055 

(accidentally merged changes from another PR in #1055)
2023-01-23 16:03:49 -05:00
mofeiZ
07cda62430 [prettier] Patch prettier to write modified TS files (#1055) 2023-01-23 15:57:11 -05:00
Sathya Gunasekaran
3927281fe4 [λ] Re run SSA on function expr
Functions can capture variables declared after the definition of the function. 
This re runs SSA to map the captured identifiers to the new SSA identifiers if 
available.
2023-01-23 13:20:33 -05:00
Sathya Gunasekaran
123d024af7 [λ] Add test for function expr passed to jsx
Function exprs become frozen when captured by jsx.
2023-01-20 19:44:32 +00:00
Sathya Gunasekaran
294b6c752d [plugin] Add default export to forget 2023-01-23 14:36:54 -05:00
Lauren Tan
8e08334a42 Specify sourceType "module" in hir-test and playground
This was preventing the playground and fixture tests from being able to compile 
an ExportDefaultDeclaration
2023-01-23 13:11:35 -05:00
Lauren Tan
b44f507c92 [test262] Strip codeframes from Forget errors so results are grouped
correctly
2023-01-20 11:47:11 -05:00
Lauren Tan
44beb3c023 Collect bailouts in BuildHIR::lowerAssignment 2023-01-20 11:47:11 -05:00
Lauren Tan
9ed6977c48 Collect bailouts in BuildHIR::lowerJsxElement 2023-01-20 11:47:10 -05:00
Lauren Tan
4b3b498e37 Collect bailouts in BuildHIR::lowerJsxElementName 2023-01-20 11:47:10 -05:00
Lauren Tan
9697e15bd5 Collect bailouts in BuildHIR::lowerMemberExpression 2023-01-20 11:47:09 -05:00
Lauren Tan
b2983571f2 Collect bailouts in BuildHIR::lowerExpression
Mostly mechanical changes to lowerExpression to push an error to HIRBuilder 
rather than invariant.
2023-01-20 11:47:09 -05:00
Lauren Tan
1a3e269274 Rename InstructionValue.OtherStatement to UnsupportedNode
As discussed, this repurposes OtherStatement as a catch all variant for 
unsupported syntax or errors in the source. This also renames the previously 
added ErrorTerminal to UnsupportedTerminal for consistency (plus makes it a 
little bit less confusing that it's not an actual terminal representing an 
Error). 

Not loving the name but couldn't think of anything better, open to suggestions!
2023-01-20 11:47:08 -05:00
Sathya Gunasekaran
c1c30889cc [hir] Lower function expressions into HIR 2023-01-20 14:01:51 +00:00
Lauren Tan
b3fedd3273 Fix import path to ReactForgetBabelPlugin
This broke jest in watch mode
2023-01-19 13:33:32 -05:00
Sathya Gunasekaran
7c73f51139 [hir] Define function name only if it exists 2023-01-19 18:39:23 +00:00
Sathya Gunasekaran
da6d372c3a [hir] Store env in HIRFunction
The env is specific to the HIR function anwyays. It's not very useful outside 
the HIRFunction.
2023-01-19 18:39:19 +00:00
Sathya Gunasekaran
0a60bf2071 [ts] Make ts emit source maps 2023-01-19 18:39:14 +00:00
Sathya Gunasekaran
a5b245b17d [hir] Fix typescript warnings 2023-01-19 12:07:05 +00:00
Joe Savona
d295bb4516 Fix scope merging in MergeOverlappingReactiveScopes
When `MergeOverlappingReactiveScopes` identified scopes to be merged, it was 
currently (oops, my bad) updating the _existing_ scope's id and range. Later, 
PropagateScopeDependencies marks outputs of a scope by updating that 
identifier's scope instance — so if that scope instance isn't shared, then the 
output is lost. This PR fixes MergeOverlappingReactiveScopes to correctly update 
all operands for a scope to have the same scope instance.
2023-01-19 10:10:21 -08:00
Joe Savona
7f7727d7e5 Consolidate logic in MergeOverlappingScopes prior to fix
Small refactor to consolidate visiting operands and their scopes, used in the 
follow-up PR.
2023-01-19 10:10:20 -08:00
Lauren Tan
6174c85ee9 [oops] Use correct import path in test262-preprocessor 2023-01-19 12:29:49 -05:00
Lauren Tan
40df4a8a86 [ez] Update test262-preprocessor to use the same config as hir-test 2023-01-19 11:37:50 -05:00
Lauren Tan
32a83374be Format CompilerErrors with Babel codeframes 2023-01-19 11:37:49 -05:00
Lauren Tan
60698e63c6 Re-emit unexpected hir-test error rather than a new one
Makes debugging a little easier as the previous console.error would be logged 
out of band with the jest error message. And the jest error would be missing the 
error stack.
2023-01-19 11:37:49 -05:00
Lauren Tan
b98e87aa63 Use BabelPlugin in hir-test 2023-01-19 11:37:48 -05:00
Lauren Tan
717719b8ea Fix infinite loop in BabelPlugin
After some painful debugging I isolated the infinite loop when attempting to use 
the BabelPlugin in hir-test rather than manually parsing and traversing it. The 
issue is that in the BabelPlugin we were replacing the original 
FunctionDeclaration with a new one, which would add it to Babel's traversal 
queue. This would effectively create an infinite loop where we would try to 
optimize a function that was already compiled by Forget (aside: _should_ running 
the compiler multiple times on code work?). 

To get around this we can just call the handy `skip` method on the new 
FunctionDeclaration to tell Babel to stop traversing it. I'm also moving the 
scope check here because I'll remove it from hir-test in a later commit.
2023-01-19 10:10:56 -05:00
Sathya Gunasekaran
f42c51b971 [λ] Support member expressions in lambdas
Lower member expression if the receiver is in scope. Skip the remaining path 
before capturing so we don't recurse down the identifiers in the member 
expression.
2023-01-19 12:13:35 +00:00
Sathya Gunasekaran
ef5636c25d [λ] Use lowerExpression to create captured identifier 2023-01-19 12:13:34 +00:00
Lauren Tan
50d27c325a Allow tests to be skipped with @skip
Also remove old filename based test directive, since the comment based one is 
much easier to use
2023-01-18 10:58:50 -05:00
Lauren Tan
23a044e406 Record more errors in BuildHIR::lowerStatement
Captures other kinds of statements that we're lowering to OtherStatements as 
TODO errors
2023-01-18 10:58:50 -05:00
Joe Savona
de83adc739 [be] Skip debug logging if content has not changed
Avoids printing debug information if it exactly matches what was last printed. 
This means when debug printing (eg with `@only`) you'll see things like: 

``` 

BuildReactiveFunctions: 

...debug view... 

FlattenReactiveLoops: (no change) 

PropagateScopeDependencies: 

...debug view... 

``` 

Which saves time figuring out if something changed in a given pass.
2023-01-17 16:00:42 -08:00
Joe Savona
8a15b65e1a [be] Reorder PruneUnusedLabels pass
This pass can run later, rather than right in between two passes that affect 
scope creation.
2023-01-17 16:00:39 -08:00
Joe Savona
0391405530 [be] Print implicit break/continue
When debug printing `ReactiveFunction`, clarifies which break/continue are 
implicit.
2023-01-17 16:00:36 -08:00
Joe Savona
2e501b0f01 Rename variables during BuildHIR
Per design discussion, this PR changes BuildHIR to maintain the invariant that, 
for each distinct variable in the input, that all references to that variable in 
the HIR will have the same unique `name` _and_ same unique `id`. Phrased 
differently: Identifiers with the same id will have the same name and 
vice-versa. 

This isn't an invariant we maintain throughout compilation — SSA form changes 
the `id`s — but crucially, ensuring that the `name` is also unique allows us to 
understand later which identifiers referred to the same original variable and 
which were different. 

Follow-up PRs will ensure that we maintain variable identifiers in the output as 
well, in all cases except shadowing (and for shadowing, we'll rewrite 
identifiers inside lambdas).
2023-01-17 09:40:50 -08:00
Joe Savona
edeaaaf38a Add PropertyCall/ComputedCall instructions
Adds `PropertyCall` and `ComputedCall`, which are a combination of 
CallExpression and PropertyLoad/ComputedLoad, respectively. The goal is to 
ensure that we correctly model the receiver of a call where the callee is a 
member expression, and also accurately record scope dependencies in for both the 
computed and non-computed (property) cases. 

An alternative that I tried first was to add a `receiver: Place | null` to 
CallExpression. That works well for HIR construction, but it's then very 
difficult at codegen time to correctly reconstruct the original call: if the 
receiver and callee share part of their structure then we can transform back to 
a non-computed member expression, otherwise it has to be computed. Eg we have to 
distinguish `a.b.c[d.foo]()` from `a.b.c[a.b.c.foo]()`. Given that our target is 
high-level code, it seems reasonable to have a higher-level representation for 
these cases. 

I'm open to feedback but this feels pretty reasonable in terms of complexity / 
precision of modeling.
2023-01-17 09:40:49 -08:00
Joe Savona
4a70aa7faf Make implicit break/continue explicit in ReactiveFunction
Previously when converting from HIR -> ReactiveFunction we elided break/continue 
terminals in places where control would implicitly transfer to the 
break/continue target and therefore nothing has to be emitted. The one downside 
of this approach is that it makes scope analysis a bit trickier. We want to 
close scopes once we see an instruction id past the end of the scope's range, 
but these implicit breaks were causing us to miss some instruction ids. We 
compensated for this, but it's helpful to keep the representation explicit and 
discard these terminals later in codegen.
2023-01-17 09:40:48 -08:00
Joe Savona
2c95cdb431 Collapse BuildReactiveFunction/TreeVisitor
Collapses HIRTReeVisitor into BuildReactiveFunction, allowing us to remove the 
generic interface and simplify the code. Note that because the visitor was 
already not attempting to group instructions by scope anymore, the visitor code 
was very straightforward. This is mostly replacing calls to `appendBlock(block, 
instr)` with `block.push(instr)`.
2023-01-17 09:40:47 -08:00
Sathya Gunasekaran
39eb89f65e [hir] Use types to infer constant declarations 2023-01-16 15:34:24 +00:00
Sathya Gunasekaran
6ea0b6a42e [alias] Don't alias primitives 2023-01-16 15:34:21 +00:00
Sathya Gunasekaran
ab6a1dbbd4 [λ] Add test for nested function declaration that captures a var 2023-01-14 10:19:40 +00:00
Sathya Gunasekaran
6af9c25300 [test] Run Forget only on top level functions 2023-01-14 10:19:40 +00:00
Sathya Gunasekaran
5fa3d41b22 [λ] Make function expr tests be reactive to input
This lets us memoize more and test function expressions better
2023-01-14 10:19:39 +00:00
Sathya Gunasekaran
e8cad1584c [be] Refactor capturePureScopes to be more generic 2023-01-14 10:19:39 +00:00
Sathya Gunasekaran
b1eb22d941 [be] Don't show duplicate codegen tab
The JS tab and the codegen tab are the same.
2023-01-13 16:33:04 +00:00
Sathya Gunasekaran
ddc135cf40 [be] Type CompilerPipeline as Generator 2023-01-13 16:26:09 +00:00
Sathya Gunasekaran
d25c2d0d58 [be] Make ts emit ES2015
This gives allows us to use generators without failing type checking.
2023-01-13 16:26:04 +00:00
Lauren Tan
7c65f62c27 Collect bailouts in BuildHIR::lowerStatement
Remove invariants and collect all errors
2023-01-13 16:14:07 -05:00
Lauren Tan
73f09248fb Add Result
Adds a direct translation of Rust's Result type, for later use in BuildHIR and 
possibly other passes
2023-01-12 19:35:20 -05:00
Lauren Tan
325987d744 Delete old CompilerError module
This was a carryover from the old architecture; we can remove it now to get rid 
of the indirection.
2023-01-12 19:35:19 -05:00
Joe Savona
321d7b7d00 [be] Clean up public exports
Cleans up the public exports for the package itself: 

* `parseFunctions()` was only used in playground, so this moves the definition 
there. I had to update playground's dependencies to ensure the babel version 
matched. 

* Flattens away the `HIR` const in the export, and exports just 4 functions all 
at the top-level: `run()`, `compile()`, `printHIR()`, and 
`printReactiveFunction()`. 

This will make it very easy to split the core compiler into a separate package 
from the babel plugin, though i'm not sure it's worth doing that (yet).
2023-01-12 12:16:07 -08:00
Joe Savona
bd7e19b85f Codegen is from ReactiveFunction->AST only
Other than BuildReactiveFunction (HIR -> ReactiveFunction), Codegen.ts was the 
only other remaining place that we use HIRTreeVisitor. However, we've already 
switched the compiler to use the new form of codegen, 
CodegenReactiveFunction.ts. This PR extracts the shared code from Codegen.ts 
into the latter, and deletes the unused bits of Codegen.ts which relied on the 
visitor API. 

This now frees us up to merge BuildReactiveFunction and HIRTreeVisitor, removing 
all the complexity of the visitor trait and type params. 

Note that the `ReactiveFunction` data type can represent non-reactive functions. 
So we can still compile non-React code after this change, the pipeline is `AST` 
-> (BuildHIR) -> `HIR` -> (BuildReactiveFunction) -> `ReactiveFunction` -> 
(CodegenReactiveFunction) -> `AST`. Which is the exact sequence I mapped out at 
the start of this project ;-) (happy that worked out!)
2023-01-12 11:49:47 -08:00
Joe Savona
da21e9c525 Switch to new BuildReactiveFunction passes 2023-01-12 11:06:40 -08:00
Joe Savona
a40ade1e61 Fixes, identical output in new version 2023-01-12 09:43:17 -08:00
Joe Savona
919f193934 MergeOverlappingScopes visits value block final values 2023-01-12 09:12:11 -08:00
Joe Savona
99dcf03852 AlignBlockScopes visits value block final values 2023-01-12 09:12:08 -08:00
Joe Savona
09246207c7 Value block final value has an InstrId 2023-01-12 09:12:01 -08:00
Joe Savona
df8945b684 Visit terminal operands during alignment 2023-01-12 09:11:58 -08:00
Joe Savona
9e549e9417 Fork HIRTreeVisitor to specialize it for ReactiveFunction conversion 2023-01-12 09:11:54 -08:00
Joe Savona
46024acb82 MergeOverlappingReactiveScopes (rewritten against ReactiveFunction)
See the background in #982. This PR reimplements part of InferReactiveScopes, 
merging overlapping reactive scopes, but against ReactiveFunction instead of the 
HIR.
2023-01-11 14:04:51 -08:00
Joe Savona
2358d9f01f AlignReactiveScopesToBlockScopes (rewritten against ReactiveFunction)
See the background in #982. This PR reimplements part of InferReactiveScopes, 
aligning reactive scopes to block boundaries, but against ReactiveFunction 
instead of the HIR.
2023-01-11 14:04:48 -08:00
Joe Savona
87d70b9129 BuildReactiveBlocks to construct scopes in ReactiveFunction
The primary goal of this stack is to change HIRTreeVisitor to make it easier to 
handle value blocks. That's complicated by the fact that the visitor is a 
general-purpose visitor, used in several analysis passes including 
BuildReactiveFunction (which translates HIR->ReactiveFunction while also 
grouping instructions into scopes) and InferReactiveScopes (which is actually 
two passes, one to align scopes to block boundaries, one to merge overlapping 
scopes). The long-term goal then is as follows: 

1. Make BuildReactiveFunction transform HIR->ReactiveFunction but _without_ 
reactive scopes. 

2. Align scopes to block boundaries, but rewritten to operate on 
ReactiveFunction 

3. Merge overlapping scopes, again rewritten to operate on ReactiveFunction 

4. Group statements within ReactiveFunction into ReactiveScopeBlocks (today this 
occurs when constructing the ReactiveFunction). 

This PR implements 1 and 4. Because the implementation is incomplete this would 
break the whole compiler, so for now both versions are still around. By default 
compilation uses the old pipeline, but if a feature flag is enabled we use the 
new version. The plan is to incrementally fix up the new version of the passes 
in this stack, and then cutover: removing the flag and the old version of the 
passes.
2023-01-11 14:04:45 -08:00
Joe Savona
a4fc975340 Scaffolding for LogicalTerminal 2023-01-10 13:49:57 -08:00
Sathya Gunasekaran
f9ecc96bf3 [hir] Update mutable range of operands during LeaveSSA
It's not enough to only update the mutable range of the canonical id created 
instead of the phi but we need to update the mutable range of each of the 
operands of the phi as well to account for the fact that the phi could've been 
mutated later. 

The operands are updated only if the phi is mutated later. Otherwise these 
operands can be cached in their blocks. 

Fixes https://github.com/facebook/react-forget/issues/978
2023-01-11 15:17:14 +00:00
Sathya Gunasekaran
986c95bc09 [hir] Add test case for incorrect memoisation of mutated objects
In this case, x should not be cached within the if or else blocks as it is 
mutated later.
2023-01-11 15:17:13 +00:00
Sathya Gunasekaran
ed63dba739 [hir] Print MutableRange in lvalue
This is admittedly a little noisy but super valuable when debugging.
2023-01-11 15:08:37 +00:00
Sathya Gunasekaran
bbba752e98 [typer] Add type inference for Phis 2023-01-11 11:30:58 +00:00
Sathya Gunasekaran
1910ede500 [typer] Add PhiType to represent Phis 2023-01-11 11:30:58 +00:00
Sathya Gunasekaran
0c3cfbdd36 [typer] Add type to Phi 2023-01-11 11:30:57 +00:00
Joe Savona
27b37341e5 Playground uses run() to automatically sync w compiler passes
Changes playground to use the modified `run()` function of the compiler, polling 
the generator and building up a Map of tabs automatically based on the passes 
that the compiler runs. This means tabs are always derived from the current 
state of the compiler and we can never forget to add a pass. 

<img width="1497" alt="Screen Shot 2023-01-10 at 11 06 03 AM" 
src="https://user-images.githubusercontent.com/6425824/211639442-da421f73-e19e-4b63-9f33-0ce5a68cceb7.png"> 

Note the inclusion of some recently added passes that weren't added to the 
playground — which was my fault but only bc i intended to ship this PR soon :-)
2023-01-10 11:05:32 -08:00
Joe Savona
7751202433 Make CompilerPipeline a generator
Turns CompilerPipeline into two functions: 

* `run()` is a generator and yields values that are a disjoint union of either 
AST/HIR/ReactiveFunction along with a name for that step. The idea is to use 
this in the playground so that it always matches the exact steps for 
compilation. I'll update playground in a follow-up. 

* `compile()` is ast in, ast out, and uses `run()` under the hood.
2023-01-10 10:15:45 -08:00
Joe Savona
f3168d020f Distinguish regular/value blocks 2023-01-10 09:37:33 -08:00
Joe Savona
dce5371763 Constant propagation/folding
Implements constant propagation/constant folding for a conservative subset of 
the language. The approach is described in detail in the comments in the file 
itself, a key note here is that this pass currently emits what looks like 
garbage: 

``` 

// input 

const x = 1; 

const y = x + 1; 

// output 

const x = 1; 

2; // <---- you'll see a bunch of lines like this 

const y = 2; 

``` 

These useless lines occur where previously there was a temporary getting 
calculated that was used later (so we saved it until it was used), but now it 
isn't used later so we just emit it in-place. Dead code elimination (DCE) can 
eliminate these and other useless statements later. 

Note that a key motivation for implementing this pass is to reduce memoization 
blocks to what is strictly required for dynamic computations. Why memoize at 
runtime when we compute at build time?
2023-01-09 15:30:20 -08:00
Sathya Gunasekaran
6f9c9cf9ea [hir] Make lambdas dependencies be a list of Places
This lets HIR understand and rename dependencies correctly.
2023-01-10 16:06:55 +00:00
Sathya Gunasekaran
346ecb27fe [hir] Gather dependencies for lambdas 2023-01-09 18:22:35 +00:00
Sathya Gunasekaran
ce7cab4eee [hir] Parse and codegen FunctionExpression 2023-01-09 18:22:32 +00:00
Joe Savona
57ea3b7e8a Merge consecutive blocks
See the previous PR for context, this PR adds a new pass to merge consecutive 
blocks.
2023-01-09 13:04:33 -08:00
Joe Savona
7a7538920e Broken test for labeled statements
When we convert a LabeledStatement to HIR we can end up emitting "consecutive" 
blocks, ie where there are two blocks such that control flow will always go from 
from one block to the other, with no other way to reach the second block but 
through the first. Example: 

```javascript 

label: { 

foo(); 

break label; 

} 

bar(); 

``` 

Converts to 

``` 

bb0: 

foo() 

goto bb1: 

bb1: 

bar(); 

... 

``` 

Ideally in this case we would merge these into a single block: 

* When debugging, the extra goto makes it look like there is conditional control 
flow when there isn't. If the code is consecutive it's easier to understand that 
if it's a single block. 

* Conversion from HIR -> AST relies on consecutive code all being in a single 
block, so this breaks codegen (we never visit the goto target since all gotos 
are assumed to be safe to convert to a break or continue). 

This PR adds a failing test case, the next PR fixes it.
2023-01-09 13:04:30 -08:00
Joe Savona
d8d5fe989c [be] Improvements to shrink/markInstructionIds 2023-01-09 10:06:03 -08:00
Joe Savona
0abc420c78 [be] Preds/phis use BlockId since blocks may change
Phi operands and Block predecessors currently use a `BasicBlock` reference 
rather than the BlockId. This diverges from other places (like terminals) where 
we use an id and not a direct object reference. Especially since blocks may get 
rewritten or pruned, it's a bit cleaner to use the block id in these places.
2023-01-09 09:22:43 -08:00
Joe Savona
3765219933 [be] Mutate HIR in-place (shrink/rpo)
Changes `shrink()` and `reversePostorderBlocks()` to modify the HIR in-place 
rather than return a new function, for consistency with all our other passes 
which mutate in-place (for performance reasons).
2023-01-09 09:13:31 -08:00
Sathya Gunasekaran
159a3f64d0 [typer] Remove unnecessary unifier parameter 2023-01-05 13:46:55 +00:00
Sathya Gunasekaran
db3839e716 [typer] Cleanup lvalue null checking 2023-01-05 13:46:51 +00:00
Sathya Gunasekaran
cccec0e5b2 [be] Don't use nested imports 2023-01-04 21:11:40 +00:00
Sathya Gunasekaran
b8fd4680b1 [hir] Move inference passes to separate folder 2023-01-04 21:11:37 +00:00
Sathya Gunasekaran
d187fff414 [typer] Move type inference to a separate folder 2023-01-04 21:11:33 +00:00
Sathya Gunasekaran
efac190973 [typer] Make type inference more conservative
It's not entirely correct to infer arguments to (not) equals operator as 
primitives.
2023-01-04 18:09:25 +00:00
Sathya Gunasekaran
09bdf3553f [hir] Remove unreachable fallthroughs separately from shrink
shrink visits all the fallthroughs even if they are unreachable so this isn't 
the right place to prune unreachable blocks. 

This PR moves pruning into a separate pass.
2023-01-04 18:09:24 +00:00
Joe Savona
e4642326d3 Make LValue non-nullable in HIR 2023-01-04 09:51:17 -08:00
Joe Savona
29bb3f55a3 Rename IndexLoad/Store to ComputedLoad/Store
Hopefully a more clear name, these values correspond to computed properties.
2023-01-03 17:03:45 -08:00
Joe Savona
bd99d06a5f Support computed property access/assignment
Supports computed properties (as LHS and RHS) correctly. Previously we only 
handled member expressions where the property was an identifier, and would 
incorrect treat `a[b]` the same as `a.b`. Now we correctly distinguish these and 
convert `a[b]` as an IndexLoad and `a.b` as a PropertyLoad. Similar for 
assignment, `a[b] = c` is an IndexStore. For both IndexLoad and IndexStore we 
lower the property to a Place first.
2023-01-03 16:59:41 -08:00
Joe Savona
ab150bd9d1 Object and array destructuring support in declaration/assignment
Implements support for array and object de-structuring in variable declarations 
and assignment expressions. Note that the code currently makes the overly 
optimistic assumption that the RHS is an array or object that can be safely 
indexed into. The correct representation would instead treat the RHS as possibly 
iterable, but we need to consider the appropriate representation. I think it's 
worth landing a first optimistic pass and we can iterate forward, this helps 
make it more clear what the ideal representation would have to be and should 
make a bunch of examples work. It also allows us to experiment with 
representations of, and handling for, scope dependencies that involve computed 
property access.
2023-01-03 16:59:38 -08:00
Joe Savona
45b3bd4899 Foundation for IndexStore/IndexLoad (no lowering)
Adds new types for IndexLoad/IndexStore (renamed later in the stack to 
ComputedLoad/ComputedStore) which will be used to represent computed property 
access/update. The actual lowering to use these is later in the stack.
2023-01-03 16:59:35 -08:00
Joe Savona
c2b2df0d17 Remove unused Place.memberPath
After the previous PR to change the scope dependency representation, 
`Place.memberPath` is now completely unused, this PR deletes that field and all 
references. Rejoice!
2023-01-03 15:34:42 -08:00
Joe Savona
c69cba357f Use distinct type for scope dependencies
This is a pre-req to deleting the `Place.memberPath` field. We no longer need 
memberPath in the HIR now that we have PropertyStore and PropertyLoad. However, 
scope dependencies use memberPath to track the precise fields that a computation 
depends upon. This PR changes scopes dependencies to use a new 
`ReactiveScopeDependency` type (Place + optional path), which allows the next PR 
to remove `Place.memberPath`.
2023-01-03 15:34:39 -08:00
Joe Savona
26cd435c7d Support chained assignment expressions
Fixes codegen for chained assignment expressions. Previously each intermediate 
assignment would be generated independently _in addition_ to the final chained 
expression being emitted. We now emit a single chained expression, almost 
exactly matching the input except for expanding from `x += 1` into `x = x + 1`. 

There are two key changes: 

* Ensuring that assignment expressions always generate an lvalue, which is 
necessary for alias analysis to kick in, since it relies on the effect of the 
lvalue to know where to look for aliasing. 

* The above makes codegen think the entire assignment expression value is a 
temporary that can be emitted later, but that isn't true. The new 
PruneTemporaryLValue pass nulls out lvalues that are never read later, ensuring 
that codegen can eagerly emit the value instead of saving it as a temporary.
2023-01-03 14:11:04 -08:00
Joe Savona
0a6af823b3 Lowering for PropertyStore
Converts assignment expressions where the lvalue is a MemberExpression to use 
PropertyStore, rather than creating an lvalue with a member path. The net effect 
is that lvalue will always have a null member path. 

There are two main cases: 

* `x.y = <value>`. We lower <value> to a Place, lower the object of the member 
expression to a place (`object)` create a temporary Place for the result of the 
assignment, and then create a `PropertyStore <object>, "<property>", <value>`. 

* `x.y += <value>` (and similar update-in-place operands). We extract the object 
of the member expression, read the current value via a PropertyLoad, compute the 
updated value, and store it back with a PropertyStore (each of these goes into 
its own temporary).
2022-12-22 15:04:57 -08:00
Joe Savona
c7a84fc033 Foundation for PropertyStore (no lowering yet)
Adds a `InstructionValue::PropertyStore` variant and the minimal necessary 
handling across the passes to get things to compile. Lowering is in the next PR.
2022-12-22 14:51:27 -08:00
lauren
7ee40764e6 Revert "[nocommit] Run test262 on this PR to verify it works for real…
…-realsies" 

This reverts commit 3c8700cbc7502e56cca8d4bb77a08e1ee747c166. 

I'm not sure how this happened but I accidentally hit up + enter on a previous 
`ghstack land` command for a different stack and it landed this one. 
Infuriating.
2022-12-22 17:53:59 -05:00
Lauren Tan
2d4c2094d8 [@nocommit] Run test262 on this PR to verify it works for real-realsies
final v2
2022-12-22 17:40:20 -05:00
Lauren Tan
5e0dba9012 [test262] Always return a pseudo Error object
For unknown reasons I didn't have the energy to dig into, some Babel Error 
objects can't be written to so despite formatting the error message, the 
original one would still be used. To get around this I'm just constructing a 
fake object with a `name` and `message` so the correctly formatted messages are 
used.
2022-12-22 17:40:18 -05:00
Joe Savona
74a0d54db2 Account for stores that can occur in object/array literals 2022-12-22 14:00:49 -08:00
Joe Savona
1fd4bad525 RHS member expression converts to PropertyLoad
This is an incremental step to removing `Place.memberPath`. This PR changes how 
we handle MemberExpressions in rvalue position, converting to a new 
`PropertyLoad` InstructionValue variant. Example: 

``` 

let x = a.b; 

x.y = b.c; 

=> 

Const tmp1 = PropertyLoad a, 'b'; 

Const x = Place tmp1; 

Const tmp2 = PropertyLoad b, 'c'; 

Reassign x.y = tmp2 

``` 

That we already recently made a chance to ensure that _if_ the lvalue is a 
member expression, that we convert the RHS to a Place. So although `x.y = b.c` 
could technically be lowered to a single instruction (with the`b.c` as a 
PropertyLoad), we force this to a temporary to ensure that we can independently 
memoize the RHS value. 

The net result is that the following combinations are possible: 

* `x = y`, lvalue identifier, rvalue identifier 

* `x.y = y` lvalue member path, rvalue identifier 

* `x = y.z` lvalue identifier, rvalue property load 

As noted above, `x.y = a.b` no longer occurs (and there's an invariant for this 
in one of the passes). 

A follow-up PR will add a PropertyStore instruction so that we can remove member 
paths in lvalue position too.
2022-12-22 14:00:48 -08:00
Lauren Tan
8246956331 Force DisjointSet.union to always pick a root
@josephsavona had the intuition that we were picking the wrong root in the 
previous infinite loop test case, so the fix for this is to force a root to 
always be picked. This works because `find` implements path compression (if a <- 
b and b <- c, then we can just point a <- c to "flatten" the tree which makes 
subsequent `find` operations more efficient since we don't have to follow the 
ancestor chain each time), so we're forcing all those unions to pick one parent. 

From some googling it looks like the traditional way to implement union is to 
call `find` so this should be the "right" way to fix it (?). 

I'm also adding a basic unit test for DisjointSet, I think we could revisit 
later and see if property testing is worth it but for now I mainly wanted to 
capture the regression test as a unit test.
2022-12-22 16:16:16 -05:00
Lauren Tan
311e5fdf69 Add infinite loop test case
Discovered this by accident modifying Joe's playground example. This seemed to 
be triggered in inferReactiveScopeVariables when iterating over the DisjointSet 
of scopeIdentifiers. In particular this test case contains a cycle and 
DisjointSet.find would never terminate.
2022-12-22 16:16:16 -05:00
Lauren Tan
75fc20981a Fix test262 for realsies
The github action was exceeding maximum allowed memory size because we were no 
longer grouping messages correctly prior to formatting them in the script. I 
think these were introduced when we integrated the Babel plugin into the 
preprocessor. This PR strips out filenames from the message so they can be 
grouped together again. Also added some light comments 

Test plan: manually ran `scripts/test262.sh` and verified that the JSON was 
grouped together correctly
2022-12-22 13:12:08 -05:00
Joe Savona
47e20c3f66 Expect files are input/ouput code only (no HIR/scopes) 2022-12-22 10:23:14 -08:00
Joe Savona
4385a283e4 Ensure non-conflicting names in output, wo suffixes by default 2022-12-22 10:23:11 -08:00
Lauren Tan
b5623abf74 [ez] Update test262 submodule
`git submodule update --remote`
2022-12-21 16:07:11 -05:00
Lauren Tan
003a1772ab Fix broken import path in test262-preprocessor
Previously we weren't ever clearing the `dist` directory so there were likely 
some vestigial files left over from previous builds that might have confused the 
import path. This commit fixes the import path and also deletes the `dist` 
directory on every build to ensure a clean slate.
2022-12-21 16:04:29 -05:00
Sathya Gunasekaran
f72397b5c1 [typer] Infer JSXText as a Primitive 2022-12-21 21:13:58 +00:00
Sathya Gunasekaran
6ba2117227 [hir] Use type inference for Effect.Store refinement
Only identifiers of type object can be safely refined to Effect.Store, default 
unknown identifiers to Effect.Mutate.
2022-12-21 21:13:58 +00:00
Sathya Gunasekaran
ff29264050 [typer] Remove type inference of fields
Given that our type inference needs to be very conservative, there's not a lot 
of benefit to having such fine grained type inference. 

In the future, we can use type information from flow/ts for inference.
2022-12-21 21:13:57 +00:00
Sathya Gunasekaran
b59f9dd561 [hir] Remove unused code from InferAlias
For now, we've decided to punt on super fine grained aliasing of fields (ie, 
mutating x.y shouldn't mutate x.z or it's aliases). 

This PR removes the code that tracks the aliases of each field. We can re-add 
this when we revisit this functionality. 

For the rest of the field aliasing, most of it has been replaced by 
InferAliasForStores.
2022-12-21 21:13:57 +00:00
Joe Savona
8c920cfa90 Factor out assignment expression cases
Splits handling of simple assignment updates (`=`) from update-assignments (`+=` 
etc). This unblocks starting to support destructuring for the simple case in the 
subsequent PR.
2022-12-21 10:02:06 -08:00
Joe Savona
69e34270a2 Factor out assignment lowering to allow generating multiple instructions 2022-12-21 10:02:06 -08:00
Joe Savona
836211a549 Prep for destructuring, factor out lowerIdentifier() 2022-12-20 16:57:13 -08:00
Joe Savona
079cfe68bf Fix test262 preprocessor
The test262 preprocessor was correctly running forget against all the functions 
in each test file, but it was incorrectly transforming the file contents 
overall. Previously we swapped the contents of the file for the transformed 
output of the last function, now we use the babel plugin to rewrite functions in 
place and keep the rest of the file intact. 

Of course, the tests that failed before still fail. But when we fix them the 
tests will work now.
2022-12-20 16:27:11 -08:00
Joe Savona
bc0787fefb More precise handling of const/reassign in ssa form
Addressed a TODO from the previous PR. When we enter SSA form, when we rewrite 
variable reassignments we currently change the identifier but leave the kind of 
the lvalue alone; technically we should convert from Reassign to Const. After 
doing that, it's easier to correctly update when we leave SSA form, we can 
convert just a subset back into let/reassign (but leave most things alone as 
const).
2022-12-20 16:09:59 -08:00
Joe Savona
178bde3495 Run LeaveSSA prior to analyzing reactive scopes
Reorders LeaveSSA so that it runs before we begin evaluating reactive scopes. 
Note that reactive scopes must span the full construction of each variable — for 
variables with a phi, this must span the declaration and all assignments of the 
phi operands. And that's exactly what the new LeaveSSA does! LeaveSSA removes 
phi nodes and ensure that all versions of a variable which flow into a phi have 
been assigned a single canonical identifier (with an appropriate mutable range). 

This PR includes this and some related changes: 

* Reorders the pass 

* Changes hir-test to print the final HIR, eg just prior to codegen 

* Teaches LeaveSSA to update the mutable range of the canonical identifiers it 
assigns, based on the min/max of the variables assigned.
2022-12-20 15:58:03 -08:00
Sathya Gunasekaran
5388839357 [hir] Generalize alias analysis for Effect.Store
Rather than special casing for field stores, look for Effect.Store to alias 
fields. 

In the future, this will be extended to other constructs like Array#push.
2022-12-21 12:04:02 +00:00
Sathya Gunasekaran
afdd2cb910 [hir] Add Effect.Store
Effect.Store is exactly like Effect.Mutate, the only difference is that Store 
aliases one into a another value. 

There is no practical difference between Effect.Mutate and Effect.Store 
currently.
2022-12-21 12:04:01 +00:00
Sathya Gunasekaran
69fbdfc7cd [typer] Add inferTypes pass to playground 2022-12-20 14:55:56 +00:00
Sathya Gunasekaran
c130bcc9c2 [typer] Use opaque TypeId to number types 2022-12-20 14:55:52 +00:00
Sathya Gunasekaran
549f5a4af6 [typer] Return a narrow-er type from makeType 2022-12-20 14:55:48 +00:00
Joe Savona
828fd81be3 Add new pass to playground 2022-12-20 12:16:34 -08:00
Joe Savona
af91a7ab86 Ensure member path assignments memoize independently
Assignment expressions to a member path are a special case because they're the 
only place where a value isn't assigned to a (possibly temporary) variable, 
which is our unit of memoization. #901 demonstrated how this can lead to values 
that can't be independently memoized: 

```javascript 

const x = {a: a} 

x.y = [b, c]; // array recomputed w `x`, even if only `a` changed 

``` 

This PR ensures that assignment expressions where the LHS is a member path lower 
the RHS to a Place. That means the above example is handled as if you wrote: 

```javascript 

const x = {a: a}; 

const tmp1 = [b, c]; 

x.y = tmp1; 

``` 

And we independently memoize the temporary.
2022-12-20 11:06:08 -08:00
Joe Savona
cedaee5b03 Codegen for scopes without dependencies
Completes a todo for the `if` condition of scopes without any inputs. In this 
case since there are no inputs we can check for changes, we check if the first 
_output_ cache slot is set to the sentinel. 

For this to work we need to ensure that all scopes have at least one output, 
which isn't currently the case. Dead code can produce output-less sentinels. So 
this PR also adds a pass to find scopes w/o any outputs and convert them to 
regular blocks. We could in theory also just delete them, but for now let's be 
more conservative. This is something we'd want to highlight as a diagnostic in 
an IDE, though existing dead code linters would almost certainly find this case 
too.
2022-12-20 11:06:07 -08:00
Lauren Tan
ae8f9066dd Fix invalid import path in test262-preprocessor 2022-12-20 11:42:51 -05:00
Lauren Tan
2721d2a0d9 Remove compiler flags
Remove our existing compiler flags since they were only being used for 
enabling/disabling passes to aid debugging and to simplify in preparation for 
the upcoming work on diagnostics and bailouts. Additionally with the new 
playground tabs disabling passes has become less necessary. In the future when 
we have actual compiler flags (eg tweaking optimization levels) we can add this 
back. 

I opted to keep the existing `CompilerResult` return value instead of just 
returning the optimized AST as we're still using `scopes` in our test fixtures.
2022-12-20 11:42:51 -05:00
Lauren Tan
7cf8e9ac42 [ez] Fixup missing passes in test262-preprocessor 2022-12-20 11:13:05 -05:00
Lauren Tan
9390e969e2 mv src/HIR/Pipeline src/CompilerPipeline
Small reorganization to move Pipeline out of HIR since it's a compiler module. 
It used to make sense before to be in HIR since the old architecture was the 
still the primary, but no longer!
2022-12-20 11:13:05 -05:00
Sathya Gunasekaran
f1af022be1 [typer] Fix objectTypeEquals 2022-12-20 13:38:09 +00:00
Sathya Gunasekaran
6f06b0e08a [typer] Reuse PolyType objects 2022-12-20 13:38:08 +00:00
Sathya Gunasekaran
2c3e572566 [typer] Be more conservative with function type inference
It's not safe to infer types of arguments and return values because Javascript 
is so polymorphic. Instead just infer the type of the callee for non methods. 

Interestingly, even this is not conservative enough for JavaScript because Proxy 
can also be callable. But I think for our use cases we will treat Proxy and 
Functions similarly (they're all just objects) so it's ok.
2022-12-20 13:38:07 +00:00
Joe Savona
9207607d37 Delete old architecture
Thanks old architecture! We learned a lot about what does and doesn't work via 
this POC, but it's time to move forward w the ~~new~~ current architecture.
2022-12-19 10:04:54 -08:00
Joe Savona
d9b9554948 Remove old architecture from playground
Removes the old architecture from the playground. Next step deletion.
2022-12-19 10:00:16 -08:00
Joe Savona
5db523a98a Fix bug with scope output analysis for nested scopes
When collecting the output of each scope I was checking to see whether each 
operand was being used after the end of the _current_ scope. What we really want 
to be checking is whether the operand is used after _the scope in which it's 
defined_. Those are the same thing when there is no nesting, involved, so the 
previous logic worked for most examples. 

It isn't super easy to tell when if the operand's scope has ended, because we 
don't always know what the "current" InstructionId is inside a ReactiveFunction. 
And that in turn isn't quite so easy to change, because of some edge cases like 
break statements that we synthesize. The solution here is to track the set of 
active scopes, and if an operand is used and its scope is not active then voila, 
it's scope must have completed and its an output.
2022-12-18 12:14:41 -08:00
Joe Savona
d649940c27 Test case showing lack of independent memoization of object properties 2022-12-18 12:14:38 -08:00
Joe Savona
05d1fe94f9 [be] Split files into directories
Moves _some_ files from HIR into new top-level directories. To not make 
@gsathya's life a pain I left the files he's touching alone, but I moved some 
others. My intent is to have something like this: 

* Babel/ - code for the Babel plugin, though ideally this actually gets split 
into a separate package and the compiler itself is AST in, AST out w no Babel 
dep. 

* HIR/ - the core HIRFunction, HIR and related data types, plus the HIR 
construction and printing, HIR visitors. 

* Inference/ - the core inference passes that operate on the HIR, including type 
inference, reference effects, alias analysis, mutable range analysis, etc. I'll 
let @gsathya  move these files when at a good stopping point. 

* ReactiveScopes/ - inference relating to reactive scopes, 
constructing/printing/codegenning ReactiveFunction 

* SSA/ - enter/leave SSA and eliminate redundant phis 

* Utils/ - every project needs a place to put stuff that doesn't fit into the 
other categories, this is ours. 

This leaves just index.ts at the top level, and overall feels pretty tidy. Not 
too tedious to figure out where anything goes, hopefully.
2022-12-16 17:25:11 -08:00
Joe Savona
6053703dbb add missing pass to playground
It's pretty tedious to keep the playground in sync w `Pipeline` — we need some 
abstraction so we can write the sequence of passes once and reuse it (while 
inspecting intermediate states).
2022-12-16 09:41:47 -08:00
Joe Savona
075cdcec9e [be] Move old compiler to separate package 2022-12-16 09:33:07 -08:00
Joe Savona
7a29405d68 [be] Rename some types for ReactiveFunction 2022-12-16 08:45:46 -08:00
Sathya Gunasekaran
7ab0303822 [playground] Increase min width of output tabs 2022-12-15 18:38:06 +00:00
Sathya Gunasekaran
25fb36c966 [playground] Make gutter smaller in the output tabs 2022-12-15 18:38:05 +00:00
Sathya Gunasekaran
9e9dba90eb [playground] Make input tab smaller 2022-12-15 18:38:05 +00:00
Sathya Gunasekaran
78d976302f [playground] Remove mobile support
This is mostly unused and there's just not enough benefit for now. We can re-add 
this if folks start using this site on mobile. Removing now to simplify the 
codebase.
2022-12-15 18:38:04 +00:00
Sathya Gunasekaran
c4f16fce5c [playground] Remove handlebar to resize input
The only use of this is to resize the input tab immediately to be smaller. 
Instead, a follow on PR will just make the input tab smaller by default.
2022-12-15 18:38:03 +00:00
Joe Savona
6efac28677 Prune unnecessary labels in output 2022-12-14 16:42:54 -08:00
Joe Savona
de3952352b Use new codegen in playground
Switches to the new codegen with memoization applied, and puts the JS tab first 
to make it easier to look at the input/output 

side by side.
2022-12-14 16:23:41 -08:00
Joe Savona
575db2b4ea [wip] Codegen with memoization applied
Updates the ReactiveFunction-based codegen from the previous PR to emit 
memoization code for each scope. This is currently naive and has some bugs, but 
it gets the idea across. The core logic is straightforward at this point, all 
the hard work is in earlier passes: 

* Compute one change variable per scope dependency, eg `const c_0 = $[0] === 
maxItems` 

* Generate one `let` binding for each scope output 

* Generate an if block where the test is if any of the change variables are true 
(`||` them together) 

* Generate the consequent block with the original code block, plus statements to 
save dependencies and outputs to their cache slots 

* Generate the alternate block to populate the scope outputs from their cached 
values 

## Todos 

A few things don't quite work yet: 

* Codegen is designed to avoid emitting variables for temporary values, but 
that's causing a few values to sort of disappear n the examples, or get emitted 
twice. There are a variety of ways to achieve this but we'll need to ensure that 
this category of values gets assigned to a variable and then reference the 
variable. This is more involved. 

* Scopes can end up with zero dependencies, in which case we should check that 
the first output cache is initialized. This one is more straightforward. 

* If there are early returns, we don't record that they occurred and replay them 
in the `else` branch for each scope. We know the algorithm though so i'm okay 
delaying that for now.
2022-12-14 16:12:36 -08:00
Joe Savona
70a7d5f41e Codegen from ReactiveFunction (no memoization yet)
Currently codegen operates from HIR using a tree visitor, but for scope 
construction we're converting the HIR (CFG) into a ReactiveFunction (AST-like). 
Our original idea for codegen was that we would convert the ReactiveFunction 
back to HIR, and then codegen from there. However, the ReactiveFunction is 
already in tree form...which makes it very straightforward to generate code 
from. 

So this PR implements codegen from ReactiveFunction. The output is _identical_ 
thanks in large part to reusing as much logic from Codegen.ts as possible. The 
next PR will add memoization logic.
2022-12-14 14:54:37 -08:00
Joe Savona
7199229cb4 Record scope outputs
While we're collecting scope dependencies, we have the exact right information 
to record scope outputs. These are variables that need to be defined outside of 
the scope and populated by recomputing (on change) or via the cached value (if 
no change).
2022-12-14 13:27:20 -08:00
Joe Savona
459263b763 Refactor reactive scope representation 2022-12-14 12:07:50 -08:00
Joe Savona
552fe1878b Cleanup previous dep collection 2022-12-14 08:42:39 -08:00
Joe Savona
dd1ff4dc30 Combine dependency collection and propagation, more precise collection
I realized that properly propagating scope dependencies requires reusing the 
same logic as dependency collection itself: a dependency of an inner scope 
should only be propagated upward if the dependency was declared before the outer 
scope, for example. So this PR reimplements dependency collection in the 
propagation pass. 

At the same time I made a few other improvements: 

* Don't report dependencies that are "constant". This is a bit simplistic for 
now, we can use a more advanced analysis later. 

* Try to avoid creating duplicate dependencies. 

This addresses the todo from the previous PR (flattening scopes in loops) since 
now we don't need to compute deps until after that runs. As a follow-up i'll 
remove the existing dependency collection.
2022-12-14 08:42:39 -08:00
Joe Savona
d909ae0434 Fix scope dependency collection ordering bug
Dependency collection has to visit the instruction id first before evaluating 
the instruction, in order to completely any scopes that would end at that 
instruction. Note the removed dependencies that don't appear within the scopes.
2022-12-14 08:42:38 -08:00
Joe Savona
972caf5bf1 Propagate scope dependencies upwards
Initial, partial implementation of dependency propagation. This lays the 
groundwork, #876 completes it after some other fixes this uncovered.
2022-12-14 08:42:37 -08:00
Joe Savona
5f3dc0b6c7 Flatten scopes within loops
We can't independently memoize values created within a loop, so this pass 
flattens scopes within loops. Right now this just flattens the scope away 
without propagating any dependency (or output) information, follow-ups will 
extend it to do that.
2022-12-14 08:42:37 -08:00
Joe Savona
be9b6356df cleanup unnecessary code from BuildReactiveFunction 2022-12-14 08:42:36 -08:00
Joe Savona
189f48ee0a Dont create scopes for consts w primitive values
We don't need to create scopes for primitive values that are never reassigned. 
The actual rules are more complex — we could choose to skip creating scopes for 
values that don't allocate — but this simple heuristic is good for now.
2022-12-14 08:42:36 -08:00
Joe Savona
73ad571c6b Add invariant for problematic LeaveSSA case
The new LeaveSSA looks ahead to the phis of fallback blocks. However, HIR can 
sometimes have multiple blocks with the same fallthrough (totally fine), so this 
diff clears the phis of fallbacks as they are reached to avoid reprocessing 
them. This caused a previously incorrect case to now fail, yay.
2022-12-14 08:42:35 -08:00
Sathya Gunasekaran
4b66531237 [test] Remove mermaid diagrams from test output 2022-12-14 15:47:13 +00:00
Sathya Gunasekaran
9bfad00410 [type-infer] Implement Hindley Milner type inference
Based on the lambda calculus of ForgetScript, this infers the types of 
primitives, objects and functions.
2022-12-14 15:36:32 +00:00
Joseph Savona
6e52cc3f38 Construct nested reactive scopes
There are a bunch of ways we can go about converting from the input HIR into a 
final form that has the preamble inserted and memoized blocks of code wrapped 
with change detection and caching. This is just one way, it might not be the 
ideal way. In any case, this pass converts HIRFunction -> ReactiveFunction. The 
latter is a recursive (tree-shaped) data structure that attempts to represent 
blocks each of composed of scopes or instructions, where scopes are themselves 
composed of blocks etc. The idea is a) this makes it easy to visualize the 
structure and check that the scopes and their dependencies are correct and b) 
this is a really nice form for adding the memoization code. We can convert from 
a ReactiveFunction back to an HIRFunction, wrapping each scope in the 
appropriate if checks and caching. 

## Example 

Consider the following example, which has 2 main scopes: an outer one for `x` 
and an inner one in the consequent for `y`: 

```javascript 

function foo(a, b, c) { 

const x = []; 

if (a) { 

const y = []; 

y.push(b); 

x.push(<div>{y}</div>); 

} else { 

x.push(c); 

} 

return x; 

} 

``` 

## Output 

The new builder constructs a ReactiveFunction for this example along the lines 
of the following (note that inputs are always empty bc we don't collect those 
yet): 

``` 

{ 

scope @0 [1:11] inputs=[]  { 

[1] Const mutate x$11_@0[1:11] = Array [] 

[2] if (read a$8) { 

scope @1 [3:5] inputs=[] { 

[3] Const mutate y$12_@1[3:5] = Array [] 

[4] Call mutate y$12_@1.push(read b$9) 

} 

scope @2 [5:6] inputs=[] { 

[5] Const mutate $13_@2 = "div" 

} 

scope @3 [6:7] inputs=[] { 

[6] Const mutate $14_@3 = JSX <read $13_@2>{freeze y$12_@1}</read $13_@2> 

} 

[7] Call mutate x$11_@0.push(read $14_@3) 

} else { 

[9] Call mutate x$11_@0.push(read c$10) 

} 

} 

[10] return x$11; 

} 

``` 

This shows the hierarchy: there's an outer scope, `@0` to compute `x` (the first 
scope), then within the if consequent there's another scope, `@1`, to compute 
`y`. We have some technically extraneous scopes to compute the JSX element; that 
can be cleaned up with a bit more refinement. 

With this structure — and the inputs and outputs of each scope filled in — we 
can convert to code in a straightforward manner. Each scope turns into a block 
along the lines of the following: 

(note here we use strings to index the cache, in reality these would be ints) 

```javascript 

// one change variable pet input: 

let c_a = a !== $['a']; 

... 

// one variable for each output: 

let x; 

... 

// if (changed) { recompute } else { use-cache } 

if (c_a || ... ) { 

x = ...; 

// one assignment per output 

$['x'] = x; 

// update cache per input 

$['a'] = a; 

... 

} else { 

// one assignment per output 

x = $['x']; 

... 

} 

```
2022-12-13 11:53:42 -08:00
Sathya Gunasekaran
2df0c832f5 [be] Remove stale TODO
Fixed in fa1ae5de5be84dd25263845fb43157d77c0826aa
2022-12-13 09:28:00 +00:00
Sathya Gunasekaran
74599a92a2 [hir] Update Place.type based on value inference 2022-12-13 09:17:19 +00:00
Sathya Gunasekaran
7f8ad49165 [hir] Split value tracking into a separate pass 2022-12-13 09:17:19 +00:00
Joe Savona
5aa4143e24 TreeVisitor distinguish blocks from statements
TreeVisitor didn't distinguish between the type of a block and the type of an 
item that can occur within a block - this was fine for Codegen which can use 
`t.Statement` for both of those values. However, the upcoming scope construction 
needs to distinguish instructions in a block from a block itself, so this PR 
adds a new type parameter.
2022-12-12 16:46:55 -08:00
Joseph Savona
35ba4149ec Support assignment in for statement's update clause
Per the title, this PR adds support for assignment expressions in update 
clauses. This was mostly fixed by the previous diff to improve value block 
handling, and there's only a bit more to do here to allow a "value block" that 
doesn't produce a value (we need a better name).
2022-12-12 15:40:46 -08:00
Sathya Gunasekaran
b8cf85d77d [hir] Add Type to Place 2022-12-12 21:10:50 +00:00
Sathya Gunasekaran
e2e5e389af [be] Move buildAliasSets to DisjointSet 2022-12-12 20:35:23 +00:00
Sathya Gunasekaran
3c976a24b3 [hir] Run mutable range analysis for aliases to fix point 2022-12-12 20:35:23 +00:00
Joseph Savona
b8d78a94a8 Improve "value block" handling
This is a pre-req to construct reactive scopes in #857. "Value blocks" such as 
`for` init/test/update and `while` test need to be consistently wrapped in 
enter/leave calls so that we can extend the range of values properly. We also 
need to handle `for` init blocks a bit differently, since they allow variable 
declarations but not other types of statements. 

This PR ensures that we use consistent methods for handling value blocks (`for` 
test/update and `while` test) and treats `for` init as a new type with its own 
enter/append/leave visitor functions.
2022-12-12 11:20:10 -08:00
Lauren Tan
c8bb9ea0af Move pretty printing Scopes into PrintHIR 2022-12-09 17:12:45 -05:00
Jan Kassens
255700e2cd Increase precision in InferMutableRangesForAlias
Previously, this step just set the mutable range of any alias set including any 

mutation to the end of the last mutable range of any of the containing 
identifiers. 

This change makes it so that the ends are only updated of the ranges that end 

before the last mutation. 

Fixes #852
2022-12-09 13:10:01 -05:00
Joseph Savona
748992d508 LeaveSSA: handle phi as operand to subsequent phi
Fixes https://github.com/facebook/react-forget/pull/858#discussion_r1044626137. 
The case is 

```javascript 

function foo() { 

let x$1 = 1; 

let y = 2; 

if (y === 2) { 

x$2 = 3; 

} 

x$3 = phi(x$1, x$2) 

if (y === 3) { 

x4 = 5; 

} 

x$5 = phi(x$3, x$4); 

y = x$5; 

} 

``` 

What happens here is that there are two _sequential_ phis for `x`. Previously 
when we encountered the second phi we would find that there is no `let` 
declaration for the phi or any of its operands, and create a new one before the 
second `if`. That's incorrect, these should all merge into a single `x` 
declaration. We now look up the phi operands to see if they are part of a 
previous phi, and merge them correctly.
2022-12-09 09:47:47 -08:00
Joseph Savona
d03c5bc0a0 Run LeaveSSA prior to analyzing reactive scopes
Reorders LeaveSSA so that it runs before we begin evaluating reactive scopes. 
Note that reactive scopes must span the full construction of each variable — for 
variables with a phi, this must span the declaration and all assignments of the 
phi operands. And that's exactly what the new LeaveSSA does! LeaveSSA removes 
phi nodes and ensure that all versions of a variable which flow into a phi have 
been assigned a single canonical identifier (with an appropriate mutable range). 

This PR includes this and some related changes: 

* Reorders the pass 

* Changes hir-test to print the final HIR, eg just prior to codegen 

* Teaches LeaveSSA to update the mutable range of the canonical identifiers it 
assigns, based on the min/max of the variables assigned.
2022-12-09 07:39:28 -08:00
Sathya Gunasekaran
daaf95854c [hir] Don't alias fields unless a mutation occurs after aliasing
A wise Joe once said, "We only care about observed aliasing". 

Instead of performing aliasing for fields and non fields together, split the 
analysis to happen over separate passes. Similarly split inferring mutable 
lifetimes pass for fields and non fields. 

Now, we can identify the aliases of fields that are *not* mutated and only alias 
the ones that do mutate. 

The algorithm is roughly as follows: 

1. Build the set of aliases for non fields 

2. Infer mutable ranges for all instructions except aliasing fields 

3. Infer mutable ranges for all aliased instructions based on the alias set 
calculated in step 1 (this doesn't include aliasing fields) 

4. Extend the set of aliases (calculated in step 1)  to include fields only if 
the field or the receiver is mutated after the aliasing, ie, if _mutability is 
observed_. 

5. Run infer mutable ranges again for all instructions including the fields that 
were aliased in the previous step. 

6. Run infer mutable ranges for all aliased instructions including the fields 
that were aliased.
2022-12-08 23:29:41 +00:00
Joseph Savona
21652a135b Improved LeaveSSA pass
## Problem 

The previous version of LeaveSSA used a very simple approach in which 
identifiers stored their pre-ssa id, and LeaveSSA restored this id back. The 
upside of this approach is that it's very simple and trivially correct (assuming 
no reordering of code). The downside is that after running LeaveSSA we lose all 
information about which versions of variable declarations are distinct, and 
which might merge together in a phi. That information is really useful for scope 
analysis! Consider this input (variables are numbered as they would be in SSA 
form): 

```javascript 

function foo(a, b, c) { 

let x$1 = null; 

if (a) { 

x$2 = b; 

} else { 

x$3 = c; 

} 

x$4 = phi(x$2, x$3); 

return x$4; 

``` 

The current LeaveSSA assigns all 4 variables back to `x$1`, with a single let 
declaration at `let x$1 = null`. However, from a reactive scopes perspective, 
there are really just 2 versions of x: the initial x$1 (defined and never used) 
and then x$2, x$3, and x$4, which have to be merged into a single scope because 
they are part of a phi. In other words, we can't independently compute x$2, x$3, 
or x$4 - if any of their inputs changes, we have to redo all the computation. 
However, the existing structure makes it difficult to figure out the correct 
starting point for this scope — there is no initial `let` declaration that we 
can refer to. 

Instead, we can represent the program as follows after LeaveSSA, and then use 
this form for scope analysis: 

```javascript 

function foo(a, b, c) { 

const x$1 = null; // NOTE: rewritten to const 

let x$2; // synthesized declaration to allow later reassignment 

if (a) { 

x$2 = b; 

} else { 

x$2 = c; 

} 

return x$2; 

``` 

Note that there are only 2 versions of x, and we have synthesized a variable 
declaration for x$2 at the appropriate scope. Our scope analysis can then 
determine that the range of x$2 is from the declaration to the end of the if. 

## Approach 

This pass does two main rewrites: 

* For variables that do *not* appear as a phi id or operand, it rewrites the 
declaration to be `const`. You can see this above for x$1. 

* For variables that *do* appear as a phi or operand, it synthesizes a new `let` 
binding at the appropriate scope (ie, in the appropriate block), and updates all 
other operands from the phi to use the same id for the variable.  You can see 
this above for x$2, x$3, and x$4. 

Note that the let binding is generated at the narrowest scope possible. In this 
example, we generate distinct let bindings for the other if and else branches: 

```javascript 

function foo(a, b, c) { 

let x = null; 

if (a) { 

// we generate a `let x$2` here 

if (b) { 

x = 0; // becomes x$2 

} else { 

x = 1;  // becomes x$2 

} 

x // becomes x$2 

} else { 

// we generate a `let x$3` here 

if (c) { 

x = 2; // becomes x$3 

} else { 

x = 3; // becomes x$3 

} 

x; // becomes x$3 

} 

} 

``` 

Because the different x values from the outer if/else can never join in a phi, 
we can treat them as independent variables and (re)compute them independently. 

The algorithm works by iterating in reverse-postorder, and looking ahead at 
fallthrough blocks to find phi nodes that may need a let declaration (see above 
example of where these are generated). It also tracks variables which _don't_ 
participate in a phi so that it can rewrite their declarations to `const`. 

## TODO 

This PR does *not* yet work for cases where there is unconditional assignment 
within a `while` test condition. That would technically create a distinct 
version of the variable that shadows the value for the loop, and you can't have 
variable declarations in a while test condition. 

That case already doesn't work, though, so i'm punting on it for now until we 
figure out a bit more around 

"value" blocks. We have some good options, like desugaring to a `for(;;)` and 
manually implementing the while semantics in that case.
2022-12-08 07:35:14 -08:00
Jan Kassens
4837e21de3 [HIR] add for terminal
The changes here are pretty significant and there's a bunch more left: 

- support for with any of `<init>`, `<test>` or `<update>` empty. - support for 
with `<init>` as `Expression` instead of VariableDeclaration` node - support 
assignment expressions in `<update>`, this seems like it might require further 
new abstractions to allow something like a block to codegen into a single 
expression.
2022-12-06 12:01:32 -05:00
Jan Kassens
db740366f8 "ValueBlock" for while test node
The instructions of a while test node cannot just be pushed to the previous 
block. This creates a new block for the test node and then during code gen 
converts the statements pushed to the "value block" into expressions.
2022-12-06 11:28:15 -05:00
Sathya Gunasekaran
3a1124fe14 [hir] Add alias analysis for lvalue aggregates
This adds a field sensitive, flow-insensitive, context-insensitive alias 
analysis for lvalue aggregates. 

In the future, InferMutableLifetimes can refine it's analysis using these field 
sensitive alias sets.
2022-12-05 22:27:01 +00:00
Sathya Gunasekaran
a855e76494 [hir] Introduce AliasSet type
Starting to get wonky typing out the entire type. This typedef is better for 
typing and readability.
2022-12-05 22:27:01 +00:00
Sathya Gunasekaran
2dab6f6c4d [hir] Store Primitives in AbstractState 2022-12-05 22:27:00 +00:00
Sathya Gunasekaran
9da7ad557d [hir] Introduce AbstractState.store
Currently AbstractState.alias performs three operations: - Reading a value - 
Storing the value - Updating aliasing (in the DisjointSet) 

This commit makes AbstractState.alias only responsible for updating the alias 
information. The rest of the operations are split into separate functions.
2022-12-05 22:27:00 +00:00
Sathya Gunasekaran
1c6e39a1d6 [hir] Introduce AbstractState.read to lookup value
Split AbstractState.alias into two separate operations for better readability.
2022-12-05 22:26:59 +00:00
Sathya Gunasekaran
7bdd0f03cb [hir] Perform alias analysis for aggregate rvalues
Maintain and use abstract memory to peform more refined aliasing of member 
expressions.
2022-12-01 19:03:50 +00:00
Sathya Gunasekaran
00f74d4e70 [hir] Correctly identify mutated identifiers
mutableRange.end is exclusive so account for this extra 1 instruction.
2022-12-01 19:03:47 +00:00
Sathya Gunasekaran
c96c9b458f [hir] Add a buildAliasSets pass
Moves the existing alias set building logic from inferMutableLifetimes to a 
separate pass. 

Additionally maintain abstract state to refine aliasing to not include 
primitives.
2022-12-01 19:03:44 +00:00
Joseph Savona
4b9c2f36fe add missing reactive scope passes to playground
I forget to add these passes to the playground, oops.
2022-12-01 11:02:27 -08:00
Jan Kassens
df58546f4e Codegen TValue are just Expressions (#839) 2022-12-01 12:56:45 -05:00
Joe Savona
509aa9f0e5 HIRVisitor - change labels to block ids
HIRTreeVisitor previously passed a string label (for certain blocks). This 
changes to pass the raw BlockId, and have codegen convert that to a string. I'm 
not sure if we'll need this but it would be helpful for eg visiting the IR and 
emitting a new IR, while mapping block ids forward. Even if we don't need that 
it makes sense for Codegen to decide how to convert a block id into a label 
(which has to obey the rules of an identifier, not the visitor's concern).
2022-11-30 10:24:51 -08:00
Jan Kassens
aa2b152ea8 Fixture tests for 2 cases of expressions that produce incorrect output
They're fairly related, but I figured it's worth keeping more examples. 

- For the `while` example we need to codegen into a single expression. - For the 
expression with contained assignment we need to either keep the SSA ids around 
or re-create a similar expression during codegen.
2022-11-29 18:33:00 -05:00
Lauren Tan
b3a26f1de2 Infer reactive scope dependencies
This builds upon @josephsavona's prior PR #817 to add support for inferring 
reactive scope dependencies for all instructions and terminals. 

Still TODO (probably in follow up PRs): 

- [ ] fix duplicate/different identity scope issue (see this [test 
fixture](5f3b260aaa/forget/src/__tests__/fixtures/hir/overlapping-scopes-while.expect.md)) 
- [ ] also collect outputs of each scope - [ ] add new tree visitor that only 
visits, consider renaming the current `visitTree` to `mapTree` or similar 

Co-authored-by: Joe Savona <joesavonafb.com>
2022-11-29 17:07:55 -05:00
Lauren Tan
ccca2d38cf [ez] Delete some old files 2022-11-29 15:23:56 -05:00
Lauren Tan
179a7f9605 Fix output tabs growing infinitely in height
The `automaticLayout` config option for Monaco causes it to remeasure itself, 
and the parent container's height being longer than the screen causes it to 
constantly grow infinitely. You can observe this bug by going to the playground, 
expanding any tab, and watch the scrollbar grow as the editor quickly grows to 
ridiculous heights and your laptop starts glowing red hot and̶͙̕ H̴͉͘e comes 
t̵͙́o ̴̜̿de̷̼̚s̷̻̍ec̶̮͒rate all knowled̵̥̆ge
2022-11-29 11:07:03 -05:00
Jan Kassens
9f7878c7c0 [easy] remove isFallthrough arg from mapTerminalSuccessors
The argument was unused and a confusing boolean argument that's easy to mix up. 
Suggesting to remove it until we see a need for it at which point we might want 
to introduce an enum to make the argument more obvious.
2022-11-28 17:48:59 -05:00
Jan Kassens
0bbb36e1a4 [codegen] more locations: binary, return, call (#820) 2022-11-28 16:54:46 -05:00
jacdebug
b932ce19a7 [playground] TabbedWindowItem lifting tabs state up
- List local state in TabbedWindowItem to Editor/index
2022-11-25 09:00:30 +00:00
Sathya Gunasekaran
9ae698c734 [playground] Add tab to display output from old architecture 2022-11-24 18:04:16 +00:00
Sathya Gunasekaran
76a4bd67f4 [playground] Remove unused kind parameter 2022-11-24 17:56:49 +00:00
jacdebug
c203d07d8c [Playground] vertical tabs to see multiple stages same time
# Add vertical tabs to see multiple stages same time 

<img width="781" alt="image" 
src="https://user-images.githubusercontent.com/430289/203837762-ccf37491-2006-4512-8014-0b7893f3396f.png">
2022-11-24 17:25:13 +00:00
Sathya Gunasekaran
d958d4b73e [playground] Remove line number from output
We already have instruction numbers as part of the output so line numbers just add extra visual noise.
2022-11-24 16:04:27 +00:00
Sathya Gunasekaran
06ca8b1026 [playground] Refactor compilation of HIR
Stack created with [Sapling](https://sapling-scm.com). Best reviewed with 
[ReviewStack](https://reviewstack.dev/facebook/react-forget/pull/825). * __->__ 
#825 

[playground] Refactor compilation of HIR
2022-11-24 15:28:38 +00:00
Sathya Gunasekaran
e2cfbeb3d3 [playground] Display only new HIR
Stack created with [Sapling](https://sapling-scm.com). Best reviewed with 
[ReviewStack](https://reviewstack.dev/facebook/react-forget/pull/824). * #825 * 
__->__ #824 

[playground] Display only new HIR
2022-11-24 15:24:40 +00:00
Jan Kassens
da6d84049e easy: name InstructionKind enum values (#818) 2022-11-23 10:48:53 -05:00
Joe Savona
e51553f5da InferReactiveScopes considers all operands, incl terminals 2022-11-22 14:10:06 -08:00
Joe Savona
a0054a1837 Visit terminal ids before terminal branches
This is a follow-up to merging ranges. I realized that we need to mark terminal 
ids as visited _before_ processing the branches of that terminal (whereas before 
we were marking terminal ids only _after_ processing the branches). That exposed 
another bug where interleaving could fail to be detected (with an error, 
thankfully) if one of the branches had completed already. 

Our small test suite is already really good!
2022-11-22 12:10:26 -08:00
Joe Savona
d2e5c31fc6 Test case showing reassignment block scoping problem
This demonstrates a situation we don't handle well today. The basic structure is 
that you have some variable defined at the top level, then some control flow 
like if/switch where _all_ branches reassign the variable, then some code after 
that references the resulting phi node: 

```javascript 

let x1; 

// ... mutate/read x1 

if (cond) { 

x2 = {}; 

} else { 

x3 = {}; 

} 

x4 = phi(x2, x3); 

``` 

We currently group x3, x3, and x4 into a scope together, but note that...there's 
no `let` declaration for any of those! This means that it looks like the scope 
for x2 and x3 start in the consequent/alternate, but the true scope spans from 
before-after the if. I'm inclined to say that LeaveSSA should run _before_ scope 
analysis, and produce something like the following in this case: 

```javascript 

let x1; 

// ...mutate/read x1 

let x2; // new variable declaration for the new version of x 

if (cond) { 

x2 = {}; 

} else { 

x2 = {}; 

} 

x2; 

``` 

This then allows us to construct a correct range for x2, which starts in the 
other block.
2022-11-22 10:42:36 -08:00
Joe Savona
4a3640f25c Merge scopes that interleave or cross control-flow boundaries together
- [x] Merge scopes that are interleaved 

- [x] Merge scopes if they both cross control-flow boundaries together 

- [x] Don't merge scopes that strictly shadow 

Still WIP because I want to double-check and see if i can find a simpler 
algorithm for this. But it works.
2022-11-22 10:42:33 -08:00
Sathya Gunasekaran
d9c6d61192 [test] Print HIR before leaving SSA
Stack created with [Sapling](https://sapling-scm.com). Best reviewed with 
[ReviewStack](https://reviewstack.dev/facebook/react-forget/pull/812). * __->__ 
#812 

[test] Print HIR before leaving SSA Looking at the HIR before it leaves SSA 
helps debugging better.
2022-11-22 17:59:42 +00:00
Lauren Tan
1f4705bbab [ez] Update test262 2022-11-22 11:39:42 -05:00
Joe Savona
6bf5a1b396 [codegen] Elide final return stmt if no value
This is a random driveby improvement. I realized that we can eliminate `return` 
statements if a) they have no value and b) they are in the top-level block. 
Functions implicitly return at that point — there can't be any succeeding 
instructions anyway — so we can save bytes in the output.
2022-11-22 08:03:40 -08:00
Joe Savona
1976fd03b4 Adjust scope ranges to block boundaries
Visits the HIR as a tree and updates mutable ranges to ensure their range end is 
aligned with the block in which the scope is declared: 

```javascript 

function foo(cond, a) { 

⌵ original scope 

⌵ expanded scope 

const x = [];    ⌝    ⌝ 

if (cond) {      ⎮    ⎮ 

...            ⎮    ⎮ 

x.push(a);     ⌟    ⎮ 

...                 ⎮ 

}                     ⎮ 

...                   ⌟ 

} 

``` 

The implementation tracks the block in which each scope "starts" (first 
instruction with an operand in that scope), and then finds the first instruction 
at that block (or a parent) which is after the scope's end.
2022-11-22 08:03:39 -08:00
Joe Savona
796e14f38e Identifier.scope includes scope id and range
Refactors `Identifier.scope` to be a `ReactiveScope` object with an id and 
range. This gives us a place to later add a list of dependencies for the scope.
2022-11-22 08:03:38 -08:00
Jan Kassens
d44082d63c [codegen] location refinements
- Add a `loc` to the `while` terminal node. 

- Move location data for assignments 1 level higher as that seems to work better 
in the generated code (not tested with actual debugger yet though, we'll 
probably want to look at this more closely.
2022-11-21 15:35:39 -05:00
Jan Kassens
1228bc8ce5 [codegen] assign some locations in generated code (#802) 2022-11-21 15:14:20 -05:00
Joseph Savona
855d13f4b2 Add instruction ids to terminals
[ghstack-poisoned] 

closes #799
2022-11-21 11:51:19 -08:00
Joseph Savona
9948e389f1 Opaque InstructionId type
[ghstack-poisoned] 

closes #797
2022-11-21 11:47:14 -08:00
Joseph Savona
391aeab5aa MutableRange is start(inclusive) to end(exclusive)
closes #796
2022-11-21 11:44:02 -08:00
Jan Kassens
e59c17ad85 [playground] upgrade to Next 13'
- update dependencies - migrate to app directory
2022-11-21 14:03:30 -05:00
Joe Savona
5f2833bd5c Add HIR "tree" visitor and use it for codegen, alternate printer
Refactors Codegen to extract the core "visit IR as a tree" logic separately from 
the code to emit JS: 

* `HIRTreeVisitor` is a new helper that visits the HIR as a tree. You call 
`visitTree(ir, yourVisitor)` and it drives visiting of the IR, tracking blocks 
and scopes and calling methods as appropriate. 

* `Codegen` is now implemented as a Visitor implementation. For example 
`enterBlock()` creates an empty `Array<t.Statement>`, `leaveBlock()` wraps that 
in a `t.BlockStatement`, etc. 

* `printHIRTree()` is a new IR printer (implemented as a visitor) that prints 
the HIR in tree form, so it retains the original shape of the code but with each 
block replaced with its IR equivalent.
2022-11-18 17:15:16 -08:00
Sathya Gunasekaran
0c1f88db6c [test262] Use compiler pipeline in test262 runner (#800) 2022-11-21 18:55:57 +00:00
Sathya Gunasekaran
61e0186950 Add compiler pipeline (#795) 2022-11-21 18:42:58 +00:00
Sathya Gunasekaran
584eff2d38 [hir] Handle simple aliases when inferring mutable lifetimes (#794) 2022-11-21 18:35:14 +00:00
Jan Kassens
43686787a0 [playground] add link to view sourcemap (#798) 2022-11-21 13:19:36 -05:00
Joe Savona
92afc1a50b Stub for final phase of reactive scope construction w docs 2022-11-18 14:59:33 -08:00
Lauren Tan
71a7f0d6b2 Wrap mermaid labels as strings
The new pretty printed scopes "syntax" breaks mermaid labels because the `@` 
character seems to be reserved. This wraps them all as a string so they work 
again.
2022-11-18 11:09:21 -05:00
Joe Savona
70e7a632bb Annotate mutable range for each scope
Expands InferReactiveScopeVariables to update the mutableRange of all 
identifiers to be the range of its scope. The result is that all identifiers in 
a given scope will have the same range, whose start is the minimum of the 
identifiers range starts, and end is the maximum.
2022-11-17 15:13:35 -08:00
Joe Savona
2d8dc3ec4f Phi operands belong in the same reactive scope
This completes the implementation of InferReactiveScopeVariables, adding support 
for phi nodes. Example: 

```javascript 

let x$0 = null; 

mutate(x$0); 

if (cond) { 

x$1 = a; 

mutate(x$1) 

} else { 

x$2 = b; 

} 

x$3 = phi(x$1, x$2); 

mutate(x$3); 

``` 

We now add x$1, x$2, and x$3 to the same reactive scope. This reflects the fact 
that x$3 cannot be computed without also computing both x$1 and x$2. Note that 
x$3 can never be x$0, so x$0 is _not_ added to the same scope. This allows us to 
take advantage of SSA form to note that _some_ instances of an identifier really 
are distinct.
2022-11-17 09:35:00 -08:00
Lauren Tan
cf7176d3f2 Add prettier script
Copies over the prettier script from the React repo with a few tiny tweaks for a 
nicer experience running prettier on changed files
2022-11-17 10:31:34 -05:00
Lauren Tan
188c1501be Condense test262 output in ci
There's no option to output the results of the test262 harness in silent mode so 
every pass and failure outputs multiple lines to stdout. Since there are many 
thousands of tests this results in unusable log files that are over 200k lines 
long. This PR redirects stdout to a tmp file and then we reformat the result 
into a small JSON object, grouped by the failure message with count. 

Example: 

```json [   {     "pass": false,     "data": {       "message": "Expected no 
error, got Error: TODO: Support complex object assignment",       "count": 66    
 }   },   {     "pass": false,     "data": {       "message": "Expected no 
error, got Error: TODO: lowerExpression(FunctionExpression)",       "count": 4   
  }   },   {     "pass": false,     "data": {       "message": "Expected no 
error, got Error: TODO: lowerExpression(UnaryExpression)",       "count": 6     
}   },   {     "pass": false,     "data": {       "message": "Expected no error, 
got Error: todo: lower initializer in ForStatement",       "count": 28     }   
},   {     "pass": false,     "data": {       "message": "Expected no error, got 
Invariant Violation: Expected value for identifier `15` to be initialized.",     
  "count": 14     }   },   {     "pass": false,     "data": {       "message": 
"Expected no error, got Invariant Violation: `var` declarations are not 
supported, use let or const",       "count": 76     }   },   {     "pass": true, 
    "data": {       "message": null,       "count": 1     }   } ] ```
2022-11-16 18:24:07 -05:00
Lauren Tan
9058797e6a Add test262 github action
Add a new workflow to run the test262 tests on commits to main but not in pull 
requests. This is to keep this test non-blocking on PRs but lets us track pass 
rates over time
2022-11-16 10:45:36 -05:00
Joe Savona
4a21bb8856 Infer sets of variables in each reactive scope
Adds a new pass `InferReactiveScopeVariables` which determines the sets of 
variables (by Identifier) which "construct together" and belong in the same 
reactive scope. Concretely, `Identifier` gets a new property `scope: ScopeId`, 
and this pass assigns each identifier a ScopeId value. The algorithm iterates 
over all instructions in all blocks (in a single pass) and builds up disjoint 
sets of identifiers that appear as mutable operands in the same instruction. 

The algorithm is relatively simple (especially since I had already implemented a 
union-find data structure): however looking at some examples reinforced that 
other planned todos around alias analysis are really important. We also have to 
think more about what "mutable lifetime" means in the context of SSA: currently 
variables that are reassigned (but never "mutated", eg bc they're assigned a 
value type) never appear as mutable.
2022-11-15 14:26:18 -08:00
Lauren Tan
e6956bd4d8 [ez] Remove test262-all script
Just realized we can run all tests without encountering the arg limit if a 
string is passed in. 

This is much better because the test runner will count all tests in the parent 
test directory rather than run the tests in each subdirectory
2022-11-15 17:01:28 -05:00
Lauren Tan
dff31d80eb [ez] Don't output the command when running test262
Currently yarn prints out the command prior to executing it which makes test 
output for test262 very verbose
2022-11-15 14:06:49 -05:00
Lauren Tan
ec64edae21 Create identifier for any global or unscoped variable
Noticed this while running test262 tests that many variables were throwing an 
invariant for being undefined. This includes things like the special `arguments` 
object, a global `assert` function used by test262, etc.
2022-11-15 13:32:53 -05:00
Lauren Tan
9027117c4f Scaffolding for test262
- Adds a shallow git submodule for test262 as the tests aren't available   as an 
npm module - To run all tests: `yarn test262:all`. Note that this chunks up the  
 tests by test262 folder as there are over 50k+ tests and the test harness only 
accepts arrays of filepaths which exceeds arg limits - To run a specific test: 
`yarn test262 test262/test/folder/file.js`.   You can also pass globs which 
expand into an array of filepaths: `yarn test262 test262/test/folder/**/*.js` - 
More instructions for the test-harness can be found here: 
https://github.com/bterlson/test262-harness
2022-11-15 13:24:14 -05:00
Joseph Savona
e149413057 Distinguish identifier id and ssa id
I noticed on @kassens's #771 that despite running LeaveSSA there are still cases 
where we still reassign to a unique identifier: functions that have reassignment 
but no phi nodes, such as: 

```javascript 

function foo() { 

let x$1 = 0; 

x$2 = x$1 + 1; 

} 

``` 

Here SSA form rewrote the second statement's LHS, but bc there's no phi node we 
can't recover what the original was supposed to be (`x = x + 1`). This was my 
oversight when suggesting the simpler LeaveSSA algorithm, it works for 
eliminating phis but not other reassignments. The only alternative to removing 
SSA form is to add assignment statements, which we obviously don't want to do 
since that generates bloat. 

This PR addresses the issue by adding an additional, optional property to 
`Identifier` called `preSsaId` that starts off null. When entering SSA we save 
the original id in this property and update id to a new SSA value. LeaveSSA does 
the inverse, setting id = preSsaId and nulling out the latter. This means that 
an identifier can always be uniquely identified by its `id` value at any point 
in the compiler, while it's trivial to correctly undo SSA form. 

```typescript 

type Identifier = { 

// Unique value for each original identifier 

id: IdentifierId; 

// The original, un-mangled variable name if this was a variable present in the 
source (null if it's generated) 

name: string | null; 

// When in SSA mode, this is set to the original, pre-SSA `id` value 

preSsaId: IdentifierId | null; 

} 

```
2022-11-15 10:36:54 -08:00
Jan Kassens
5f40a1bf75 [bug] fix codegen for LVals with memberPath (#772) 2022-11-14 11:35:47 -05:00
Jan Kassens
99181160d5 [hir] implement assignment expressions
Implements assignment expressions with operators other than `=` (such as `+=`) 
by lowering to an assignment. 

I think this isn't fully correct for something like `a.b.c += 1`, but it seems 
like there's more gaps in object accesses.
2022-11-14 10:58:55 -05:00
Lauren Tan
0c5fc52c47 Refactor VisualizeHIRMermaid
- Get rid of `indent` as it was making the code hard to read - Remove 
unnecessary 2nd iteration over blocks - Remove extra newline between the bb 
subgraphs and the jumps section - Remove trailing spaces - Remove newlines 
between each subgraph and jump
2022-11-13 16:07:53 -05:00
Jan Kassens
d9724c7e00 [playground] option to codegen output in HIR tab
We might need to revisit the tabs vs. options split, but for now this just adds 
a checkbox toggle that outputs codegenned JS instead of HIR in the HIR tab. Open 
to ideas to organize this in the future...
2022-11-13 11:00:29 -05:00
Jan Kassens
16430247a4 [playground] remove unused dependency next-transpile-modules (#768) 2022-11-11 12:27:28 -05:00
Jan Kassens
3fd88b7f6f [playground] fixup for TS error 2022-11-11 12:18:18 -05:00
Jan Kassens
b9ffb59478 [playground] remove multi-file support and preview (#767) 2022-11-11 12:11:08 -05:00
Lauren Tan
3b3107fb3e Add Mermaid visualizations for CFG into fixtures
This PR adds a new section to fixture tests, which renders the HIR into 

a visualization using mermaid.js syntax which can then be embedded 

directly into markdown. 

The nice thing about the mermaid syntax is that it's quite readable, so 

if desired we could replace the current basic block textual output with 

the mermaid block. I'm opting to append it for now and wait for feedback 

if we want to keep both or replace. 

To view the graphs in your editor, download an extension that 

adds mermaid.js support: 
https://mermaid-js.github.io/mermaid/#/integrations?id=editor-plugins. In vscode 
you can use this plugin by right clicking on "Open Preview" on any expect.md 
file. No extra dependencies are required for GitHub which should have builtin 
support for mermaid in markdown
2022-11-10 17:09:28 -05:00
Jan Kassens
779947520d [playground] new HIR tab
Instead of hacking into the babel plugin passes, this now takes a new approach 
for the HIR tab: 

- The different passes are exported from the babel plugin (for simplicity) 

- The tab actually runs the compiler steps based on local config in the tab. 

- Re-purposed the CompilerFlags component to configure what passes to run. This 
is currently mostly causing different errors, but could be useful going forward 
as a direction.
2022-11-10 15:45:51 -05:00
Jan Kassens
5e86e6a9c8 [playground] refactor output tabs
Removes a bit of indirection and opens the way to more easily customize specific 
tabs.
2022-11-10 14:32:40 -05:00
Jan Kassens
21b6112238 Fix yarn dev to run tsc watch mode
The `--watch` argument forwarding wasn't working anymore because `yarn build` 
doesn't have the `tsc` command at the end anymore. There's maybe something that 
can be done to forward the watch argument, just call `tsc --watch` directly.
2022-11-10 11:30:45 -05:00
Joe Savona
f57bcacced fix for while (...) { break } edge case
Small adjustment to the previous PR for a special case: 

```javascript 

while (cond) { 

break; 

} 

``` 

The loop body is an indirection to the fallthrough, so shrink() collapses that 
and makes the while.loop === while.fallthrough. We now detect that this is the 
case in codegen and correctly emit a `break` rather than trying to write the 
fallthrough block inside the loop.
2022-11-09 20:52:08 -08:00
Joe Savona
656b8c9a0e While terminal and codegen
Adds a new 'while' terminal variant, which will be a model for other loop 
terminals, and adds support for the entire compilation pipeline through codegen. 
To understand the structure of the terminal consider this input: 

```javascript 

let x = 0; 

while (x) { 

x = foo(x); 

} 

return x; 

``` 

We currently lower this to ifs and gotos: 

``` 

bb0: precursor to loop 

let x = 0; 

goto(break) bb1; // <-- **The new terminal replaces this** 

bb1: test block, whether to (re-)enter the loop 

if (x) consequent=bb2 alternate=bb3; 

bb2: loop body 

x = foo(x); 

goto(continue) bb1; 

bb3: fallthrough after the loop 

return x 

``` 

This representation correctly models the semantics of while statements, but 
loses the high-level information that there was a loop. The new 'while' terminal 
replaces the first 'goto(break) bb1'. Conceptually, the 'while' terminal means 
"enter the starting point of a while loop". In this example the terminal would 
look like this: 

``` 

{ 

kind: 'while', 

testBlock: 'bb1', // the basic block that checks whether to enter the loop or 
not 

loop: 'bb2', // the block containing the loop body 

fallthrough: 'bb3' // the block that goes after the loop 

} 

``` 

Most passes will only look at 'testBlock', ie they will treat this terminal as a 
simple goto:testBlock. However, codegen uses the full information in the 
terminal to reconstruct the loop. My previous PR, #755, added a mechanism to be 
smart about when to emit or not emit `break` statements; this PR improves upon 
that to accurately emit the minimal break and continue statements: ie omitting 
entirely where they are extraneous, emitting unlabeled break/continue when 
sufficient, and falling back to labeled break/continue only where strictly 
necessary. The logic is very much analogous to IR construction.
2022-11-09 16:39:50 -08:00
Lauren Tan
90aa627c6b Add LeaveSSA pass to DumpHIRPass 2022-11-09 17:31:00 -05:00
Lauren Tan
e64ef9e1e0 Extract eachBlockOperand to its own visitor
Follow up for #757: 

- Adds a new visitor which iterates over every Place within a BasicBlock - 
Remove unused entryBlock binding - Comments
2022-11-09 16:37:14 -05:00
Lauren Tan
74e6ffc4a2 Leave SSA form
Alternative approach to #750. We now store the original identifier on the Phi 
node, then rewrite every BasicBlock's identifiers to reference the original id 
instead of the SSA'd id. 

This solves the shadowing problem and also lets us omit adding copies of 
instructions.
2022-11-09 15:12:04 -05:00
Joe Savona
ea1a18ec16 Emit labeled ifs/switch/break; gen each block exactly once
The approach is very similar to what BuildHIR does to resolve break and continue 
targets during IR construction: 

* We annotate goto targets as either a break or a continue (during HIR 
construction). This is necessary to reconstruct the right kind in codegen. 

* Codegen continues to work by traversing the IR as if it were a tree, relying 
on the `fallthrough` branches of if/switch to be able to visit the 
consequent/alternate recursively and then emit the fallthrough branch. 

* We track a Set of blocks that are scheduled to be emitted by some parent in 
the tree. Nested ifs may all have the same fallthrough branch, which we only 
want to emit once. This set helps us to know that a parent is already going to 
emit some block, such that children can skip it. 

* We also keep a stack of break targets that are in scope, and use this to 
convert gotos appropriately, as either a break, continue, or nothing at all (for 
example a switch case that falls through has no explicit syntax to model this 
fall-through, the only option is to emit nothing for the goto). 

* Then, if/switch have to carefully check whether each branch should be emitted 
or not. For example, if the alternate is already scheduled to be emitted (by a 
parent), then we emit a block with a break statement instead. 

* Switch in particular is tricky, because we need to know that subsequent cases 
are scheduled, but only for preceding blocks. So we visit the cases in reverse 
order (not surprisingly, we do the same thing during IR construction for similar 
reasons!). 

The bookkeeping is a bit finicky but this works reliably. There are some cases 
where we could try to emit an unlabeled break instead of a labeled break, or 
avoid emitting a label at all (if nothing will explicitly break to that label), 
but overall the generated code is readable enough that i'm inclined to ship and 
iterate. I'm open to feedback though, as always!
2022-11-08 21:14:10 -08:00
Joe Savona
22d1481125 Add and use mapInstructionOperands() 2022-11-08 14:46:03 -08:00
Joe Savona
afabaa0af3 Add and use mapTerminalOperands() 2022-11-08 14:45:59 -08:00
Joe Savona
2a049044bb Add and use eachTerminalSuccessor() 2022-11-08 14:45:56 -08:00
Joe Savona
e665f6b877 Cleanup in advance of reworking codegen 2022-11-08 14:45:53 -08:00
Jan Kassens
e336ea44ed [playground] include HIR output (#756) 2022-11-09 13:39:50 -05:00
Lauren Tan
f8f4dc4b8b s/SSAify/Enter SSA/
Rename this existing pass to make more sense with the next PR
2022-11-07 18:34:45 -05:00
Jan Kassens
5bd3a39d86 Implement HIR visitors (#747) 2022-11-08 09:55:35 -05:00
Jan Kassens
7c094af34e [HIR] fix missing read reference to switch test value
Unless I'm mistaken, the switch operand is missing here.
2022-11-07 16:38:58 -05:00
Jan Kassens
5d81fdeb5f [easy] use yield* in eachInstructionOperand
Noticed we could simplify this code a bit using `yield*`.
2022-11-07 15:10:49 -05:00
Sathya Gunasekaran
69cce3d51f [hir][be] Move collectInputs to HIRBuilder
This seems like a better place to put helper methods.
2022-11-07 18:45:29 +00:00
Lauren Tan
4d9022a17c Remove early SSA phi optimzation
Reverts #726 which added an early optimization to the SSAify pass in skipping 
over phi creation if only one unique operand. This is no longer necessary with 
the addition of a phi elimination pass added in #739.
2022-11-07 12:25:50 -05:00
Joseph Savona
3a200fa4a1 Pass to eliminate redundant phis
This is an alternate take on phi elimination to the one we pursued over VC w 
@poteto driving. This version exploits the RPO ordering of blocks to do phi 
elimination in a single pass when there are no loops, and to minimize repeated 
visits when there are loops. The main difference is when redundant phis are 
removed. Rather than eagerly walking through the CFG for each pruned phi to 
rewrite its uses, we build up a mapping of rewritten identifiers. As we walk 
through subsequent instructions, we rewrite each place based on that mapping. We 
continue cycling through the blocks so long as a given iteration *both* added 
new rewrites (meaning there may be subsequent uses to rewrite) *and* there are 
back-edges. With no loops this results in a single visit of each block and of 
each instruction, but even with loops this is bounded.
2022-11-07 08:37:19 -08:00
Robert Zhang
2c37aa78d3 [Playground] Style compiler options editor
This diff adds styling to the compiler options editor. The floating input/output 
toggle button on small screens now spans the bottom of the screen, so that it 
doesn't block the compiler options. 

Height overflows when adjusting screen size are complicated by Monaco Editor and 
will be addressed in a later diff. 

Test plan: 

Start Playground and see the latest look of the compiler options editor beneath 
the output section.
2022-10-23 18:33:39 -04:00
Lauren Tan
22cbd97fe5 Update outdated package.json repo url 2022-11-04 18:13:47 -04:00
Lauren Tan
285b7e7acb Update outdated fixture snapshots 2022-11-04 17:59:04 -04:00
Joseph Savona
24ea3fa4bc Support JSX fragments (#736)
* handle jsx fragments

* update effects for jsx fragments
2022-11-04 11:16:50 -07:00
Sathya Gunasekaran
871a171a1f [hir] Add InferMutableRange pass 2022-11-04 16:39:45 +00:00
Sathya Gunasekaran
c75a1fe6bb [hir] Add MutableRange to Identifier 2022-11-04 16:39:45 +00:00
Sathya Gunasekaran
e69888054c [hir] Add id to number Instruction 2022-11-04 16:39:44 +00:00
Sathya Gunasekaran
e015bf6a7b [test] Add pragma parsing to test runner 2022-11-04 16:39:43 +00:00
Joseph Savona
5f81b7bc30 Replace NodePath with SourceLocation
Removes all the `path: NodePath` values from various IR node types, replacing 
them with `loc: SourceLocation`. This type is an alias for babel's source 
location type plus a "generated" variant. The "OtherStatement" kind also used 
the `path` to print back the original AST (since we don't look into these); 
instead, we now capture the underlying `node` and emit that as-is during 
codegen. 

While I was doing this, i also fixed up the places where we had passed a null 
path; the vast majority have a clear place we can pull a location from. For 
example, `a ?? b` syntax creates some places/instructions for the `a != null`, 
but those can all point back to the `a` location.
2022-11-04 08:55:18 -07:00
Lauren Tan
a9419099b4 Allow fixture tests to be only or skip
This commit introduces a small update to our fixture tests to allow certain jest 
`test` modifiers to be added to fixture tests via a leading prefix in the 
fixture name. 

For example, `only.my-fixture.js` is the equivalent of writing 
`test.only("my-fixture")`.
2022-11-03 18:47:02 -04:00
Lauren Tan
acb4999049 Import invariant from CompilerError 2022-11-03 18:47:00 -04:00
Jan Kassens
189b22171f [hir] implement nullish coalescing operator
This currently basically lowers the code into the equivalent of 

``` 

const vLeft = <left>; 

const vNull = null; 

const vCond = vLeft != vNull; 

vCond ? vLeft : <right> 

``` 

I created a temporary `Place` to hold the `null` constant value because the 
binary operator in HIR accepts only `Place`s. Not sure if this is the preferred 
approach. Alternatives I could think of: 

-  Allow constants as an alternative to Place? 

- A `NotNull` operator for `<x> != null` 

- Some other extension to the HIR?
2022-11-03 10:28:26 -04:00
Joseph Savona
515c33d2a6 Custom version of no-use-before-define rule
## Proper Detection of Out-of-order Functions 

The no-use-before-define rule from ESLint has a strange behavior in which it 
treats variables differently than functions: 

```javascript 

function foo() { 

return bar(X); 

} 

const X = null; 

function bar(x) {} 

``` 

By default, `bar(x)` has two errors: one because X is used before defined, and 
once because `bar` is used before defined. The rule has an option `{variables: 
false}` which only enables validation when the variable is from the same "scope" 
as the reference, the net result of which is it means it doesn't report spurious 
errors such as X being undefined. There is _also_ a `{functions: false}` option, 
but for some reason that doesn't work the same way, it just turns off all 
validation of references that came from functions. So enabling that option would 
suppress the (spurious) error on invoking `bar()` above, but causes the rule to 
miss invalid code such as: 

``` 

function foo() { 

return bar(); 

function bar() {} 

} 

``` 

This PR adds a fork of the rule that makes `{functions: false}` behave similarly 
to `{variables: false}`, which should help avoid some of the spurious errors i 
saw internally. The rule is exported from Forget itself, which will make it 
easier to consume internally, in tests, and in the playground. 

## Targeting the validation to Forget functions 

Even with the above, there are still some false positives coming from code such 
as: 

```javascript 

const x = foo(); 

function foo() {} 

``` 

This PR changes codegen to ensure that the output of a function _always_ has the 
body starting with 'use forget'. The ESLint rule then only looks at function 
declarations/expressions whose body starts with that expression. The new unit 
test confirms that the validation finds invalid reorderings even on functions 
that weren't explicitly tagged as 'use forget'.
2022-10-27 16:29:09 -07:00
Joseph Savona
fc1320552b [ssa] Potential improvements to ssa construction
Redo of #711 

Note that one test (switch.js) is failing, i'm working on a fix.
2022-10-27 13:20:10 -07:00
Sathya Gunasekaran
89151eb487 [ssa][be] Refactor visitedBlocks out of SSABuilder 2022-10-27 16:43:41 +01:00
Sathya Gunasekaran
2329326552 [hir] Run SSA pass on all tests 2022-10-27 16:43:38 +01:00
Sathya Gunasekaran
a5fb869640 [hir] Skip backedges when handling phis
These will be handled later during Environment.merge, no need to set an empty 
Set as value.
2022-10-27 16:43:34 +01:00
Sathya Gunasekaran
d9c61bac70 [hir][be] Improve debug printing of invariant 2022-10-27 16:43:31 +01:00
Sathya Gunasekaran
fd6cb34843 [ssa] Run SSA in RPO traversal
This prevents us from looking up state in blocks that have not been visited.
2022-10-27 16:43:28 +01:00
Sathya Gunasekaran
15104ab21b [ssa] Infer arguments of a function 2022-10-27 15:16:56 +01:00
Sathya Gunasekaran
fed106a8eb [ssa][be] Fix printing of SSA blocks 2022-10-27 15:16:52 +01:00
Sathya Gunasekaran
97757ea988 [hir] Run SSA pass before InferReferenceEffects pass 2022-10-27 15:16:49 +01:00
Sathya Gunasekaran
6b9f07624f [hir] Add support for handling phis in InferReferenceEffects
This just piggybacks on the infrastructure for handling Env.#variables. 

In the future, a better approach would be to simplify the environment creation 
and merging by leveraging the SSA property of the new IR -- 1) We don't need to 
track IdentifierId per environment as they are all    unique 2) Rather than 
tracking values, we can just track Identifiers because    Identifiers can never 
be reassigned.
2022-10-27 15:16:46 +01:00
Sathya Gunasekaran
de6afc2c52 [ssa] Update lvalue based on whether it has a memberPath or not
The semantics of lvalue changes based on whether lvalue.place.memberPath is null 
or not. If it's null, then lvalue.place acts as the lvalue for the instruction, 
otherwise it's just a reference to the memberPath specified location. 

Ideally we'd have an MemberExpression IR that lowers this complex lvalue into a 
temporary Place, uses this temporary place and stores back to the 
MemberExpression. 

Working around for now, will refactor to create a MemberExpression in the future 
if necessary for other analysis.
2022-10-27 14:11:23 +01:00
Sathya Gunasekaran
c8aaa7f172 [ssa] Use Identifier for updating SSA
Instead of using Place, use Identifier as the unit of comparison in SSA. 

Place is too high level and can not be substituted for other Places (even those 
with the same Identifier) as Place contain higher level metadata such as 
memberPath.
2022-10-27 14:11:19 +01:00
Sathya Gunasekaran
588a098149 [hir][be] Remove unused param 2022-10-27 14:11:16 +01:00
Sathya Gunasekaran
1396ee28a4 [ssa][be] Refactor to be more idiomatic typescript 2022-10-27 14:11:12 +01:00
Joseph Savona
e262d9e5ba only run validation if code is actually transformed
Disables post-codegen validation if there were no React functions in the input.
2022-10-25 08:33:20 -07:00
Jan Kassens
eb13f84b2d [fix] consistently import from react
Instead of a combination of `react` and `React`, consistently use `react` which 
is the preferred name to import.
2022-10-25 11:00:27 -04:00
Sathya Gunasekaran
34e02dab32 [ssa] Fix lookup of global values
Currently, HIR doesn't load global idenfifiers into a temporary Place which 
means our SSA transform breaks when it tries to lookup this global identifier. 

Instead of throwing, let's log and return the old place. This works for now but 
will probably break when we start mutating globals, but at that point our HIR 
builder will need fixes.
2022-10-24 16:04:20 +01:00
Sathya Gunasekaran
75871d7de7 [ssa] Add support for other kinds of Instructions 2022-10-24 16:04:19 +01:00
Sathya Gunasekaran
bb91cbbf62 [hir][be] Use a Map to store ObjectExpression.properties
Semantically this seems like a better fit as we're using Map like methods to 
iterate and update values anyway.
2022-10-24 16:04:18 +01:00
Sathya Gunasekaran
f54d121d71 [hir][be] Use a Map to store JsxExpression.props
Semantically this seems like a better fit as we're using Map like methods to 
iterate and update values anyway.
2022-10-24 16:04:18 +01:00
Sathya Gunasekaran
c347e8b6d2 [ssa][be] Delete dead code
Leftover from 4eba7a87ec6b27d1e8bad697ff6c0ac5db1ae7ad
2022-10-24 15:49:49 +01:00
Sathya Gunasekaran
152c3e81d3 [hir][be] Use .kind() to update and find ValueKind 2022-10-24 15:49:45 +01:00
Joseph Savona
f4df345420 Improve playground validation, workaround lack of JSX support
ESLint's default parser doesn't support any non-standard syntax, which includes 
JSX. So when I added the ESLint validation step to the playground, it meant that 
valid examples containing JSX still reported "invalid output". I tried to use an 
alternative parser, but I couldn't figure out the right webpack incantations to 
make `@babel/eslint-parser` or `hermes-eslint` work. I even tried recreating 
some of their code to avoid problematic imports, no dice. 

Instead this PR: 

* No longer uses the `postCodegenValidator` step, and runs the validation on the 
output after compilation completes. This is better anyway since we can see the 
output *and* the error messages 

* Shows rule violations as an "Invalid output" comment 

* Shows parser errors as a note (mostly to indicate that the validation step 
couldn't run, there could still be no-use-before-define violations that weren't 
found) 

Invalid example: 

<img width="1502" alt="Screen Shot 2022-10-21 at 9 50 23 AM" 
src="https://user-images.githubusercontent.com/6425824/197249007-1ec244a0-6dfe-4ec6-a0d0-60302efd86bd.png"> 

Sample example but with some JSX: 

<img width="1500" alt="Screen Shot 2022-10-21 at 9 50 39 AM" 
src="https://user-images.githubusercontent.com/6425824/197249030-e68ba968-4101-47c7-a148-f548f84f375c.png">
2022-10-21 11:00:39 -07:00
Joseph Savona
a7450572fa import runtime from "React.unstable_ForgetRuntime"
Imports the runtime from `React.unstable_ForgetRuntime` rather than from a 
separate module. The hope is that any code that gets transformed already has a 
dependency on React anyway, so we can avoid adding a new dependency that other 
systems don't know about. 

While here, i also cleaned up the `guardThrows` flag (we still parse it if 
present and warn, rather than throwing, to make it easier to adopt the latest 
version in various places).
2022-10-21 09:13:37 -07:00
Joseph Savona
2f34f85011 Enable no-use-before-define validation in playground
#686 added an option to validate generated code after transformation and adds an 
ESLint-based validator function to transform-test. Unfortunately it isn't super 
easy to wire up ESLint for use in a browser: traditionally the ESLint project 
specifically did _not_ support browser builds, but they recently have relaxed 
this because they added a browser playground on their website. There isn't 
official support, but the [playground 
repo](f3b1f78cc1/webpack.config.js) 
has a webpack config that, when combined with requiring a specific file, allows 
making things work in a browser. 

I tried using this directly in our playground app but Next's default webpack 
config doesn't work. So I created a separate package, playground-validator, 
which exports a webpack-built version of `eslint.Linter`. Then the playground 
can consume that, and everything works: 

## Test Plan 👀 

Confirmed that a known problematic example displays the validation message in 
playground (both locally and on the preview deployment): 

<img width="1500" alt="Screen Shot 2022-10-20 at 12 22 59 PM" 
src="https://user-images.githubusercontent.com/6425824/197041265-966ffda2-a3d0-450e-8fc4-fd1a7ca06e1a.png">
2022-10-21 08:37:18 -07:00
Joseph Savona
00a58cdab1 Option to validate output with ESLint
Adds a new compiler option `validateNoUseBeforeDefine`, which enables a 
post-codegen pass to validate that there are no usages of values before they are 
defined (which causes a ReferenceError at runtime). This can occur when a value 
is accessed when its in the TDZ (temporary dead zone), after the hoisted 
_declaration_ but before the variable is defined: 

```javascript 

function foo() { 

x; // x is in the TDX here: the binding from the subsequent statement is 
hoisted, but x is not yet defined. 

let x; 

} 

``` 

* The validation is off by default, but enabled in transform-test 

* The validation crashes compilation, rather than bailout, because the code has 
already been mangled and we can't roll back at the point the validation runs. 

* The validator uses ESLint's no-use-before-define rule by printing the program 
to source and then configuring ESLint to use Hermes parser. 

* transform-test now supports tests prefixed with "error." to indicate tests for 
which compilation is expected to crash (not just bailout), and the expect file 
includes the error message.
2022-10-19 13:00:47 -07:00
Jan Kassens
94f1e6f27c Add runtime dependencies to the dependencies
These are used somewhere in the `dist/` directory after building and as such 
should be included in the dependencies of the package.json.
2022-10-19 13:09:03 -04:00
Sathya Gunasekaran
eeb01b17e6 [ssa] Remove lambdas for update functions
Rather than passing lambdas above to the builder, pass the builder down and 
update places.
2022-10-18 19:30:53 +01:00
Sathya Gunasekaran
b9ce8bdcfd [SSA] Fix identifierID hack
Rather than starting from 1000, start from the last used identifier id.
2022-10-18 18:57:34 +01:00
Sathya Gunasekaran
379251c65f Add SSA-ify pass
The algorithm is described in detail here: 

https://pp.info.uni-karlsruhe.de/uploads/publikationen/braun13cc.pdf 

Note that the SSA form generated is not minimal. A follow on 
RedundantPhiElimination pass 

is required to prune the graph.
2022-10-18 18:45:01 +01:00
Jan Kassens
9524c58486 Replace import from '.' with path name
Somehow these imports aren't compatible with builds inside buck.
2022-10-18 13:14:27 -04:00
Tianyu Yao
95fe4ed0ed Include Utils.Component() in BailOnCapitalizedFunctionCalls
`Utils.Component()` wasn't caught by the bailout, and there are such usage on 
WWW 

Fixes #671
2022-10-17 22:44:03 -07:00
Joseph Savona
704d936009 [easy] rename InferReference{Capability=>Effects}
Forgot to rename the function/file when changing from capability to effect. 

#accept2ship
2022-10-18 08:49:31 -07:00
Tianyu Yao
4712017744 Include Utils.Component() in BailOnCapitalizedFunctionCalls
`Utils.Component()` wasn't caught by the bailout, and there are such usage on 
WWW 

Fixes #671
2022-10-17 14:41:26 -07:00
Lauren Tan
46d7f4f8af Synchronize symbol names with React
In the upstream useMemoCache PR this symbol got renamed and unexpectedly caused 
a RN crash. Synchronize it so the comparison works.
2022-10-17 16:07:50 -04:00
Lauren Tan
b03752cbe0 Use for loop instead of Array.prototype.fill
Some prior [microbenchmarking](https://jsbench.me/7ol98ws520/1) showed that a 
for loop outperformed `fill` (which is about ~60% slower). This is the same 
approach we use in the latest useMemoCache PR
2022-10-17 16:07:50 -04:00
Jan Kassens
90e189ae82 Add react-forget-runtime
This is a new module that holds: 

- the `useMemoCache` stub (hopefully to be deleted next week) 

- various helpers that can be imported by the compiler, e.g. the dispatcher 
guard `$startLazy` 

- skipped the implementation of `makeReadOnly` for now as there's already 
multiple copies and I wanted to avoid typescript in this file for now to make 
the build easier (i.e. no build)
2022-10-17 13:08:34 -04:00
Lauren Tan
8f42eda432 Provide full path to tsc
I think my previous stack got mangled somehow
2022-10-17 11:33:24 -04:00
Lauren Tan
9e8224cc59 Pass init to E0007 diagnostic
I'm not sure why exactly but previously this diagnostic message was 

unusually slow to typecheck. Lifting the getter for init outside of the 

diagnostic to the callsite seems to fix the hotspot. Probably some 

interaction with string interpolation, or something else. 

Test case: ran `yarn ts:analyze-trace`, hotspot for Diagnostic.ts no 

longer present
2022-10-14 17:36:20 -04:00
Lauren Tan
33735b90fe Add a ts:analyze-trace command
I think all of us have noticed TS slowing to a crawl in the past couple of weeks 
and I was curious what exactly was causing it. This adds a yarn command to 
generate a TS trace and then runs a script on it to identify any hot spots 
during compilation. 

Example trace and hot spot analysis: 

``` yarn run v1.22.19 $ scripts/ts-analyze-trace.sh Hot Spots ├─ Check file 
[35m/users/laurentan/code/react-forget/forget/src/ir/[36mbabel-utils.ts[39m[35m[39m 
(2326ms) │  └─ Check expression from (line 141, char 5) to (line 141, char 19) 
(1760ms) │     └─ Check expression from (line 141, char 14) to (line 141, char 
18) (1760ms) │        └─ Compare types 658 and 607 (1759ms) │           ├─ 
{"id":658,"kind":"GenericInstantiation","name":"NodePath","instantiatedType":350,"typeArguments":[657],"location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@types/babel__traverse[39m[35m/index.d.ts[39m","line":237,"char":1}} 
│           │  ├─ 
{"id":350,"kind":"GenericType","name":"NodePath","typeArguments":[351],"location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@types/babel__traverse[39m[35m/index.d.ts[39m","line":237,"char":1}} 
│           │  │  └─ 
{"id":351,"kind":"TypeParameter","name":"T","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@types/babel__traverse[39m[35m/index.d.ts[39m","line":237,"char":23}} 
│           │  └─ {"id":657,"kind":"Union","count":2,"types":[430,452]} │        
   │     ├─ 
{"id":430,"kind":"Object","name":"Identifier","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":156,"char":1}} 
│           │     └─ 
{"id":452,"kind":"Object","name":"JSXIdentifier","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":982,"char":1}} 
│           └─ 
{"id":607,"kind":"GenericInstantiation","name":"NodePath","instantiatedType":350,"typeArguments":[606],"location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@types/babel__traverse[39m[35m/index.d.ts[39m","line":237,"char":1}} 
│              ├─ 
{"id":350,"kind":"GenericType","name":"NodePath","typeArguments":[351],"location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@types/babel__traverse[39m[35m/index.d.ts[39m","line":237,"char":1}} 
│              │  └─ 
{"id":351,"kind":"TypeParameter","name":"T","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@types/babel__traverse[39m[35m/index.d.ts[39m","line":237,"char":23}} 
│              └─ 
{"id":606,"kind":"AliasedUnion","name":"Node","count":252,"types":[353,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605],"location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":38,"char":1}} 
│                 ├─ 
{"id":353,"kind":"Object","name":"AnyTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":625,"char":1}} 
│                 ├─ 
{"id":355,"kind":"Object","name":"ArgumentPlaceholder","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1035,"char":1}} 
│                 ├─ 
{"id":356,"kind":"Object","name":"ArrayExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":39,"char":1}} 
│                 ├─ 
{"id":357,"kind":"Object","name":"ArrayPattern","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":356,"char":1}} 
│                 ├─ 
{"id":358,"kind":"Object","name":"ArrayTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":628,"char":1}} 
│                 ├─ 
{"id":359,"kind":"Object","name":"ArrowFunctionExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":363,"char":1}} 
│                 ├─ 
{"id":360,"kind":"Object","name":"AssignmentExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":43,"char":1}} 
│                 ├─ 
{"id":361,"kind":"Object","name":"AssignmentPattern","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":349,"char":1}} 
│                 ├─ 
{"id":362,"kind":"Object","name":"AwaitExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":523,"char":1}} 
│                 ├─ 
{"id":363,"kind":"Object","name":"BigIntLiteral","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":530,"char":1}} 
│                 ├─ 
{"id":364,"kind":"Object","name":"BinaryExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":49,"char":1}} 
│                 ├─ 
{"id":365,"kind":"Object","name":"BindExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1038,"char":1}} 
│                 ├─ 
{"id":366,"kind":"Object","name":"BlockStatement","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":67,"char":1}} 
│                 ├─ 
{"id":367,"kind":"Object","name":"BooleanLiteral","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":192,"char":1}} 
│                 ├─ 
{"id":368,"kind":"Object","name":"BooleanLiteralTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":635,"char":1}} 
│                 ├─ 
{"id":369,"kind":"Object","name":"BooleanTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":632,"char":1}} 
│                 ├─ 
{"id":370,"kind":"Object","name":"BreakStatement","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":72,"char":1}} 
│                 ├─ 
{"id":371,"kind":"Object","name":"CallExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":76,"char":1}} 
│                 ├─ 
{"id":372,"kind":"Object","name":"CatchClause","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":84,"char":1}} 
│                 ├─ 
{"id":373,"kind":"Object","name":"ClassAccessorProperty","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":570,"char":1}} 
│                 ├─ 
{"id":374,"kind":"Object","name":"ClassBody","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":374,"char":1}} 
│                 ├─ 
{"id":375,"kind":"Object","name":"ClassDeclaration","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":389,"char":1}} 
│                 ├─ 
{"id":376,"kind":"Object","name":"ClassExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":378,"char":1}} 
│                 ├─ 
{"id":377,"kind":"Object","name":"ClassImplements","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":642,"char":1}} 
│                 ├─ 
{"id":378,"kind":"Object","name":"ClassMethod","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":460,"char":1}} 
│                 ├─ 
{"id":379,"kind":"Object","name":"ClassPrivateMethod","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":598,"char":1}} 
│                 ├─ 
{"id":380,"kind":"Object","name":"ClassPrivateProperty","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":587,"char":1}} 
│                 ├─ 
{"id":381,"kind":"Object","name":"ClassProperty","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":553,"char":1}} 
│                 ├─ 
{"id":382,"kind":"Object","name":"ConditionalExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":89,"char":1}} 
│                 ├─ 
{"id":383,"kind":"Object","name":"ContinueStatement","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":95,"char":1}} 
│                 ├─ 
{"id":384,"kind":"Object","name":"DebuggerStatement","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":99,"char":1}} 
│                 ├─ 
{"id":385,"kind":"Object","name":"DecimalLiteral","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1069,"char":1}} 
│                 ├─ 
{"id":386,"kind":"Object","name":"DeclareClass","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":647,"char":1}} 
│                 ├─ 
{"id":387,"kind":"Object","name":"DeclareExportAllDeclaration","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":704,"char":1}} 
│                 ├─ 
{"id":388,"kind":"Object","name":"DeclareExportDeclaration","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":697,"char":1}} 
│                 ├─ 
{"id":389,"kind":"Object","name":"DeclareFunction","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":656,"char":1}} 
│                 ├─ 
{"id":390,"kind":"Object","name":"DeclareInterface","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":661,"char":1}} 
│                 ├─ 
{"id":391,"kind":"Object","name":"DeclareModule","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":670,"char":1}} 
│                 ├─ 
{"id":392,"kind":"Object","name":"DeclareModuleExports","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":676,"char":1}} 
│                 ├─ 
{"id":393,"kind":"Object","name":"DeclareOpaqueType","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":686,"char":1}} 
│                 ├─ 
{"id":394,"kind":"Object","name":"DeclareTypeAlias","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":680,"char":1}} 
│                 ├─ 
{"id":395,"kind":"Object","name":"DeclareVariable","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":693,"char":1}} 
│                 ├─ 
{"id":396,"kind":"Object","name":"DeclaredPredicate","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":709,"char":1}} 
│                 ├─ 
{"id":397,"kind":"Object","name":"Decorator","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1048,"char":1}} 
│                 ├─ 
{"id":398,"kind":"Object","name":"Directive","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":59,"char":1}} 
│                 ├─ 
{"id":399,"kind":"Object","name":"DirectiveLiteral","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":63,"char":1}} 
│                 ├─ 
{"id":400,"kind":"Object","name":"DoExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1052,"char":1}} 
│                 ├─ 
{"id":401,"kind":"Object","name":"DoWhileStatement","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":102,"char":1}} 
│                 ├─ 
{"id":402,"kind":"Object","name":"EmptyStatement","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":107,"char":1}} 
│                 ├─ 
{"id":403,"kind":"Object","name":"EmptyTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":764,"char":1}} 
│                 ├─ 
{"id":404,"kind":"Object","name":"EnumBooleanBody","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":902,"char":1}} 
│                 ├─ 
{"id":405,"kind":"Object","name":"EnumBooleanMember","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":925,"char":1}} 
│                 ├─ 
{"id":406,"kind":"Object","name":"EnumDeclaration","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":897,"char":1}} 
│                 ├─ 
{"id":407,"kind":"Object","name":"EnumDefaultedMember","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":940,"char":1}} 
│                 ├─ 
{"id":408,"kind":"Object","name":"EnumNumberBody","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":908,"char":1}} 
│                 ├─ 
{"id":409,"kind":"Object","name":"EnumNumberMember","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":930,"char":1}} 
│                 ├─ 
{"id":410,"kind":"Object","name":"EnumStringBody","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":914,"char":1}} 
│                 ├─ 
{"id":411,"kind":"Object","name":"EnumStringMember","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":935,"char":1}} 
│                 ├─ 
{"id":412,"kind":"Object","name":"EnumSymbolBody","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":920,"char":1}} 
│                 ├─ 
{"id":413,"kind":"Object","name":"ExistsTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":713,"char":1}} 
│                 ├─ 
{"id":414,"kind":"Object","name":"ExportAllDeclaration","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":402,"char":1}} 
│                 ├─ 
{"id":415,"kind":"Object","name":"ExportDefaultDeclaration","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":408,"char":1}} 
│                 ├─ 
{"id":416,"kind":"Object","name":"ExportDefaultSpecifier","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1057,"char":1}} 
│                 ├─ 
{"id":417,"kind":"Object","name":"ExportNamedDeclaration","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":413,"char":1}} 
│                 ├─ 
{"id":418,"kind":"Object","name":"ExportNamespaceSpecifier","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":534,"char":1}} 
│                 ├─ 
{"id":419,"kind":"Object","name":"ExportSpecifier","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":421,"char":1}} 
│                 ├─ 
{"id":420,"kind":"Object","name":"ExpressionStatement","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":110,"char":1}} 
│                 ├─ 
{"id":421,"kind":"Object","name":"File","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":114,"char":1}} 
│                 ├─ 
{"id":422,"kind":"Object","name":"ForInStatement","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":120,"char":1}} 
│                 ├─ 
{"id":423,"kind":"Object","name":"ForOfStatement","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":427,"char":1}} 
│                 ├─ 
{"id":424,"kind":"Object","name":"ForStatement","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":126,"char":1}} 
│                 ├─ 
{"id":425,"kind":"Object","name":"FunctionDeclaration","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":133,"char":1}} 
│                 ├─ 
{"id":426,"kind":"Object","name":"FunctionExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":145,"char":1}} 
│                 ├─ 
{"id":427,"kind":"Object","name":"FunctionTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":716,"char":1}} 
│                 ├─ 
{"id":428,"kind":"Object","name":"FunctionTypeParam","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":724,"char":1}} 
│                 ├─ 
{"id":429,"kind":"Object","name":"GenericTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":730,"char":1}} 
│                 ├─ 
{"id":430,"kind":"Object","name":"Identifier","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":156,"char":1}} 
│                 ├─ 
{"id":431,"kind":"Object","name":"IfStatement","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":163,"char":1}} 
│                 ├─ 
{"id":432,"kind":"Object","name":"Import","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":527,"char":1}} 
│                 ├─ 
{"id":433,"kind":"Object","name":"ImportAttribute","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1043,"char":1}} 
│                 ├─ 
{"id":434,"kind":"Object","name":"ImportDeclaration","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":434,"char":1}} 
│                 ├─ 
{"id":435,"kind":"Object","name":"ImportDefaultSpecifier","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":441,"char":1}} 
│                 ├─ 
{"id":436,"kind":"Object","name":"ImportNamespaceSpecifier","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":445,"char":1}} 
│                 ├─ 
{"id":437,"kind":"Object","name":"ImportSpecifier","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":449,"char":1}} 
│                 ├─ 
{"id":438,"kind":"Object","name":"IndexedAccessType","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":944,"char":1}} 
│                 ├─ 
{"id":439,"kind":"Object","name":"InferredPredicate","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":735,"char":1}} 
│                 ├─ 
{"id":440,"kind":"Object","name":"InterfaceDeclaration","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":743,"char":1}} 
│                 ├─ 
{"id":441,"kind":"Object","name":"InterfaceExtends","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":738,"char":1}} 
│                 ├─ 
{"id":442,"kind":"Object","name":"InterfaceTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":752,"char":1}} 
│                 ├─ 
{"id":443,"kind":"Object","name":"InterpreterDirective","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":55,"char":1}} 
│                 ├─ 
{"id":444,"kind":"Object","name":"IntersectionTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":757,"char":1}} 
│                 ├─ 
{"id":445,"kind":"Object","name":"JSXAttribute","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":955,"char":1}} 
│                 ├─ 
{"id":446,"kind":"Object","name":"JSXClosingElement","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":960,"char":1}} 
│                 ├─ 
{"id":447,"kind":"Object","name":"JSXClosingFragment","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1020,"char":1}} 
│                 ├─ 
{"id":448,"kind":"Object","name":"JSXElement","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":964,"char":1}} 
│                 ├─ 
{"id":449,"kind":"Object","name":"JSXEmptyExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":971,"char":1}} 
│                 ├─ 
{"id":450,"kind":"Object","name":"JSXExpressionContainer","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":974,"char":1}} 
│                 ├─ 
{"id":451,"kind":"Object","name":"JSXFragment","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1011,"char":1}} 
│                 ├─ 
{"id":452,"kind":"Object","name":"JSXIdentifier","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":982,"char":1}} 
│                 ├─ 
{"id":453,"kind":"Object","name":"JSXMemberExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":986,"char":1}} 
│                 ├─ 
{"id":454,"kind":"Object","name":"JSXNamespacedName","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":991,"char":1}} 
│                 ├─ 
{"id":455,"kind":"Object","name":"JSXOpeningElement","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":996,"char":1}} 
│                 ├─ 
{"id":456,"kind":"Object","name":"JSXOpeningFragment","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1017,"char":1}} 
│                 ├─ 
{"id":457,"kind":"Object","name":"JSXSpreadAttribute","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1003,"char":1}} 
│                 ├─ 
{"id":458,"kind":"Object","name":"JSXSpreadChild","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":978,"char":1}} 
│                 ├─ 
{"id":459,"kind":"Object","name":"JSXText","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1007,"char":1}} 
│                 ├─ 
{"id":460,"kind":"Object","name":"LabeledStatement","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":169,"char":1}} 
│                 ├─ 
{"id":461,"kind":"Object","name":"LogicalExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":209,"char":1}} 
│                 ├─ 
{"id":462,"kind":"Object","name":"MemberExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":215,"char":1}} 
│                 ├─ 
{"id":463,"kind":"Object","name":"MetaProperty","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":455,"char":1}} 
│                 ├─ 
{"id":464,"kind":"Object","name":"MixedTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":761,"char":1}} 
│                 ├─ 
{"id":465,"kind":"Object","name":"ModuleExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1073,"char":1}} 
│                 ├─ 
{"id":466,"kind":"Object","name":"NewExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":222,"char":1}} 
│                 ├─ 
{"id":467,"kind":"Object","name":"Noop","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1023,"char":1}} 
│                 ├─ 
{"id":468,"kind":"Object","name":"NullLiteral","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":189,"char":1}} 
│                 ├─ 
{"id":469,"kind":"Object","name":"NullLiteralTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":639,"char":1}} 
│                 ├─ 
{"id":470,"kind":"Object","name":"NullableTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":767,"char":1}} 
│                 ├─ 
{"id":471,"kind":"Object","name":"NumberLiteral$1","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":185,"char":1}} 
│                 ├─ 
{"id":472,"kind":"Object","name":"NumberLiteralTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":771,"char":1}} 
│                 ├─ 
{"id":473,"kind":"Object","name":"NumberTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":775,"char":1}} 
│                 ├─ 
{"id":474,"kind":"Object","name":"NumericLiteral","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":178,"char":1}} 
│                 ├─ 
{"id":475,"kind":"Object","name":"ObjectExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":238,"char":1}} 
│                 ├─ 
{"id":476,"kind":"Object","name":"ObjectMethod","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":242,"char":1}} 
│                 ├─ 
{"id":477,"kind":"Object","name":"ObjectPattern","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":479,"char":1}} 
│                 ├─ 
{"id":478,"kind":"Object","name":"ObjectProperty","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":255,"char":1}} 
│                 ├─ 
{"id":479,"kind":"Object","name":"ObjectTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":778,"char":1}} 
│                 ├─ 
{"id":480,"kind":"Object","name":"ObjectTypeCallProperty","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":795,"char":1}} 
│                 ├─ 
{"id":481,"kind":"Object","name":"ObjectTypeIndexer","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":800,"char":1}} 
│                 ├─ 
{"id":482,"kind":"Object","name":"ObjectTypeInternalSlot","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":787,"char":1}} 
│                 ├─ 
{"id":483,"kind":"Object","name":"ObjectTypeProperty","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":808,"char":1}} 
│                 ├─ 
{"id":484,"kind":"Object","name":"ObjectTypeSpreadProperty","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":819,"char":1}} 
│                 ├─ 
{"id":485,"kind":"Object","name":"OpaqueType","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":823,"char":1}} 
│                 ├─ 
{"id":486,"kind":"Object","name":"OptionalCallExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":545,"char":1}} 
│                 ├─ 
{"id":487,"kind":"Object","name":"OptionalIndexedAccessType","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":949,"char":1}} 
│                 ├─ 
{"id":488,"kind":"Object","name":"OptionalMemberExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":538,"char":1}} 
│                 ├─ 
{"id":489,"kind":"Object","name":"ParenthesizedExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":288,"char":1}} 
│                 ├─ 
{"id":490,"kind":"Object","name":"PipelineBareFunction","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1084,"char":1}} 
│                 ├─ 
{"id":491,"kind":"Object","name":"PipelinePrimaryTopicReference","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1088,"char":1}} 
│                 ├─ 
{"id":492,"kind":"Object","name":"PipelineTopicExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1080,"char":1}} 
│                 ├─ 
{"id":493,"kind":"Object","name":"Placeholder","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1026,"char":1}} 
│                 ├─ 
{"id":494,"kind":"Object","name":"PrivateName","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":617,"char":1}} 
│                 ├─ 
{"id":495,"kind":"Object","name":"Program","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":230,"char":1}} 
│                 ├─ 
{"id":496,"kind":"Object","name":"QualifiedTypeIdentifier","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":830,"char":1}} 
│                 ├─ 
{"id":497,"kind":"Object","name":"RecordExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1061,"char":1}} 
│                 ├─ 
{"id":498,"kind":"Object","name":"RegExpLiteral","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":196,"char":1}} 
│                 ├─ 
{"id":499,"kind":"Object","name":"RegexLiteral$1","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":204,"char":1}} 
│                 ├─ 
{"id":500,"kind":"Object","name":"RestElement","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":263,"char":1}} 
│                 ├─ 
{"id":501,"kind":"Object","name":"RestProperty$1","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":273,"char":1}} 
│                 ├─ 
{"id":502,"kind":"Object","name":"ReturnStatement","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":280,"char":1}} 
│                 ├─ 
{"id":503,"kind":"Object","name":"SequenceExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":284,"char":1}} 
│                 ├─ 
{"id":504,"kind":"Object","name":"SpreadElement","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":485,"char":1}} 
│                 ├─ 
{"id":505,"kind":"Object","name":"SpreadProperty$1","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":492,"char":1}} 
│                 ├─ 
{"id":506,"kind":"Object","name":"StaticBlock","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":621,"char":1}} 
│                 ├─ 
{"id":507,"kind":"Object","name":"StringLiteral","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":174,"char":1}} 
│                 ├─ 
{"id":508,"kind":"Object","name":"StringLiteralTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":835,"char":1}} 
│                 ├─ 
{"id":509,"kind":"Object","name":"StringTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":839,"char":1}} 
│                 ├─ 
{"id":510,"kind":"Object","name":"Super","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":496,"char":1}} 
│                 ├─ 
{"id":511,"kind":"Object","name":"SwitchCase","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":292,"char":1}} 
│                 ├─ 
{"id":512,"kind":"Object","name":"SwitchStatement","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":297,"char":1}} 
│                 ├─ 
{"id":513,"kind":"Object","name":"SymbolTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":842,"char":1}} 
│                 ├─ 
{"id":514,"kind":"Object","name":"TSAnyKeyword","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1171,"char":1}} 
│                 ├─ 
{"id":515,"kind":"Object","name":"TSArrayType","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1246,"char":1}} 
│                 ├─ 
{"id":516,"kind":"Object","name":"TSAsExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1342,"char":1}} 
│                 ├─ 
{"id":517,"kind":"Object","name":"TSBigIntKeyword","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1177,"char":1}} 
│                 ├─ 
{"id":518,"kind":"Object","name":"TSBooleanKeyword","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1174,"char":1}} 
│                 ├─ 
{"id":519,"kind":"Object","name":"TSCallSignatureDeclaration","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1132,"char":1}} 
│                 ├─ 
{"id":520,"kind":"Object","name":"TSConditionalType","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1276,"char":1}} 
│                 ├─ 
{"id":521,"kind":"Object","name":"TSConstructSignatureDeclaration","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1138,"char":1}} 
│                 ├─ 
{"id":522,"kind":"Object","name":"TSConstructorType","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1219,"char":1}} 
│                 ├─ 
{"id":523,"kind":"Object","name":"TSDeclareFunction","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1099,"char":1}} 
│                 ├─ 
{"id":524,"kind":"Object","name":"TSDeclareMethod","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1109,"char":1}} 
│                 ├─ 
{"id":525,"kind":"Object","name":"TSEnumDeclaration","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1352,"char":1}} 
│                 ├─ 
{"id":526,"kind":"Object","name":"TSEnumMember","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1360,"char":1}} 
│                 ├─ 
{"id":527,"kind":"Object","name":"TSExportAssignment","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1397,"char":1}} 
│                 ├─ 
{"id":528,"kind":"Object","name":"TSExpressionWithTypeArguments","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1313,"char":1}} 
│                 ├─ 
{"id":529,"kind":"Object","name":"TSExternalModuleReference","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1389,"char":1}} 
│                 ├─ 
{"id":530,"kind":"Object","name":"TSFunctionType","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1213,"char":1}} 
│                 ├─ 
{"id":531,"kind":"Object","name":"TSImportEqualsDeclaration","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1382,"char":1}} 
│                 ├─ 
{"id":532,"kind":"Object","name":"TSImportType","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1376,"char":1}} 
│                 ├─ 
{"id":533,"kind":"Object","name":"TSIndexSignature","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1164,"char":1}} 
│                 ├─ 
{"id":534,"kind":"Object","name":"TSIndexedAccessType","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1296,"char":1}} 
│                 ├─ 
{"id":535,"kind":"Object","name":"TSInferType","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1283,"char":1}} 
│                 ├─ 
{"id":536,"kind":"Object","name":"TSInstantiationExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1337,"char":1}} 
│                 ├─ 
{"id":537,"kind":"Object","name":"TSInterfaceBody","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1326,"char":1}} 
│                 ├─ 
{"id":538,"kind":"Object","name":"TSInterfaceDeclaration","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1318,"char":1}} 
│                 ├─ 
{"id":539,"kind":"Object","name":"TSIntersectionType","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1272,"char":1}} 
│                 ├─ 
{"id":540,"kind":"Object","name":"TSIntrinsicKeyword","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1180,"char":1}} 
│                 ├─ 
{"id":541,"kind":"Object","name":"TSLiteralType","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1309,"char":1}} 
│                 ├─ 
{"id":542,"kind":"Object","name":"TSMappedType","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1301,"char":1}} 
│                 ├─ 
{"id":543,"kind":"Object","name":"TSMethodSignature","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1154,"char":1}} 
│                 ├─ 
{"id":544,"kind":"Object","name":"TSModuleBlock","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1372,"char":1}} 
│                 ├─ 
{"id":545,"kind":"Object","name":"TSModuleDeclaration","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1365,"char":1}} 
│                 ├─ 
{"id":546,"kind":"Object","name":"TSNamedTupleMember","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1262,"char":1}} 
│                 ├─ 
{"id":547,"kind":"Object","name":"TSNamespaceExportDeclaration","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1401,"char":1}} 
│                 ├─ 
{"id":548,"kind":"Object","name":"TSNeverKeyword","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1183,"char":1}} 
│                 ├─ 
{"id":549,"kind":"Object","name":"TSNonNullExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1393,"char":1}} 
│                 ├─ 
{"id":550,"kind":"Object","name":"TSNullKeyword","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1186,"char":1}} 
│                 ├─ 
{"id":551,"kind":"Object","name":"TSNumberKeyword","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1189,"char":1}} 
│                 ├─ 
{"id":552,"kind":"Object","name":"TSObjectKeyword","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1192,"char":1}} 
│                 ├─ 
{"id":553,"kind":"Object","name":"TSOptionalType","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1254,"char":1}} 
│                 ├─ 
{"id":554,"kind":"Object","name":"TSParameterProperty","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1091,"char":1}} 
│                 ├─ 
{"id":555,"kind":"Object","name":"TSParenthesizedType","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1287,"char":1}} 
│                 ├─ 
{"id":556,"kind":"Object","name":"TSPropertySignature","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1144,"char":1}} 
│                 ├─ 
{"id":557,"kind":"Object","name":"TSQualifiedName","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1127,"char":1}} 
│                 ├─ 
{"id":558,"kind":"Object","name":"TSRestType","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1258,"char":1}} 
│                 ├─ 
{"id":559,"kind":"Object","name":"TSStringKeyword","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1195,"char":1}} 
│                 ├─ 
{"id":560,"kind":"Object","name":"TSSymbolKeyword","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1198,"char":1}} 
│                 ├─ 
{"id":561,"kind":"Object","name":"TSThisType","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1210,"char":1}} 
│                 ├─ 
{"id":562,"kind":"Object","name":"TSTupleType","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1250,"char":1}} 
│                 ├─ 
{"id":563,"kind":"Object","name":"TSTypeAliasDeclaration","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1330,"char":1}} 
│                 ├─ 
{"id":564,"kind":"Object","name":"TSTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1405,"char":1}} 
│                 ├─ 
{"id":565,"kind":"Object","name":"TSTypeAssertion","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1347,"char":1}} 
│                 ├─ 
{"id":566,"kind":"Object","name":"TSTypeLiteral","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1242,"char":1}} 
│                 ├─ 
{"id":567,"kind":"Object","name":"TSTypeOperator","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1291,"char":1}} 
│                 ├─ 
{"id":568,"kind":"Object","name":"TSTypeParameter","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1417,"char":1}} 
│                 ├─ 
{"id":569,"kind":"Object","name":"TSTypeParameterDeclaration","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1413,"char":1}} 
│                 ├─ 
{"id":570,"kind":"Object","name":"TSTypeParameterInstantiation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1409,"char":1}} 
│                 ├─ 
{"id":571,"kind":"Object","name":"TSTypePredicate","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1231,"char":1}} 
│                 ├─ 
{"id":572,"kind":"Object","name":"TSTypeQuery","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1237,"char":1}} 
│                 ├─ 
{"id":573,"kind":"Object","name":"TSTypeReference","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1226,"char":1}} 
│                 ├─ 
{"id":574,"kind":"Object","name":"TSUndefinedKeyword","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1201,"char":1}} 
│                 ├─ 
{"id":575,"kind":"Object","name":"TSUnionType","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1268,"char":1}} 
│                 ├─ 
{"id":576,"kind":"Object","name":"TSUnknownKeyword","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1204,"char":1}} 
│                 ├─ 
{"id":577,"kind":"Object","name":"TSVoidKeyword","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1207,"char":1}} 
│                 ├─ 
{"id":578,"kind":"Object","name":"TaggedTemplateExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":499,"char":1}} 
│                 ├─ 
{"id":579,"kind":"Object","name":"TemplateElement","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":505,"char":1}} 
│                 ├─ 
{"id":580,"kind":"Object","name":"TemplateLiteral","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":513,"char":1}} 
│                 ├─ 
{"id":581,"kind":"Object","name":"ThisExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":302,"char":1}} 
│                 ├─ 
{"id":582,"kind":"Object","name":"ThisTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":845,"char":1}} 
│                 ├─ 
{"id":583,"kind":"Object","name":"ThrowStatement","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":305,"char":1}} 
│                 ├─ 
{"id":584,"kind":"Object","name":"TopicReference","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1077,"char":1}} 
│                 ├─ 
{"id":585,"kind":"Object","name":"TryStatement","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":309,"char":1}} 
│                 ├─ 
{"id":586,"kind":"Object","name":"TupleExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1065,"char":1}} 
│                 ├─ 
{"id":587,"kind":"Object","name":"TupleTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":848,"char":1}} 
│                 ├─ 
{"id":588,"kind":"Object","name":"TypeAlias","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":856,"char":1}} 
│                 ├─ 
{"id":589,"kind":"Object","name":"TypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":862,"char":1}} 
│                 ├─ 
{"id":590,"kind":"Object","name":"TypeCastExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":866,"char":1}} 
│                 ├─ 
{"id":591,"kind":"Object","name":"TypeParameter","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":871,"char":1}} 
│                 ├─ 
{"id":592,"kind":"Object","name":"TypeParameterDeclaration","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":878,"char":1}} 
│                 ├─ 
{"id":593,"kind":"Object","name":"TypeParameterInstantiation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":882,"char":1}} 
│                 ├─ 
{"id":594,"kind":"Object","name":"TypeofTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":852,"char":1}} 
│                 ├─ 
{"id":595,"kind":"Object","name":"UnaryExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":315,"char":1}} 
│                 ├─ 
{"id":596,"kind":"Object","name":"UnionTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":886,"char":1}} 
│                 ├─ 
{"id":597,"kind":"Object","name":"UpdateExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":321,"char":1}} 
│                 ├─ 
{"id":598,"kind":"Object","name":"V8IntrinsicIdentifier","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":1031,"char":1}} 
│                 ├─ 
{"id":599,"kind":"Object","name":"VariableDeclaration","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":327,"char":1}} 
│                 ├─ 
{"id":600,"kind":"Object","name":"VariableDeclarator","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":333,"char":1}} 
│                 ├─ 
{"id":601,"kind":"Object","name":"Variance","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":890,"char":1}} 
│                 ├─ 
{"id":602,"kind":"Object","name":"VoidTypeAnnotation","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":894,"char":1}} 
│                 ├─ 
{"id":603,"kind":"Object","name":"WhileStatement","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":339,"char":1}} 
│                 ├─ 
{"id":604,"kind":"Object","name":"WithStatement","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":344,"char":1}} 
│                 └─ 
{"id":605,"kind":"Object","name":"YieldExpression","location":{"path":"[35m/users/laurentan/code/react-forget/forget/node_modules/[36m@babel/types[39m[35m/lib/index.d.ts[39m","line":518,"char":1}} 
└─ Check file 
[35m/users/laurentan/code/react-forget/forget/src/[36mdiagnostic.ts[39m[35m[39m 
(809ms)    └─ Check expression from (line 222, char 14) to (line 228, char 8) 
(698ms)       └─ Check expression from (line 227, char 21) to (line 227, char 
74) (697ms)          └─ Check expression from (line 227, char 29) to (line 227, 
char 61) (697ms)             └─ Check expression from (line 227, char 39) to 
(line 227, char 60) (697ms) 

No duplicate packages found Done in 16.27s. ```
2022-10-14 17:36:19 -04:00
Lauren Tan
e939cacf97 Don't console.error in dev
Previously the PassManager would console.error if an unexpected error was 
thrown, to help with debugging jest. However because we now capture all 
invariants in compiler passes as bailouts, these are already captured in fixture 
tests. 

Additionally, we also already console.error if we find an unexpected bailout in 
a fixture test. So this is purely redundant and removing reduces some noise when 
running tests.
2022-10-14 12:10:58 -04:00
Lauren Tan
101ee3a440 Allow capitalized function identifiers to be allowlisted
The current allowlist for capitalized function identifiers only allows stdlib JS 
modules. This PR introduces a new compiler option to allow passing in a set of 
allowed capitalized user function identifiers. It's a hack (in the absence of a 
type system) to let us check that capitalized function calls are only possible 
for identifiers that aren't bound to a React component. 

I considered adding a mechanism in fixtures to configure compiler options 
per-fixture, but opted to keep it simple for now and special case a 
`ReactForgetSecretInternals` identifier as one allowed user function in 
transform tests.
2022-10-14 12:10:55 -04:00
Lauren Tan
58100e3d60 Use CompilerError invariant
The CompilerError module's `invariant` is wired up to our bailout system so in 
compiler passes should be preferred to the raw `invariant` module. 

We should probably add an internal eslint rule to suggest using CompilerError if 
the file is in one of the compiler pass directories.
2022-10-14 12:10:53 -04:00
Jan Kassens
b1006e12cd Invariant for use before definition of reactive values (#662) 2022-10-13 16:17:39 -04:00
Joseph Savona
0000ab6ca4 [new-arch] HIR cleanup per discussion
* Renames `Capability` to `Effect` and clarifies the kinds as Freeze, Read, and 
Mutate. The real intent of what we're inferring/representing is "what effect 
does this reference to the value have on its value". Ie freeze freezes the 
value, mutate mutates it. 

* Consolidates Capability and EffectKind into Effect 

* Renames some properties on Place for clarity 

* Adds `value: ValueKind` to Place, which indicates the (merged) kind of the 
value at that place at that point in the program. 

* Changes HIR printing to show the effect and the value kind 

* Simplifies some inference logic
2022-10-13 11:23:14 -07:00
mofeiZ
73059d9a4f [Playground][Nit] Add delete + overtype for autoclosing brackets
Fix for papercut: playground should now be smart about autoclosing brackets. 

Before: 


https://user-images.githubusercontent.com/34200447/195653419-0eb529e4-0aed-4894-9db6-bfe1554aaded.mov 

Now: 


https://user-images.githubusercontent.com/34200447/195653738-07abed36-d9e8-4842-bac5-9e84b72c0704.mov
2022-10-13 13:16:56 -04:00
Joseph Savona
144e856264 Cleanup reverse postordering
Splits out the reverse postorder logic from `shrink` into a separate method.
2022-10-13 07:58:06 -07:00
Sathya Gunasekaran
b01fbfa7fb Change function parameters to type Place
So that we can infer a Capability of Freeze for components
2022-10-13 11:29:58 +01:00
Joseph Savona
5149ce0fbb [new-arch] Store HIR blocks in reverse postorder
* Changes HIR to store blocks in reverse postorder, which allows forward data 
flow analysis to iterate the blocks in order and (in the absence of loops) see 
all predecessors before visiting a successor. 

* Updates reference kind inference to exploit this ordering 

Note that the approach of modifying the ordering in `mapTerminalSuccessors()` 
feels gross, i'd like to split this up a bit.
2022-10-12 16:11:15 -07:00
Joseph Savona
3d4130140a [new-arch] Scaffolding and fixtures for mutability lifetime inference
Scaffolds out mutable lifetime inference and the potential two-pass approach, 
with motivating examples. Also adds some fixtures that collectively demonstrate 
a bunch of cases of aliasing: 

* direct assignment `a = b` 

* property assignment `a.b = b` 

* array literals `a = [b]` 

* object literals `a = {b}` 

* mutable arguments to the same call `foo(mut a, mut b)` 

* return values aliasing arguments `a = foo(mut b)` 

* aliasing that occurs only after multiple loop iterations 

All of these fixtures use an empty `if (varName) {}` as a way to check that an 
otherwise readonly usage of a variable is correctly inferred as mutable.
2022-10-12 10:54:09 -07:00
Joseph Savona
94adcf91b1 [new-arch] Cleanup unused analysis (#651) 2022-10-11 12:00:45 -07:00
Joseph Savona
beb6e1431c [new-arch] Improve reference-kind analysis
This implements an alternative approach to reference kind inference in the new 
architecture based on feedback. Here, we track an environment that maps 
top-level identifiers (IdentifierId) to the "kind" of value stored: immutable, 
mutable, frozen, or maybe-frozen. We then do a forward data flow analysis 
updating this environment based on the semantics of each instruction combined 
with the types of values present. For example a reference of a value in a 
"mutable" position is inferred as readonly if the value is known to be frozen or 
immutable. Similarly, a usage of a reference in a "freeze" position is inferred 
as a freeze if the value is not yet definitively frozen, and inferred as 
readonly if the value is already frozen. 

When multiple control paths converge we merge the previous and new incoming 
environments, and only reprocess the block if the environment changed relative 
to the previous value. This has some noticeable benefits over the previous 
version: 

* We now infer precisely where `makeReadOnly()` calls need to be inserted, aka 
points where a value needs to be frozen may not yet be frozen. 

* We track immutable values and can infer their usage as readonly rather than 
mutable. 

* The system handles aliasing by representing values as distinct from variables, 
so that we can handle situations such as: 

```javascript 

const a = [];  // env: {a: value0; value0: mutable} 

const b = a;   // env: {a: value0, b: value0; value0: mutable} 

freeze(a);     // env: {a: value0, b: value0; value0: frozen} 

mayMutate(b);  // ordinarily inferred as a mutable reference, but we know its 
readonly 

```
2022-10-11 11:32:49 -07:00
Jan Kassens
7f17dd84d1 Import useMemoCache from unstable name
I didn't make this an option as it's unclear we'll really need this. We can 
always add an option later, I think. 

This is the name that's available on facebook.com at the moment.
2022-10-11 13:16:01 -04:00
Jan Kassens
e575e22925 Add hash of Babel plugin to Jest transform
Jest caches based on the hash of the babel config. This caused us to not break 
that cache when the we made changes to the plugin. 

Fixes #643
2022-10-06 16:59:12 -04:00
Xuan Huang (黄玄)
b605fc1ab3 [BE] Even more permissive ref annotation comment
Allow `readOnly` and `writable` or even `WriTaBlE`. 

commit-id:f6f77fd8
2022-10-05 18:42:16 -04:00
Xuan Huang (黄玄)
a574d15985 [Fix] Control Dep Should Only Add To Defs
Control dep should only affect how things are invalidated, which are modeled as 
defs including declarations, writable uses to variables and expressions. 

Closes #633 

commit-id:41bd6fe5
2022-10-05 18:42:11 -04:00
Xuan Huang (黄玄)
19b08e67ad [Bailout] When Inputs Detected In DepGraph Cycle
Inputs occured in depGraph cycle is dangenrous and should be treated as an 
invariant since Forget _may_ generate broken code in this case, despite that 
technically this is a stricter then what we needed for the particular case of 
#633 and #634 and there   could be case that this is safe (like many `cfg-`   
tests that I have to mark as `bailout.`) 

I expect the next diff will fix them though. 

commit-id:0b13ed02
2022-10-05 18:42:10 -04:00
Joseph Savona
cbbfba27b4 [new-arch][easy] Handle declarations (let/const) through codegen
Distinguishes between `LValue` and `Place`. For the most part this is the same 
data structure (LValue composes Place), but it's helpful to distinguish them 
since LValue has other properties such as the kind of declaration. The 
representations may diverge more in the future. 

This change lets us correctly emit code for variable declarations: previously we 
didn't emit `let` or `const`.
2022-10-06 08:56:20 -07:00
Joseph Savona
90ab346505 [new-arch][easy] Codegen for switch statements
Flushes out basic codegen for switch statements. This is more indication that we 
can recover nearly the original source even for complex control-flow, given the 
right IR design.
2022-10-05 19:45:05 -07:00
Joseph Savona
f5f9143381 [new-architecture] Reassignment-safe frozenness inference
NOTE: this improves the equivalent of "ref kind inference" in the new 
architecture. I'd appreciate review here on the algorithm in particular, but in 
general my plan is to try to implement this on the current architecture. 

The previous InferMutability reference kind inference didn't properly handle 
capturing or reassignment combined with control flow. This is a new version 
(i'll clean up to delete InferMutability entirely) that is less ambitious but 
fully accurate (i hope, hence WIP): 

* Annotates all references of frozen variables as frozen. This includes 
following reassignment, so if you do `const x = props.x; foo(x);` we know that 
`x` is frozen because it derived from a frozen value. This even works 
conditionally, so if `x` is conditionally assigned to some value derived from eg 
props, and you later use `x`, that will be marked as frozen even if it could 
have other values at runtime (since it must conservatively assume a frozen value 
flowed in at runtime). 

* Annotates references that may mutate as mutable. 

* Annotates references that are not frozen, but not mutated _at this reference 
site_, as readonly. It's possible that a readonly usage is followed by a mutable 
usage, since we don't yet know the "lifetime" of the mutability. 

The notable difference from the previous attempt is that we do not attempt to 
find the point at which a formerly-mutable values becomes readonly (and 
therefore eligible for caching). That requires pointer analysis, let's discuss 
offline.
2022-10-05 13:56:13 -07:00
Xuan Huang (黄玄)
e877f89dda Allow comment annotation to be spaced
I.e. `/* readonly */` is fine too 

commit-id:1437395a
2022-10-05 13:30:25 -04:00
Xuan Huang (黄玄)
83f3999a2e Support var declaration hoisting
Var declarations were treated identical as other declarations which cause code 
relying on them getting hoisted now triggers runtime exception on TDZ. 

This diff fixed that by generating `var` for `var` so they can be hoisted as 
usual. 

commit-id:00ab02f6
2022-10-05 13:23:37 -04:00
Mofei Zhang
896ef251b4 patch makeReadOnly into playground 2022-10-03 14:37:24 -04:00
Mofei Zhang
4e0639c2dc Add makeReadOnly codegen into the compiler 2022-10-03 14:37:23 -04:00
Mofei Zhang
72775fba10 makeReadOnly implementation
Added utility package to track mutations to objects marked as "read-only".
2022-09-29 14:17:38 -04:00
Joseph Savona
3158146989 POC of new expression-oriented inference model for memoization within control-flow (#589)
* [hir] Core data types and lowering for new model

* Handle more expressions, including using babel for binding resolution

* test setup with pretty printing of ir

* Basic codegen and improved pretty printing

* avoid else block when if has no fallthrough

* emit function declarations with mapped name/params

* start of scope analysis

* saving state pre-run

* add slightly more complex example and flush out lowering/printing (jsx, new, variables)

* Various improvements:
* Convert logical expressions (|| and &&) to control flow, accounting
  for lazy evaluation semantics.
* Handle expression statements
* Improve printing of HIR for unsupported node kinds
* Handle more cases of JSX by falling by to OtherStatement to wrap
  subtrees at coarse granularity.

* improve HIR printing, lowering of expression statements

* handle object expression printing

* improve IR model for values/places along w codegen

* more test cases

* start of mutability inference

* passable but still incorrect mutability inference

* improved mutability inference, should cover most cases now

* visualization of reference graph

* correctly flow mutability backwards (have to actually set the capability)

* separate visualization in output

* consolidate on frozen/readonly/mutable capabilities

* cleanup

* conditional reassignment test (not quite working)

* hack to output svg files for debugging

* handle conditional reassignment

* improve capture analysis

* treat jsx as (interior) mutable; handle memberexpression lvalues

* lots of comments; hook return is frozen

* update main comment

* inference for switch, which reveals a bug

* fix yarn.lock
2024-03-25 10:39:47 +00:00
Xuan Huang (黄玄)
a33c0b897c Initial commit 2024-03-25 10:39:47 +00:00
4398 changed files with 392601 additions and 15798 deletions

View File

@@ -2,7 +2,7 @@ version: 2.1
aliases:
- &docker
- image: cimg/openjdk:18.0-node
- image: cimg/node:18.20.1-browsers
- &environment
TZ: /usr/share/zoneinfo/America/Los_Angeles
@@ -211,6 +211,8 @@ jobs:
- setup_node_modules
- run:
command: node ./scripts/tasks/danger
- store_artifacts:
path: sizebot-message.md
build_devtools_and_process_artifacts:
docker: *docker
@@ -226,6 +228,13 @@ jobs:
command: ./scripts/circleci/pack_and_store_devtools_artifacts.sh
- store_artifacts:
path: ./build/devtools.tgz
# Simplifies getting the extension for local testing
- store_artifacts:
path: ./build/devtools/chrome-extension.zip
destination: react-devtools-chrome-extension.zip
- store_artifacts:
path: ./build/devtools/firefox-extension.zip
destination: react-devtools-firefox-extension.zip
run_devtools_e2e_tests:
docker: *docker
@@ -246,8 +255,7 @@ jobs:
command: ./scripts/circleci/run_devtools_e2e_tests.js
run_fixtures_flight_tests:
docker:
- image: cimg/openjdk:20.0-node
docker: *docker
environment: *environment
steps:
- checkout
@@ -499,6 +507,10 @@ workflows:
- "-r=www-modern --env=production --variant=false"
- "-r=www-modern --env=development --variant=true"
- "-r=www-modern --env=production --variant=true"
- "-r=xplat --env=development --variant=false"
- "-r=xplat --env=development --variant=true"
- "-r=xplat --env=production --variant=false"
- "-r=xplat --env=production --variant=true"
# TODO: Test more persistent configurations?
- '-r=stable --env=development --persistent'
@@ -544,6 +556,12 @@ workflows:
# - "-r=www-modern --env=development --variant=true"
# - "-r=www-modern --env=production --variant=true"
# TODO: Update test config to support xplat build tests
# - "-r=xplat --env=development --variant=false"
# - "-r=xplat --env=development --variant=true"
# - "-r=xplat --env=production --variant=false"
# - "-r=xplat --env=production --variant=true"
# TODO: Test more persistent configurations?
- download_base_build_for_sizebot:
filters:
@@ -638,7 +656,7 @@ workflows:
# because this used to be called the "next" channel and some
# downstream consumers might still expect that tag. We can remove this
# after some time has elapsed and the change has been communicated.
dist_tag: "canary,next"
dist_tag: "canary,next,rc"
- publish_prerelease:
name: Publish to Experimental channel
requires:
@@ -667,7 +685,7 @@ workflows:
name: Publish to Canary channel
commit_sha: << pipeline.git.revision >>
release_channel: stable
dist_tag: "canary,next"
dist_tag: "canary,next,rc"
- publish_prerelease:
name: Publish to Experimental channel
requires:

View File

@@ -13,11 +13,15 @@ scripts/bench/benchmarks/**/*.js
# React repository clone
scripts/bench/remote-repo/
# Compiler uses its own lint setup
compiler/
packages/react-devtools-core/dist
packages/react-devtools-extensions/chrome/build
packages/react-devtools-extensions/firefox/build
packages/react-devtools-extensions/shared/build
packages/react-devtools-extensions/src/ErrorTesterCompiled.js
packages/react-devtools-fusebox/dist
packages/react-devtools-inline/dist
packages/react-devtools-shared/src/hooks/__tests__/__source__/__compiled__/
packages/react-devtools-shared/src/hooks/__tests__/__source__/__untransformed__/

View File

@@ -486,6 +486,8 @@ module.exports = {
$ReadOnlyArray: 'readonly',
$ArrayBufferView: 'readonly',
$Shape: 'readonly',
CallSite: 'readonly',
ConsoleTask: 'readonly', // TOOD: Figure out what the official name of this will be.
ReturnType: 'readonly',
AnimationFrameID: 'readonly',
// For Flow type annotation. Only `BigInt` is valid at runtime.
@@ -499,7 +501,12 @@ module.exports = {
DOMHighResTimeStamp: 'readonly',
EventListener: 'readonly',
Iterable: 'readonly',
AsyncIterable: 'readonly',
$AsyncIterable: 'readonly',
$AsyncIterator: 'readonly',
Iterator: 'readonly',
AsyncIterator: 'readonly',
IteratorResult: 'readonly',
JSONValue: 'readonly',
JSResourceReference: 'readonly',
MouseEventHandler: 'readonly',
@@ -520,6 +527,7 @@ module.exports = {
React$Portal: 'readonly',
React$Ref: 'readonly',
ReadableStreamController: 'readonly',
ReadableStreamReader: 'readonly',
RequestInfo: 'readonly',
RequestOptions: 'readonly',
StoreAsGlobal: 'readonly',
@@ -539,7 +547,6 @@ module.exports = {
__EXTENSION__: 'readonly',
__PROFILE__: 'readonly',
__TEST__: 'readonly',
__UMD__: 'readonly',
__VARIANT__: 'readonly',
__unmockReact: 'readonly',
gate: 'readonly',

1
.git-blame-ignore-revs Normal file
View File

@@ -0,0 +1 @@
c998bb1ed4b3285398c9c7797135d3f060243c6a

18
.github/ISSUE_TEMPLATE/19.md vendored Normal file
View File

@@ -0,0 +1,18 @@
---
name: "⚛React 19 beta issue"
about: Report a issue with React 19 beta.
title: '[React 19]'
labels: 'React 19'
---
## Summary
<!--
Please provide a CodeSandbox (https://codesandbox.io/s/new), a link to a
repository on GitHub, or provide a minimal code example that reproduces the
problem. You may provide a screenshot of the application if you think it is
relevant to your bug report. Here are some tips for providing a minimal
example: https://stackoverflow.com/help/mcve.
-->

View File

@@ -0,0 +1,57 @@
name: "⚛️ ✨ Compiler bug report"
description: "Report a problem with React Compiler. Please provide enough information that we can reproduce the problem."
title: "[Compiler Bug]: "
labels: ["Component: Optimizing Compiler", "Type: Bug", "Status: Unconfirmed"]
body:
- type: checkboxes
attributes:
label: What kind of issue is this?
description: |
Please indicate if this issue affects the following tools provided by React Compiler.
options:
- label: React Compiler core (the JS output is incorrect, or your app works incorrectly after optimization)
- label: babel-plugin-react-compiler (build issue installing or using the Babel plugin)
- label: eslint-plugin-react-compiler (build issue installing or using the eslint plugin)
- label: react-compiler-healthcheck (build issue installing or using the healthcheck script)
- type: input
attributes:
label: Link to repro
description: |
Please provide a repro by either sharing a [Playground link](https://playground.react.dev), or a public GitHub repo so the React team can reproduce the error being reported. Please do not share localhost links!
placeholder: |
e.g. public GitHub repo, or Playground link
validations:
required: true
- type: textarea
attributes:
label: Repro steps
description: |
What were you doing when the bug happened? Detailed information helps maintainers reproduce and fix bugs.
Issues filed without repro steps will be closed.
placeholder: |
Example bug report:
1. Log in with username/password
2. Click "Messages" on the left menu
3. Open any message in the list
validations:
required: true
- type: dropdown
attributes:
label: How often does this bug happen?
description: |
Following the repro steps above, how easily are you able to reproduce this bug?
options:
- Every time
- Often
- Sometimes
- Only once
validations:
required: true
- type: input
attributes:
label: What version of React are you using?
description: |
Please provide your React version in the app where this issue occurred.
validations:
required: true

View File

@@ -1,3 +1,4 @@
blank_issues_enabled: false
contact_links:
- name: 📃 Documentation Issue
url: https://github.com/reactjs/react.dev/issues/new/choose

View File

@@ -10,7 +10,36 @@ jobs:
outputs:
www_branch_count: ${{ steps.check_branches.outputs.www_branch_count }}
fbsource_branch_count: ${{ steps.check_branches.outputs.fbsource_branch_count }}
last_version_classic: ${{ steps.get_last_version_www.outputs.last_version_classic }}
last_version_modern: ${{ steps.get_last_version_www.outputs.last_version_modern }}
last_version_rn: ${{ steps.get_last_version_rn.outputs.last_version_rn }}
current_version_classic: ${{ steps.get_current_version.outputs.current_version_classic }}
current_version_modern: ${{ steps.get_current_version.outputs.current_version_modern }}
current_version_rn: ${{ steps.get_current_version.outputs.current_version_rn }}
steps:
- uses: actions/checkout@v4
with:
ref: builds/facebook-www
- name: "Get last version string for www"
id: get_last_version_www
run: |
# Empty checks only needed for backwards compatibility,can remove later.
VERSION_CLASSIC=$( [ -f ./compiled/facebook-www/VERSION_CLASSIC ] && cat ./compiled/facebook-www/VERSION_CLASSIC || echo '' )
VERSION_MODERN=$( [ -f ./compiled/facebook-www/VERSION_MODERN ] && cat ./compiled/facebook-www/VERSION_MODERN || echo '' )
echo "Last classic version is $VERSION_CLASSIC"
echo "Last modern version is $VERSION_MODERN"
echo "last_version_classic=$VERSION_CLASSIC" >> "$GITHUB_OUTPUT"
echo "last_version_modern=$VERSION_MODERN" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@v4
with:
ref: builds/facebook-fbsource
- name: "Get last version string for rn"
id: get_last_version_rn
run: |
# Empty checks only needed for backwards compatibility,can remove later.
VERSION_NATIVE_FB=$( [ -f ./compiled-rn/VERSION_NATIVE_FB ] && cat ./compiled-rn/VERSION_NATIVE_FB || echo '' )
echo "Last rn version is $VERSION_NATIVE_FB"
echo "last_version_rn=$VERSION_NATIVE_FB" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@v4
- name: "Check branches"
id: check_branches
@@ -132,7 +161,7 @@ jobs:
./compiled/facebook-www/eslint-plugin-react-hooks.js
# Move unstable_server-external-runtime.js into facebook-www
mv build/oss-stable/react-dom/unstable_server-external-runtime.js \
mv build/oss-experimental/react-dom/unstable_server-external-runtime.js \
./compiled/facebook-www/unstable_server-external-runtime.js
# Move react-refresh-babel.development.js into babel-plugin-react-refresh
@@ -145,26 +174,42 @@ jobs:
run: |
BASE_FOLDER='compiled-rn/facebook-fbsource/xplat/js'
mkdir -p ${BASE_FOLDER}/react-native-github/Libraries/Renderer/
mkdir -p ${BASE_FOLDER}/RKJSModules/vendor/{scheduler,react,react-is,react-test-renderer}/
mkdir -p ${BASE_FOLDER}/RKJSModules/vendor/react/{scheduler,react,react-is,react-test-renderer}/
# Move React Native renderer
# Move React Native renderer
mv build/react-native/implementations/ $BASE_FOLDER/react-native-github/Libraries/Renderer/
mv build/react-native/shims/ $BASE_FOLDER/react-native-github/Libraries/Renderer/
mv build/facebook-react-native/scheduler/cjs/ $BASE_FOLDER/RKJSModules/vendor/scheduler/
mv build/facebook-react-native/react/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/
mv build/facebook-react-native/react-is/cjs/ $BASE_FOLDER/RKJSModules/vendor/react-is/
mv build/facebook-react-native/react-test-renderer/cjs/ $BASE_FOLDER/RKJSModules/vendor/react-test-renderer/
mv build/facebook-react-native/scheduler/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/scheduler/
mv build/facebook-react-native/react/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/react/
mv build/facebook-react-native/react-is/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/react-is/
mv build/facebook-react-native/react-test-renderer/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/react-test-renderer/
# Delete OSS renderer. OSS renderer is synced through internal script.
RENDERER_FOLDER=$BASE_FOLDER/react-native-github/Libraries/Renderer/implementations/
rm $RENDERER_FOLDER/ReactFabric-{dev,prod,profiling}.js
rm $RENDERER_FOLDER/ReactNativeRenderer-{dev,prod,profiling}.js
ls -R ./compiled
- name: Add REVISION file
# Move React Native version file
mv build/facebook-react-native/VERSION_NATIVE_FB ./compiled-rn/VERSION_NATIVE_FB
ls -R ./compiled-rn
- name: Add REVISION files
run: |
echo ${{ github.sha }} >> ./compiled/facebook-www/REVISION
cp ./compiled/facebook-www/REVISION ./compiled/facebook-www/REVISION_TRANSFORMS
echo ${{ github.sha }} >> ./compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/REVISION
- name: "Get current version string"
id: get_current_version
run: |
VERSION_CLASSIC=$(cat ./compiled/facebook-www/VERSION_CLASSIC)
VERSION_MODERN=$(cat ./compiled/facebook-www/VERSION_MODERN)
VERSION_NATIVE_FB=$(cat ./compiled-rn/VERSION_NATIVE_FB)
echo "Current classic version is $VERSION_CLASSIC"
echo "Current modern version is $VERSION_MODERN"
echo "Current rn version is $VERSION_NATIVE_FB"
echo "current_version_classic=$VERSION_CLASSIC" >> "$GITHUB_OUTPUT"
echo "current_version_modern=$VERSION_MODERN" >> "$GITHUB_OUTPUT"
echo "current_version_rn=$VERSION_NATIVE_FB" >> "$GITHUB_OUTPUT"
- uses: actions/upload-artifact@v3
with:
name: compiled
@@ -188,15 +233,58 @@ jobs:
with:
name: compiled
path: compiled/
- run: git status -u
- name: Revert version changes
if: needs.download_artifacts.outputs.last_version_classic != '' && needs.download_artifacts.outputs.last_version_modern != ''
env:
CURRENT_VERSION_CLASSIC: ${{ needs.download_artifacts.outputs.current_version_classic }}
CURRENT_VERSION_MODERN: ${{ needs.download_artifacts.outputs.current_version_modern }}
LAST_VERSION_CLASSIC: ${{ needs.download_artifacts.outputs.last_version_classic }}
LAST_VERSION_MODERN: ${{ needs.download_artifacts.outputs.last_version_modern }}
run: |
echo "Reverting $CURRENT_VERSION_CLASSIC to $LAST_VERSION_CLASSIC"
grep -rl "$CURRENT_VERSION_CLASSIC" ./compiled || echo "No files found with $CURRENT_VERSION_CLASSIC"
grep -rl "$CURRENT_VERSION_CLASSIC" ./compiled | xargs -r sed -i -e "s/$CURRENT_VERSION_CLASSIC/$LAST_VERSION_CLASSIC/g"
grep -rl "$CURRENT_VERSION_CLASSIC" ./compiled || echo "Classic version reverted"
echo "===================="
echo "Reverting $CURRENT_VERSION_MODERN to $LAST_VERSION_MODERN"
grep -rl "$CURRENT_VERSION_MODERN" ./compiled || echo "No files found with $CURRENT_VERSION_MODERN"
grep -rl "$CURRENT_VERSION_MODERN" ./compiled | xargs -r sed -i -e "s/$CURRENT_VERSION_MODERN/$LAST_VERSION_MODERN/g"
grep -rl "$CURRENT_VERSION_MODERN" ./compiled || echo "Modern version reverted"
- name: Check if only the REVISION file has changed
id: check_should_commit
run: |
if git status --porcelain | grep -qv '/REVISION$'; then
echo "Full git status"
git status
echo "===================="
if git status --porcelain | grep -qv '/REVISION'; then
echo "Changes detected"
echo "should_commit=true" >> "$GITHUB_OUTPUT"
else
echo "No Changes detected"
echo "should_commit=false" >> "$GITHUB_OUTPUT"
fi
- name: Re-apply version changes
if: steps.check_should_commit.outputs.should_commit == 'true' && needs.download_artifacts.outputs.last_version_classic != '' && needs.download_artifacts.outputs.last_version_modern != ''
env:
CURRENT_VERSION_CLASSIC: ${{ needs.download_artifacts.outputs.current_version_classic }}
CURRENT_VERSION_MODERN: ${{ needs.download_artifacts.outputs.current_version_modern }}
LAST_VERSION_CLASSIC: ${{ needs.download_artifacts.outputs.last_version_classic }}
LAST_VERSION_MODERN: ${{ needs.download_artifacts.outputs.last_version_modern }}
run: |
echo "Re-applying $LAST_VERSION_CLASSIC to $CURRENT_VERSION_CLASSIC"
grep -rl "$LAST_VERSION_CLASSIC" ./compiled || echo "No files found with $LAST_VERSION_CLASSIC"
grep -rl "$LAST_VERSION_CLASSIC" ./compiled | xargs -r sed -i -e "s/$LAST_VERSION_CLASSIC/$CURRENT_VERSION_CLASSIC/g"
grep -rl "$LAST_VERSION_CLASSIC" ./compiled || echo "Classic version re-applied"
echo "===================="
echo "Re-applying $LAST_VERSION_MODERN to $CURRENT_VERSION_MODERN"
grep -rl "$LAST_VERSION_MODERN" ./compiled || echo "No files found with $LAST_VERSION_MODERN"
grep -rl "$LAST_VERSION_MODERN" ./compiled | xargs -r sed -i -e "s/$LAST_VERSION_MODERN/$CURRENT_VERSION_MODERN/g"
grep -rl "$LAST_VERSION_MODERN" ./compiled || echo "Classic version re-applied"
- name: Will commit these changes
if: steps.check_should_commit.outputs.should_commit == 'true'
run: |
echo ":"
git status -u
- name: Commit changes to branch
if: steps.check_should_commit.outputs.should_commit == 'true'
uses: stefanzweifel/git-auto-commit-action@v4
@@ -212,29 +300,59 @@ jobs:
commit_fbsource_artifacts:
needs: download_artifacts
runs-on: ubuntu-latest
if: ${{ (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.fbsource_branch_count == '0') || github.ref == 'refs/heads/meta-fbsource' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: main
repository: facebook/react-fbsource-import
token: ${{secrets.FBSOURCE_SYNC_PUSH_TOKEN}}
ref: builds/facebook-fbsource
- name: Ensure clean directory
run: rm -rf compiled-rn
- uses: actions/download-artifact@v3
with:
name: compiled-rn
path: compiled-rn/
- run: git status -u
- name: Revert version changes
if: needs.download_artifacts.outputs.last_version_rn != ''
env:
CURRENT_VERSION: ${{ needs.download_artifacts.outputs.current_version_rn }}
LAST_VERSION: ${{ needs.download_artifacts.outputs.last_version_rn }}
run: |
echo "Reverting $CURRENT_VERSION to $LAST_VERSION"
grep -rl "$CURRENT_VERSION" ./compiled-rn || echo "No files found with $CURRENT_VERSION"
grep -rl "$CURRENT_VERSION" ./compiled-rn | xargs -r sed -i -e "s/$CURRENT_VERSION/$LAST_VERSION/g"
grep -rl "$CURRENT_VERSION" ./compiled-rn || echo "Version reverted"
- name: Check if only the REVISION file has changed
id: check_should_commit
run: |
if git status --porcelain | grep -qv '/REVISION$'; then
echo "Full git status"
git status
echo "===================="
echo "Checking for changes"
# Check if there are changes in the files other than REVISION or @generated headers
# We also filter out the file name lines with "---" and "+++".
if git diff -- . ':(exclude)*REVISION' | grep -vE "^(@@|diff|index|\-\-\-|\+\+\+|@generated SignedSource)" | grep "^[+-]" > /dev/null; then
echo "Changes detected"
echo "should_commit=true" >> "$GITHUB_OUTPUT"
else
echo "No Changes detected"
echo "should_commit=false" >> "$GITHUB_OUTPUT"
fi
- name: Re-apply version changes
if: steps.check_should_commit.outputs.should_commit == 'true' && needs.download_artifacts.outputs.last_version_rn != ''
env:
CURRENT_VERSION: ${{ needs.download_artifacts.outputs.current_version_rn }}
LAST_VERSION: ${{ needs.download_artifacts.outputs.last_version_rn }}
run: |
echo "Re-applying $LAST_VERSION to $CURRENT_VERSION"
grep -rl "$LAST_VERSION" ./compiled-rn || echo "No files found with $LAST_VERSION"
grep -rl "$LAST_VERSION" ./compiled-rn | xargs -r sed -i -e "s/$LAST_VERSION/$CURRENT_VERSION/g"
grep -rl "$LAST_VERSION" ./compiled-rn || echo "Version re-applied"
- name: Will commit these changes
if: steps.check_should_commit.outputs.should_commit == 'true'
run: |
echo ":"
git status -u
- name: Commit changes to branch
if: steps.check_should_commit.outputs.should_commit == 'true'
uses: stefanzweifel/git-auto-commit-action@v4
@@ -243,5 +361,7 @@ jobs:
${{ github.event.head_commit.message }}
DiffTrain build for commit https://github.com/facebook/react/commit/${{ github.sha }}.
branch: builds/facebook-fbsource
commit_user_name: ${{ github.actor }}
commit_user_email: ${{ github.actor }}@users.noreply.github.com
create_branch: true

View File

@@ -0,0 +1,34 @@
name: Compiler Playground
on:
push:
branches: [main]
pull_request:
paths:
- "compiler/**"
- .github/workflows/compiler-playground.yml
defaults:
run:
working-directory: compiler
jobs:
playground:
name: Test playground
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.x
cache: "yarn"
cache-dependency-path: compiler/yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: npx playwright install --with-deps chromium
- run: yarn workspace playground test

72
.github/workflows/compiler-rust.yml vendored Normal file
View File

@@ -0,0 +1,72 @@
name: React Compiler (Rust)
on:
push:
branches: ["main"]
paths:
- .github/workflows/**
- compiler/crates/**
- compiler/Cargo.*
- compiler/*.toml
pull_request:
paths:
- .github/workflows/**
- compiler/crates/**
- compiler/Cargo.*
- compiler/*.toml
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: -Dwarnings
defaults:
run:
working-directory: compiler
jobs:
test:
name: Rust Test (${{ matrix.target.os }})
strategy:
matrix:
target:
- target: ubuntu-latest
os: ubuntu-latest
# TODO: run on more platforms
# - target: macos-latest
# os: macos-latest
# - target: windows-latest
# os: windows-latest
runs-on: ${{ matrix.target.os }}
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- name: cargo test
run: cargo test --manifest-path=Cargo.toml --locked ${{ matrix.target.features && '--features' }} ${{ matrix.target.features }}
lint:
name: Rust Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
with:
# NOTE: use `rustup run <toolchain> <command>` in commands below
# with this exact same toolchain value
toolchain: nightly-2023-08-01
override: true
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- name: rustfmt
run: grep -r --include "*.rs" --files-without-match "@generated" crates | xargs rustup run nightly-2023-08-01 rustfmt --check --config="skip_children=true"
# - name: cargo clippy
# run: rustup run nightly-2023-08-01 cargo clippy -- -Dclippy::correctness
build:
name: Rust Build
runs-on: ubuntu-latest
# TODO: build on more platforms, deploy, etc
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- name: cargo build
run: cargo build --release

View File

@@ -0,0 +1,106 @@
name: React Compiler (TypeScript)
on:
push:
branches: [main]
pull_request:
paths:
- "compiler/**"
- .github/workflows/compiler-typescript.yml
defaults:
run:
working-directory: compiler
jobs:
discover_yarn_workspaces:
name: Discover yarn workspaces
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- uses: actions/checkout@v4
- id: set-matrix
run: echo "matrix=$(find packages -mindepth 1 -maxdepth 1 -type d | sed 's!packages/!!g' | tr '\n' ',' | sed s/.$// | jq -Rsc '. / "," - [""]')" >> $GITHUB_OUTPUT
# Hardcoded to improve parallelism for babel-plugin-react-compiler
prettier:
name: Run prettier
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.x
cache: "yarn"
cache-dependency-path: compiler/yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn prettier:ci
# Hardcoded to improve parallelism
lint:
name: Lint babel-plugin-react-compiler
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.x
cache: "yarn"
cache-dependency-path: compiler/yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn workspace babel-plugin-react-compiler lint
# Hardcoded to improve parallelism
jest:
name: Jest babel-plugin-react-compiler
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.x
cache: "yarn"
cache-dependency-path: compiler/yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn workspace babel-plugin-react-compiler jest
test:
name: Test ${{ matrix.workspace_name }}
needs: discover_yarn_workspaces
runs-on: ubuntu-latest
continue-on-error: true
strategy:
matrix:
workspace_name: ${{ fromJSON(needs.discover_yarn_workspaces.outputs.matrix) }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.x
cache: "yarn"
cache-dependency-path: compiler/yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn workspace ${{ matrix.workspace_name }} test

View File

@@ -2,8 +2,8 @@
name: 'Manage stale issues and PRs'
on:
schedule:
# Run daily at 2AM
- cron: '0 2 * * *'
# Run hourly
- cron: '0 * * * *'
jobs:
stale:
@@ -16,7 +16,7 @@ jobs:
days-before-stale: 90
# Number of days of inactivity before a stale issue or PR is closed
days-before-close: 7
# Number of issues or PRs to process per day
# API calls per run
operations-per-run: 100
# --- Issues ---
@@ -43,4 +43,4 @@ jobs:
close-pr-message: >
Closing this pull request after a prolonged period of inactivity. If this issue is still present in the latest release, please ask for this pull request to be reopened. Thank you!
# PRs with these labels will never be considered stale
exempt-pr-labels: "Partner,React Core Team,Resolution: Backlog,Type: Bug,Type: Discussion,Type: Needs Investigation,Type: Regression,Type: Feature Request,Type: Enhancement"
exempt-pr-labels: "Partner,React Core Team,Resolution: Backlog,Type: Bug,Type: Discussion,Type: Needs Investigation,Type: Regression,Type: Feature Request,Type: Enhancement"

1
.gitignore vendored
View File

@@ -33,6 +33,7 @@ packages/react-devtools-extensions/firefox/*.xpi
packages/react-devtools-extensions/firefox/*.pem
packages/react-devtools-extensions/shared/build
packages/react-devtools-extensions/.tempUserDataDir
packages/react-devtools-fusebox/dist
packages/react-devtools-inline/dist
packages/react-devtools-shell/dist
packages/react-devtools-timeline/dist

View File

@@ -1,11 +1,13 @@
build
compiler
packages/react-devtools-core/dist
packages/react-devtools-extensions/chrome/build
packages/react-devtools-extensions/firefox/build
packages/react-devtools-extensions/edge/build
packages/react-devtools-extensions/shared/build
packages/react-devtools-extensions/src/ErrorTesterCompiled.js
packages/react-devtools-fusebox/dist
packages/react-devtools-inline/dist
packages/react-devtools-shared/src/hooks/__tests__/__source__/__compiled__/
packages/react-devtools-shared/src/hooks/__tests__/__source__/__untransformed__/

View File

@@ -1,6 +1,9 @@
'use strict';
const {esNextPaths} = require('./scripts/shared/pathsByLanguageVersion');
const {
esNextPaths,
typescriptPaths,
} = require('./scripts/shared/pathsByLanguageVersion');
module.exports = {
bracketSpacing: false,
@@ -17,5 +20,12 @@ module.exports = {
trailingComma: 'all',
},
},
{
files: typescriptPaths,
options: {
trailingComma: 'all',
parser: 'typescript',
},
},
],
};

1130
AUTHORS

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,28 @@
## 18.3.1 (April 26, 2024)
- Export `act` from `react` [f1338f](https://github.com/facebook/react/commit/f1338f8080abd1386454a10bbf93d67bfe37ce85)
## 18.3.0 (April 25, 2024)
This release is identical to 18.2 but adds warnings for deprecated APIs and other changes that are needed for React 19.
Read the [React 19 Upgrade Guide](https://react.dev/blog/2024/04/25/react-19-upgrade-guide) for more info.
### React
- Allow writing to `this.refs` to support string ref codemod [909071](https://github.com/facebook/react/commit/9090712fd3ca4e1099e1f92e67933c2cb4f32552)
- Warn for deprecated `findDOMNode` outside StrictMode [c3b283](https://github.com/facebook/react/commit/c3b283964108b0e8dbcf1f9eb2e7e67815e39dfb)
- Warn for deprecated `test-utils` methods [d4ea75](https://github.com/facebook/react/commit/d4ea75dc4258095593b6ac764289f42bddeb835c)
- Warn for deprecated Legacy Context outside StrictMode [415ee0](https://github.com/facebook/react/commit/415ee0e6ea0fe3e288e65868df2e3241143d5f7f)
- Warn for deprecated string refs outside StrictMode [#25383](https://github.com/facebook/react/pull/25383)
- Warn for deprecated `defaultProps` for function components [#25699](https://github.com/facebook/react/pull/25699)
- Warn when spreading `key` [#25697](https://github.com/facebook/react/pull/25697)
- Warn when using `act` from `test-utils` [d4ea75](https://github.com/facebook/react/commit/d4ea75dc4258095593b6ac764289f42bddeb835c)
### React DOM
- Warn for deprecated `unmountComponentAtNode` [8a015b](https://github.com/facebook/react/commit/8a015b68cc060079878e426610e64e86fb328f8d)
- Warn for deprecated `renderToStaticNodeStream` [#28874](https://github.com/facebook/react/pull/28874)
## 18.2.0 (June 14, 2022)
### React DOM

View File

@@ -4,7 +4,7 @@ React is a JavaScript library for building user interfaces.
* **Declarative:** React makes it painless to create interactive UIs. Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes. Declarative views make your code more predictable, simpler to understand, and easier to debug.
* **Component-Based:** Build encapsulated components that manage their own state, then compose them to make complex UIs. Since component logic is written in JavaScript instead of templates, you can easily pass rich data through your app and keep the state out of the DOM.
* **Learn Once, Write Anywhere:** We don't make assumptions about the rest of your technology stack, so you can develop new features in React without rewriting existing code. React can also render on the server using Node and power mobile apps using [React Native](https://reactnative.dev/).
* **Learn Once, Write Anywhere:** We don't make assumptions about the rest of your technology stack, so you can develop new features in React without rewriting existing code. React can also render on the server using [Node](https://nodejs.org/en) and power mobile apps using [React Native](https://reactnative.dev/).
[Learn how to use React in your project](https://react.dev/learn).
@@ -69,7 +69,7 @@ Facebook has adopted a Code of Conduct that we expect project participants to ad
Read our [contributing guide](https://legacy.reactjs.org/docs/how-to-contribute.html) to learn about our development process, how to propose bugfixes and improvements, and how to build and test your changes to React.
### Good First Issues
### [Good First Issues](https://github.com/facebook/react/labels/good%20first%20issue)
To help you get your feet wet and get you familiar with our contribution process, we have a list of [good first issues](https://github.com/facebook/react/labels/good%20first%20issue) that contain bugs that have a relatively limited scope. This is a great place to get started.

View File

@@ -26,7 +26,11 @@ const ReactVersion = '19.0.0';
//
// It only affects the label used in the version string. To customize the
// npm dist tags used during publish, refer to .circleci/config.yml.
const canaryChannelLabel = 'canary';
const canaryChannelLabel = 'rc';
// If the canaryChannelLabel is "rc", the build pipeline will use this to build
// an RC version of the packages.
const rcNumber = 0;
const stablePackages = {
'eslint-plugin-react-hooks': '5.1.0',
@@ -53,6 +57,7 @@ const experimentalPackages = [];
module.exports = {
ReactVersion,
canaryChannelLabel,
rcNumber,
stablePackages,
experimentalPackages,
};

100
compiler/.eslintrc.js Normal file
View File

@@ -0,0 +1,100 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* Our philosophy for linting is that lints should be very high-signal:
* - Error, don't warn. If it's worth mentioning it's worth fixing.
* - Enable rules that consistently identify real problems. If we frequently would have to
* disable the rule due to false positives, it isn't high-signal.
* - Enable rules that help improve consistent style (to avoid code review about style rather
* than substance).
*/
module.exports = {
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
rules: {
/*
* We prefer using const where variables are not reassigned, but occassional mistakes
* aren't a major issue
*/
"prefer-const": "off",
// Not valuable enough to enable
"no-useless-escape": "off",
/*
* There are valid use cases for loops with constant conditions where the body contains the
* break
*/
"no-constant-condition": "off",
// eslint only knows about builtin control flow (eg throw, return, break) and not custom ones
// like invariant.
"no-fallthrough": "off",
/*
* Low-value: this fires even for declarations that capture references which wouldn't be as
* obvious if the declaration was lifted to the parent root
*/
"no-inner-declarations": "off",
"multiline-comment-style": ["error", "starred-block"],
"@typescript-eslint/no-empty-function": "off",
/*
* Explicitly casting to/through any is sometimes required, often for error messages to
* assertExhaustive()
*/
"@typescript-eslint/no-explicit-any": "off",
/*
* We use non-null assertions carefully. Ideally, there would be a TS option to codegen
* a non-null check at the assertion site.
*/
"@typescript-eslint/no-non-null-assertion": "off",
// Being explicit provides value in cases where inference may later change
"@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/explicit-function-return-type": "error",
/*
* Unused variables are frequently a bug. Prefix unused variables with an _ to fix, but note
* that eslint won't warn you that an underscore prefixed variable is used and that the prefix
* should be dropped.
*/
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],
// Consider enabling for consistency. Ideally violations could be auto-fixed.
"@typescript-eslint/consistent-generic-constructors": [
"off",
"constructor",
],
"@typescript-eslint/array-type": ["error", { default: "generic" }],
"@typescript-eslint/triple-slash-reference": "off",
"@typescript-eslint/no-var-requires": "off"
},
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
root: true,
ignorePatterns: ["**/__tests__/**/*", "**/*.d.ts", "**/dist/**/*"],
env: {
node: true,
},
/*
* If rules need to be disabled then the rule is insufficiently high signal
* and should be diasbled altogether or customized (in either case via a standalone PR)
*/
noInlineConfig: true,
reportUnusedDisableDirectives: true,
};

24
compiler/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
.DS_Store
.spr.yml
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
node_modules
.watchmanconfig
.watchman-cookie-*
dist
.vscode
!packages/playground/.vscode
.spr.yml
testfilter.txt
bundle-oss.sh

0
compiler/.gitmodules vendored Normal file
View File

21
compiler/.prettierignore Normal file
View File

@@ -0,0 +1,21 @@
**/dist
**/__tests__/fixtures/**/*.expect.md
**/__tests__/fixtures/**/*.flow.js
**/.next
crates
apps/playground/public
**/LICENSE
.*
*.md*
*.json
*.css
*.webmanifest
*.map
*.sh
*.txt
*.ico
*.svg
*.lock
*.toml

9
compiler/.prettierrc.js Normal file
View File

@@ -0,0 +1,9 @@
const config = {
requirePragma: false,
parser: "babel-ts",
semi: true,
singleQuote: false,
trailingComma: "es5"
}
module.exports = config;

8
compiler/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"editor.formatOnSave": true,
"[typescript][typescriptreact]": {
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
}
}
}

1
compiler/.watchmanconfig Normal file
View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1,80 @@
# Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to make participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies within all project spaces, and it also applies when
an individual is representing the project or its community in public spaces.
Examples of representing a project or community include using an official
project e-mail address, posting via an official social media account, or acting
as an appointed representative at an online or offline event. Representation of
a project may be further defined and clarified by project maintainers.
This Code of Conduct also applies outside the project spaces when there is a
reasonable belief that an individual's behavior may have a negative impact on
the project or its community.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at <opensource-conduct@fb.com>. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

31
compiler/CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,31 @@
# Contributing to react-forget
We want to make contributing to this project as easy and transparent as
possible.
## Pull Requests
We actively welcome your pull requests.
1. Fork the repo and create your branch from `main`.
2. If you've added code that should be tested, add tests.
3. If you've changed APIs, update the documentation.
4. Ensure the test suite passes.
5. Make sure your code lints.
6. If you haven't already, complete the Contributor License Agreement ("CLA").
## Contributor License Agreement ("CLA")
In order to accept your pull request, we need you to submit a CLA. You only need
to do this once to work on any of Facebook's open source projects.
Complete your CLA here: <https://code.facebook.com/cla>
## Issues
We use GitHub issues to track public bugs. Please ensure your description is
clear and has sufficient instructions to be able to reproduce the issue.
Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
disclosure of security bugs. In those cases, please go through the process
outlined on that page and do not file a public issue.
## License
By contributing to react-forget, you agree that your contributions will be licensed
under the LICENSE file in the root directory of this source tree.

1217
compiler/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

61
compiler/Cargo.toml Normal file
View File

@@ -0,0 +1,61 @@
[workspace]
resolver = "2"
members = ["crates/*"]
[workspace.package]
authors = ["The React Team https://react.dev/community/team"]
description = "React Compiler"
edition = "2021"
homepage = "https://github.com/facebook/react"
keywords = ["JavaScript", "TypeScript", "React", "React Compiler", "Compiler"]
license = "MIT"
repository = "https://github.com/facebook/react"
[workspace.dependencies]
# workspace crates
react_build_hir = { path = "crates/react_build_hir" }
react_diagnostics = { path = "crates/react_diagnostics" }
react_estree = { path = "crates/react_estree" }
react_estree_codegen = { path = "crates/react_estree_codegen" }
react_fixtures = { path = "crates/react_fixtures" }
react_hermes_parser = { path = "crates/react_hermes_parser" }
react_hir = { path = "crates/react_hir" }
react_optimization = { path = "crates/react_optimization" }
react_semantic_analysis = { path = "crates/react_semantic_analysis" }
react_ssa = { path = "crates/react_ssa" }
react_utils = { path = "crates/react_utils" }
# dependencies
indexmap = { version = "2.0.0", features = ["serde"] }
insta = { version = "1.30.0", features = ["glob"] }
miette = { version = "5.9.0" }
prettyplease = "0.2.10"
quote = "1.0.29"
serde = { version = "1.0.167", features = ["serde_derive"] }
serde_json = "1.0.100"
stacker = "0.1.15"
static_assertions = "1.1.0"
syn = "2.0.23"
thiserror = "1.0.41"
hermes = { git = "https://github.com/facebook/hermes.git" }
juno_support = { git = "https://github.com/facebook/hermes.git" }
[profile.release]
# configuration adapted from oxc
# https://github.com/Boshen/oxc/blob/ea85ee9f2d64dd284c5b7410f491d81fb879abae/Cargo.toml#L89-L97
opt-level = 3
lto = "fat"
codegen-units = 1
strip = "symbols"
debug = false
panic = "abort" # Let it crash and force ourselves to write safe Rust.
# Make insta run faster by compiling with release mode optimizations
# https://docs.rs/insta/latest/insta/#optional-faster-runs
[profile.dev.package.insta]
opt-level = 3
# Make insta diffing libary faster by compiling with release mode optimizations
# https://docs.rs/insta/latest/insta/#optional-faster-runs
[profile.dev.package.similar]
opt-level = 3

21
compiler/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) Meta Platforms, Inc. and affiliates.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

7
compiler/README.md Normal file
View File

@@ -0,0 +1,7 @@
# React Compiler
React Compiler is a compiler that optimizes React applications, ensuring that only the minimal parts of components and hooks will re-render when state changes. The compiler also validates that components and hooks follow the Rules of React.
More information about the design and architecture of the compiler are covered in the [Design Goals](./docs/DESIGN_GOALS.md).
More information about developing the compiler itself is covered in the [Development Guide](./docs/DEVELOPMENT_GUIDE.md).

View File

@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}

45
compiler/apps/playground/.gitignore vendored Normal file
View File

@@ -0,0 +1,45 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
/test-results
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
# external fonts
public/fonts/Optimistic_Display_W_Lt.woff2
public/fonts/Optimistic_Display_W_Md.woff2
public/fonts/Optimistic_Display_W_Bd.woff2
# vscode
.vscode/*
!.vscode/extensions.json

View File

@@ -0,0 +1,3 @@
{
"recommendations": ["bradlc.vscode-tailwindcss", "heybourn.headwind"]
}

View File

@@ -0,0 +1,35 @@
# React Compiler Playground
An interactive playground to demonstrate, test, and have fun with React Compiler.
## Setup
```sh
# Build React Compiler from source and install Playground dependencies.
$ yarn
# Or similarly
$ npm install
```
## Development
```sh
# Start the local development server with
$ yarn dev
# Or
$ npm run dev
# Rerun the following (in a separate terminal window) when React Compiler
# is changed locally to keep Playground in sync.
$ yarn
```
## Deployment
This project has been deployed using Vercel. Vercel does the exact same thing as we would
locally, by running `yarn` at the install step in the Playground directory to build
React Compiler from source and [symlink](https://classic.yarnpkg.com/en/docs/cli/link) it as its dependency.
This means that Playground is automatically deployed on every push and pull requests will reflect
the behaviors of React Compiler of that commit.

View File

@@ -0,0 +1,11 @@
function MyApp() {
  const $ = _c(1);
  let t0;
  if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
    t0 = <div>Hello World</div>;
    $[0] = t0;
  } else {
    t0 = $[0];
  }
  return t0;
}

View File

@@ -0,0 +1,13 @@
function TestComponent(t0) {
  const $ = _c(2);
  const { x } = t0;
  let t1;
  if ($[0] !== x) {
    t1 = <Button>{x}</Button>;
    $[0] = x;
    $[1] = t1;
  } else {
    t1 = $[1];
  }
  return t1;
}

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { expect, test } from "@playwright/test";
import { encodeStore, type Store } from "../../lib/stores";
const STORE: Store = {
source: `export default function TestComponent({ x }) {
return <Button>{x}</Button>;
}
`,
};
const HASH = encodeStore(STORE);
function concat(data: Array<string>): string {
return data.join("");
}
test("editor should compile successfully", async ({ page }) => {
await page.goto(`/#${HASH}`, { waitUntil: "networkidle" });
await page.screenshot({
fullPage: true,
path: "test-results/00-on-networkidle.png",
});
// User input from hash compiles
await page.screenshot({
fullPage: true,
path: "test-results/01-show-js-before.png",
});
const userInput =
(await page.locator(".monaco-editor").nth(2).allInnerTexts()) ?? [];
expect(concat(userInput)).toMatchSnapshot("user-input.txt");
// Reset button works
page.on("dialog", (dialog) => dialog.accept());
await page.getByRole("button", { name: "Reset" }).click();
await page.screenshot({
fullPage: true,
path: "test-results/02-show-js-after.png",
});
const defaultInput =
(await page.locator(".monaco-editor").nth(2).allInnerTexts()) ?? [];
expect(concat(defaultInput)).toMatchSnapshot("default-input.txt");
});

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { NextPage } from "next";
import Head from "next/head";
import { SnackbarProvider } from "notistack";
import { Editor, Header, StoreProvider } from "../components";
import MessageSnackbar from "../components/Message";
const Home: NextPage = () => {
return (
<div className="flex flex-col w-screen h-screen font-light">
<Head>
<title>
{process.env.NODE_ENV === "development"
? "[DEV] React Compiler Playground"
: "React Compiler Playground"}
</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"
></meta>
<link rel="icon" href="/favicon.ico" />
<link rel="manifest" href="/site.webmanifest" />
<link
rel="preload"
href="/fonts/Source-Code-Pro-Regular.woff2"
as="font"
type="font/woff2"
crossOrigin="anonymous"
/>
<link
rel="preload"
href="/fonts/Optimistic_Display_W_Lt.woff2"
as="font"
type="font/woff2"
crossOrigin="anonymous"
/>
</Head>
<StoreProvider>
<SnackbarProvider
preventDuplicate
maxSnack={10}
Components={{ message: MessageSnackbar }}
>
<Header />
<Editor />
</SnackbarProvider>
</StoreProvider>
</div>
);
};
export default Home;

View File

@@ -0,0 +1,48 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import "../styles/globals.css";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
"use no memo";
return (
<html lang="en">
<head>
<title>
{process.env.NODE_ENV === "development"
? "[DEV] React Compiler Playground"
: "React Compiler Playground"}
</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"
></meta>
<link rel="icon" href="/favicon.ico" />
<link rel="manifest" href="/site.webmanifest" />
<link
rel="preload"
href="/fonts/Source-Code-Pro-Regular.woff2"
as="font"
type="font/woff2"
crossOrigin="anonymous"
/>
<link
rel="preload"
href="/fonts/Optimistic_Display_W_Lt.woff2"
as="font"
type="font/woff2"
crossOrigin="anonymous"
/>
</head>
<body className="font-sans h-screen overflow-y-hidden">{children}</body>
</html>
);
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
"use client";
import { SnackbarProvider } from "notistack";
import { Editor, Header, StoreProvider } from "../components";
import MessageSnackbar from "../components/Message";
export default function Hoot() {
return (
<StoreProvider>
<SnackbarProvider
preventDuplicate
maxSnack={10}
Components={{ message: MessageSnackbar }}
>
<Header />
<Editor />
</SnackbarProvider>
</StoreProvider>
);
}

View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
module.exports = function (api) {
api.cache(true);
return {
presets: ["next/babel"],
plugins: [
[
"babel-plugin-react-compiler",
{
runtimeModule: "react-compiler-runtime",
},
],
],
};
};

View File

@@ -0,0 +1,96 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* Sync from <https://github.com/reactjs/reactjs.org/blob/main/beta/colors.js>.
*/
module.exports = {
// Text colors
primary: "#23272F", // gray-90
"primary-dark": "#F6F7F9", // gray-5
secondary: "#404756", // gray-70
"secondary-dark": "#EBECF0", // gray-10
link: "#087EA4", // blue-50
"link-dark": "#149ECA", // blue-40
syntax: "#EBECF0", // gray-10
wash: "#FFFFFF",
"wash-dark": "#23272F", // gray-90
card: "#F6F7F9", // gray-05
"card-dark": "#343A46", // gray-80
highlight: "#E6F7FF", // blue-10
"highlight-dark": "rgba(88,175,223,.1)",
border: "#EBECF0", // gray-10
"border-dark": "#343A46", // gray-80
"secondary-button": "#EBECF0", // gray-10
"secondary-button-dark": "#404756", // gray-70
// Gray
"gray-95": "#16181D",
"gray-90": "#23272F",
"gray-80": "#343A46",
"gray-70": "#404756",
"gray-60": "#4E5769",
"gray-50": "#5E687E", // unused
"gray-40": "#78839B",
"gray-30": "#99A1B3",
"gray-20": "#BCC1CD",
"gray-10": "#EBECF0",
"gray-5": "#F6F7F9",
// Blue
"blue-60": "#045975",
"blue-50": "#087EA4",
"blue-40": "#149ECA", // Brand Blue
"blue-30": "#58C4DC", // unused
"blue-20": "#ABE2ED",
"blue-10": "#E6F7FF", // todo: doesn't match illustrations
"blue-5": "#E6F6FA",
// Yellow
"yellow-60": "#B65700",
"yellow-50": "#C76A15",
"yellow-40": "#DB7D27", // unused
"yellow-30": "#FABD62", // unused
"yellow-20": "#FCDEB0", // unused
"yellow-10": "#FDE7C7",
"yellow-5": "#FEF5E7",
// Purple
"purple-60": "#2B3491", // unused
"purple-50": "#575FB7",
"purple-40": "#6B75DB",
"purple-30": "#8891EC",
"purple-20": "#C3C8F5", // unused
"purple-10": "#E7E9FB",
"purple-5": "#F3F4FD",
// Green
"green-60": "#2B6E62",
"green-50": "#388F7F",
"green-40": "#44AC99",
"green-30": "#7FCCBF",
"green-20": "#ABDED5",
"green-10": "#E5F5F2",
"green-5": "#F4FBF9",
// RED
"red-60": "#712D28",
"red-50": "#A6423A", // unused
"red-40": "#C1554D",
"red-30": "#D07D77",
"red-20": "#E5B7B3", // unused
"red-10": "#F2DBD9", // unused
"red-5": "#FAF1F0",
// MISC
"code-block": "#99a1b30f", // gray-30 @ 6%
"gradient-blue": "#58C4DC", // Only used for the landing gradient for now.
github: {
highlight: "#fffbdd",
},
};

View File

@@ -0,0 +1,335 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { parse, ParserPlugin } from "@babel/parser";
import traverse, { NodePath } from "@babel/traverse";
import * as t from "@babel/types";
import {
CompilerError,
CompilerErrorDetail,
Effect,
ErrorSeverity,
parseConfigPragma,
printHIR,
printReactiveFunction,
run,
ValueKind,
type Hook,
} from "babel-plugin-react-compiler/src";
import { type ReactFunctionType } from "babel-plugin-react-compiler/src/HIR/Environment";
import clsx from "clsx";
import invariant from "invariant";
import { useSnackbar } from "notistack";
import { useDeferredValue, useMemo } from "react";
import { useMountEffect } from "../../hooks";
import { defaultStore } from "../../lib/defaultStore";
import {
createMessage,
initStoreFromUrlOrLocalStorage,
MessageLevel,
MessageSource,
type Store,
} from "../../lib/stores";
import { useStore, useStoreDispatch } from "../StoreContext";
import Input from "./Input";
import {
CompilerOutput,
default as Output,
PrintedCompilerPipelineValue,
} from "./Output";
function parseFunctions(
source: string
): Array<
NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
>
> {
const items: Array<
NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
>
> = [];
try {
const isFlow = source
.trim()
.split("\n", 1)[0]
.match(/\s*\/\/\s*\@flow\s*/);
let type_transform: ParserPlugin;
if (isFlow) {
type_transform = "flow";
} else {
type_transform = "typescript";
}
const ast = parse(source, {
plugins: [type_transform, "jsx"],
sourceType: "module",
});
traverse(ast, {
FunctionDeclaration(nodePath) {
items.push(nodePath);
nodePath.skip();
},
ArrowFunctionExpression(nodePath) {
items.push(nodePath);
nodePath.skip();
},
FunctionExpression(nodePath) {
items.push(nodePath);
nodePath.skip();
},
});
} catch (e) {
console.error(e);
CompilerError.throwInvalidJS({
reason: String(e),
description: null,
loc: null,
suggestions: null,
});
}
return items;
}
const COMMON_HOOKS: Array<[string, Hook]> = [
[
"useFragment",
{
valueKind: ValueKind.Frozen,
effectKind: Effect.Freeze,
noAlias: true,
transitiveMixedData: true,
},
],
[
"usePaginationFragment",
{
valueKind: ValueKind.Frozen,
effectKind: Effect.Freeze,
noAlias: true,
transitiveMixedData: true,
},
],
[
"useRefetchableFragment",
{
valueKind: ValueKind.Frozen,
effectKind: Effect.Freeze,
noAlias: true,
transitiveMixedData: true,
},
],
[
"useLazyLoadQuery",
{
valueKind: ValueKind.Frozen,
effectKind: Effect.Freeze,
noAlias: true,
transitiveMixedData: true,
},
],
[
"usePreloadedQuery",
{
valueKind: ValueKind.Frozen,
effectKind: Effect.Freeze,
noAlias: true,
transitiveMixedData: true,
},
],
];
function isHookName(s: string): boolean {
return /^use[A-Z0-9]/.test(s);
}
function getReactFunctionType(
id: NodePath<t.Identifier | null | undefined>
): ReactFunctionType {
if (id && id.node && id.isIdentifier()) {
if (isHookName(id.node.name)) {
return "Hook";
}
const isPascalCaseNameSpace = /^[A-Z].*/;
if (isPascalCaseNameSpace.test(id.node.name)) {
return "Component";
}
}
return "Other";
}
function compile(source: string): CompilerOutput {
const results = new Map<string, PrintedCompilerPipelineValue[]>();
const error = new CompilerError();
const upsert = (result: PrintedCompilerPipelineValue) => {
const entry = results.get(result.name);
if (Array.isArray(entry)) {
entry.push(result);
} else {
results.set(result.name, [result]);
}
};
try {
// Extract the first line to quickly check for custom test directives
const pragma = source.substring(0, source.indexOf("\n"));
const config = parseConfigPragma(pragma);
for (const fn of parseFunctions(source)) {
if (!fn.isFunctionDeclaration()) {
error.pushErrorDetail(
new CompilerErrorDetail({
reason: `Unexpected function type ${fn.node.type}`,
description:
"Playground only supports parsing function declarations",
severity: ErrorSeverity.Todo,
loc: fn.node.loc ?? null,
suggestions: null,
})
);
continue;
}
const id = fn.get("id");
for (const result of run(
fn,
{
...config,
customHooks: new Map([...COMMON_HOOKS]),
},
getReactFunctionType(id),
"_c",
null,
null,
null
)) {
const fnName = fn.node.id?.name ?? null;
switch (result.kind) {
case "ast": {
upsert({
kind: "ast",
fnName,
name: result.name,
value: {
type: "FunctionDeclaration",
id: result.value.id,
async: result.value.async,
generator: result.value.generator,
body: result.value.body,
params: result.value.params,
},
});
break;
}
case "hir": {
upsert({
kind: "hir",
fnName,
name: result.name,
value: printHIR(result.value.body),
});
break;
}
case "reactive": {
upsert({
kind: "reactive",
fnName,
name: result.name,
value: printReactiveFunction(result.value),
});
break;
}
case "debug": {
upsert({
kind: "debug",
fnName,
name: result.name,
value: result.value,
});
break;
}
default: {
const _: never = result;
throw new Error(`Unhandled result ${result}`);
}
}
}
}
} catch (err) {
// error might be an invariant violation or other runtime error
// (i.e. object shape that is not CompilerError)
if (err instanceof CompilerError && err.details.length > 0) {
error.details.push(...err.details);
} else {
// Handle unexpected failures by logging (to get a stack trace)
// and reporting
console.error(err);
error.details.push(
new CompilerErrorDetail({
severity: ErrorSeverity.Invariant,
reason: `Unexpected failure when transforming input! ${err}`,
loc: null,
suggestions: null,
})
);
}
}
if (error.hasErrors()) {
return { kind: "err", results, error: error };
}
return { kind: "ok", results };
}
export default function Editor() {
const store = useStore();
const deferredStore = useDeferredValue(store);
const dispatchStore = useStoreDispatch();
const { enqueueSnackbar } = useSnackbar();
const compilerOutput = useMemo(
() => compile(deferredStore.source),
[deferredStore.source]
);
useMountEffect(() => {
let mountStore: Store;
try {
mountStore = initStoreFromUrlOrLocalStorage();
} catch (e) {
invariant(e instanceof Error, "Only Error may be caught.");
enqueueSnackbar(e.message, {
variant: "message",
...createMessage(
"Bad URL - fell back to the default Playground.",
MessageLevel.Info,
MessageSource.Playground
),
});
mountStore = defaultStore;
}
dispatchStore({
type: "setStore",
payload: { store: mountStore },
});
});
return (
<>
<div className="relative flex basis top-14">
<div className={clsx("relative sm:basis-1/4")}>
<Input
errors={
compilerOutput.kind === "err" ? compilerOutput.error.details : []
}
/>
</div>
<div className={clsx("flex sm:flex flex-wrap")}>
<Output store={deferredStore} compilerOutput={compilerOutput} />
</div>
</div>
</>
);
}

View File

@@ -0,0 +1,127 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import MonacoEditor, { loader, type Monaco } from "@monaco-editor/react";
import { CompilerErrorDetail } from "babel-plugin-react-compiler/src";
import invariant from "invariant";
import type { editor } from "monaco-editor";
import * as monaco from "monaco-editor";
import { Resizable } from "re-resizable";
import { useEffect, useState } from "react";
import { renderReactCompilerMarkers } from "../../lib/reactCompilerMonacoDiagnostics";
import { useStore, useStoreDispatch } from "../StoreContext";
import { monacoOptions } from "./monacoOptions";
// TODO: Make TS recognize .d.ts files, in addition to loading them with webpack.
// @ts-ignore
import React$Types from "../../node_modules/@types/react/index.d.ts";
loader.config({ monaco });
type Props = {
errors: CompilerErrorDetail[];
};
export default function Input({ errors }: Props) {
const [monaco, setMonaco] = useState<Monaco | null>(null);
const store = useStore();
const dispatchStore = useStoreDispatch();
// Set tab width to 2 spaces for the selected input file.
useEffect(() => {
if (!monaco) return;
const uri = monaco.Uri.parse(`file:///index.js`);
const model = monaco.editor.getModel(uri);
invariant(model, "Model must exist for the selected input file.");
renderReactCompilerMarkers({ monaco, model, details: errors });
// N.B. that `tabSize` is a model property, not an editor property.
// So, the tab size has to be set per model.
model.updateOptions({ tabSize: 2 });
}, [monaco, errors]);
const handleChange = (value: string | undefined) => {
if (!value) return;
dispatchStore({
type: "updateFile",
payload: {
source: value,
},
});
};
const handleMount = (_: editor.IStandaloneCodeEditor, monaco: Monaco) => {
setMonaco(monaco);
// Ignore "can only be used in TypeScript files." errors, since
// we want to support syntax highlighting for Flow (*.js) files
// and Flow is not a built-in language.
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
diagnosticCodesToIgnore: [
8002, 8003, 8004, 8005, 8006, 8008, 8009, 8010, 8011, 8012, 8013,
],
noSemanticValidation: true,
noSyntaxValidation: false,
});
const tscOptions = {
allowNonTsExtensions: true,
target: monaco.languages.typescript.ScriptTarget.ES2015,
moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
jsx: monaco.languages.typescript.JsxEmit.Preserve,
typeRoots: ["node_modules/@types"],
allowSyntheticDefaultImports: true,
};
monaco.languages.typescript.javascriptDefaults.setCompilerOptions(
tscOptions
);
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
...tscOptions,
checkJs: true,
allowJs: true,
});
// Add React type declarations to Monaco
const reactLib = [
React$Types,
"file:///node_modules/@types/react/index.d.ts",
] as [any, string];
monaco.languages.typescript.javascriptDefaults.addExtraLib(...reactLib);
monaco.languages.typescript.typescriptDefaults.addExtraLib(...reactLib);
// Remeasure the font in case the custom font is loaded only after
// Monaco Editor is mounted.
// N.B. that this applies also to the output editor as it seems
// Monaco Editor instances share the same font config.
document.fonts.ready.then(() => {
monaco.editor.remeasureFonts();
});
};
return (
<div className="relative flex flex-col flex-none border-r border-gray-200">
<Resizable
minWidth={650}
enable={{ right: true }}
// Restrict MonacoEditor's height, since the config autoLayout:true
// will grow the editor to fit within parent element
className="!h-[calc(100vh_-_3.5rem)]"
>
<MonacoEditor
path={"index.js"}
// .js and .jsx files are specified to be TS so that Monaco can actually
// check their syntax using its TS language service. They are still JS files
// due to their extensions, so TS language features don't work.
language={"javascript"}
value={store.source}
onMount={handleMount}
onChange={handleChange}
options={monacoOptions}
/>
</Resizable>
</div>
);
}

View File

@@ -0,0 +1,306 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import generate from "@babel/generator";
import * as t from "@babel/types";
import {
CodeIcon,
DocumentAddIcon,
InformationCircleIcon,
} from "@heroicons/react/outline";
import MonacoEditor, { DiffEditor } from "@monaco-editor/react";
import { type CompilerError } from "babel-plugin-react-compiler/src";
import parserBabel from "prettier/plugins/babel";
import * as prettierPluginEstree from "prettier/plugins/estree";
import * as prettier from "prettier/standalone";
import { memo, useEffect, useState } from "react";
import { type Store } from "../../lib/stores";
import TabbedWindow from "../TabbedWindow";
import { monacoOptions } from "./monacoOptions";
const MemoizedOutput = memo(Output);
export default MemoizedOutput;
export type PrintedCompilerPipelineValue =
| {
kind: "ast";
name: string;
fnName: string | null;
value: t.FunctionDeclaration;
}
| {
kind: "hir";
name: string;
fnName: string | null;
value: string;
}
| { kind: "reactive"; name: string; fnName: string | null; value: string }
| { kind: "debug"; name: string; fnName: string | null; value: string };
export type CompilerOutput =
| { kind: "ok"; results: Map<string, PrintedCompilerPipelineValue[]> }
| {
kind: "err";
results: Map<string, PrintedCompilerPipelineValue[]>;
error: CompilerError;
};
type Props = {
store: Store;
compilerOutput: CompilerOutput;
};
async function tabify(source: string, compilerOutput: CompilerOutput) {
const tabs = new Map<string, React.ReactNode>();
const reorderedTabs = new Map<string, React.ReactNode>();
const concattedResults = new Map<string, string>();
let topLevelFnDecls: Array<t.FunctionDeclaration> = [];
// Concat all top level function declaration results into a single tab for each pass
for (const [passName, results] of compilerOutput.results) {
for (const result of results) {
switch (result.kind) {
case "hir": {
const prev = concattedResults.get(result.name);
const next = result.value;
const identName = `function ${result.fnName}`;
if (prev != null) {
concattedResults.set(passName, `${prev}\n\n${identName}\n${next}`);
} else {
concattedResults.set(passName, `${identName}\n${next}`);
}
break;
}
case "reactive": {
const prev = concattedResults.get(passName);
const next = result.value;
if (prev != null) {
concattedResults.set(passName, `${prev}\n\n${next}`);
} else {
concattedResults.set(passName, next);
}
break;
}
case "ast":
topLevelFnDecls.push(result.value);
break;
case "debug": {
concattedResults.set(passName, result.value);
break;
}
default: {
const _: never = result;
throw new Error("Unexpected result kind");
}
}
}
}
let lastPassOutput: string | null = null;
for (const [passName, text] of concattedResults) {
tabs.set(
passName,
<TextTabContent
output={text}
diff={lastPassOutput ?? null}
showInfoPanel={true}
></TextTabContent>
);
lastPassOutput = text;
}
// Ensure that JS and the JS source map come first
if (topLevelFnDecls.length > 0) {
// Make a synthetic Program so we can have a single AST with all the top level
// FunctionDeclarations
const ast = t.program(topLevelFnDecls);
const { code, sourceMapUrl } = await codegen(ast, source);
reorderedTabs.set(
"JS",
<TextTabContent
output={code}
diff={null}
showInfoPanel={false}
></TextTabContent>
);
if (sourceMapUrl) {
reorderedTabs.set(
"SourceMap",
<>
<iframe
src={sourceMapUrl}
className="w-full h-monaco_small sm:h-monaco"
title="Generated Code"
/>
</>
);
}
}
tabs.forEach((tab, name) => {
reorderedTabs.set(name, tab);
});
return reorderedTabs;
}
async function codegen(
ast: t.Program,
source: string
): Promise<{ code: any; sourceMapUrl: string | null }> {
const generated = generate(
ast,
{ sourceMaps: true, sourceFileName: "input.js" },
source
);
const sourceMapUrl = getSourceMapUrl(
generated.code,
JSON.stringify(generated.map)
);
const codegenOutput = await prettier.format(generated.code, {
semi: true,
parser: "babel",
plugins: [parserBabel, prettierPluginEstree],
});
return { code: codegenOutput, sourceMapUrl };
}
function utf16ToUTF8(s: string): string {
return unescape(encodeURIComponent(s));
}
function getSourceMapUrl(code: string, map: string): string | null {
code = utf16ToUTF8(code);
map = utf16ToUTF8(map);
return `https://evanw.github.io/source-map-visualization/#${btoa(
`${code.length}\0${code}${map.length}\0${map}`
)}`;
}
function Output({ store, compilerOutput }: Props) {
const [tabsOpen, setTabsOpen] = useState<Set<string>>(() => new Set(["JS"]));
const [tabs, setTabs] = useState<Map<string, React.ReactNode>>(
() => new Map()
);
useEffect(() => {
tabify(store.source, compilerOutput).then((tabs) => {
setTabs(tabs);
});
}, [store.source, compilerOutput]);
const changedPasses: Set<string> = new Set();
let lastResult: string = "";
for (const [passName, results] of compilerOutput.results) {
for (const result of results) {
let currResult = "";
if (result.kind === "hir" || result.kind === "reactive") {
currResult += `function ${result.fnName}\n\n${result.value}`;
}
if (currResult !== lastResult) {
changedPasses.add(passName);
}
lastResult = currResult;
}
}
return (
<>
<TabbedWindow
defaultTab="HIR"
setTabsOpen={setTabsOpen}
tabsOpen={tabsOpen}
tabs={tabs}
changedPasses={changedPasses}
/>
{compilerOutput.kind === "err" ? (
<div
className="flex flex-wrap absolute bottom-0 bg-white grow border-y border-grey-200 transition-all ease-in"
style={{ width: "calc(100vw - 650px)" }}
>
<div className="w-full p-4 basis-full border-b">
<h2>COMPILER ERRORS</h2>
</div>
<pre
className="p-4 basis-full text-red-600 overflow-y-scroll whitespace-pre-wrap"
style={{ width: "calc(100vw - 650px)", height: "150px" }}
>
<code>{compilerOutput.error.toString()}</code>
</pre>
</div>
) : null}
</>
);
}
function TextTabContent({
output,
diff,
showInfoPanel,
}: {
output: string;
diff: string | null;
showInfoPanel: boolean;
}) {
const [diffMode, setDiffMode] = useState(false);
return (
// Restrict MonacoEditor's height, since the config autoLayout:true
// will grow the editor to fit within parent element
<div className="w-full h-monaco_small sm:h-monaco">
{showInfoPanel ? (
<div className="flex items-center gap-1 bg-amber-50 p-2">
{diff != null && output !== diff ? (
<button
className="flex items-center gap-1 transition-colors duration-150 ease-in text-secondary hover:text-link"
onClick={() => setDiffMode((diffMode) => !diffMode)}
>
{!diffMode ? (
<>
<DocumentAddIcon className="w-5 h-5" /> Show Diff
</>
) : (
<>
<CodeIcon className="w-5 h-5" /> Show Output
</>
)}
</button>
) : (
<>
<span className="flex items-center gap-1">
<InformationCircleIcon className="w-5 h-5" /> No changes from
previous pass
</span>
</>
)}
</div>
) : null}
{diff != null && diffMode ? (
<DiffEditor
original={diff}
modified={output}
options={{
...monacoOptions,
readOnly: true,
lineNumbers: "off",
glyphMargin: false,
// Undocumented see https://github.com/Microsoft/vscode/issues/30795#issuecomment-410998882
lineDecorationsWidth: 0,
lineNumbersMinChars: 0,
}}
/>
) : (
<MonacoEditor
defaultLanguage="javascript"
value={output}
options={{
...monacoOptions,
readOnly: true,
lineNumbers: "off",
glyphMargin: false,
// Undocumented see https://github.com/Microsoft/vscode/issues/30795#issuecomment-410998882
lineDecorationsWidth: 0,
lineNumbersMinChars: 0,
}}
/>
)}
</div>
);
}

View File

@@ -0,0 +1,16 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import dynamic from "next/dynamic";
// monaco-editor is currently not compatible with ssr
// https://github.com/vercel/next.js/issues/31692
const Editor = dynamic(() => import("./EditorImpl"), {
ssr: false,
});
export default Editor;

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { EditorProps } from "@monaco-editor/react";
export const monacoOptions: Partial<EditorProps["options"]> = {
fontSize: 14,
padding: { top: 8 },
scrollbar: {
verticalScrollbarSize: 10,
alwaysConsumeMouseWheel: false,
},
minimap: {
enabled: false,
},
formatOnPaste: true,
formatOnType: true,
fontFamily: '"Source Code Pro", monospace',
glyphMargin: true,
autoClosingBrackets: "languageDefined",
autoClosingDelete: "always",
autoClosingOvertype: "always",
automaticLayout: true,
wordWrap: "on",
wrappingIndent: "deepIndent",
};

View File

@@ -0,0 +1,94 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { RefreshIcon, ShareIcon } from "@heroicons/react/outline";
import { CheckIcon } from "@heroicons/react/solid";
import clsx from "clsx";
import Link from "next/link";
import { useSnackbar } from "notistack";
import { useState } from "react";
import { defaultStore } from "../lib/defaultStore";
import { IconGitHub } from "./Icons/IconGitHub";
import Logo from "./Logo";
import { useStoreDispatch } from "./StoreContext";
export default function Header() {
const [showCheck, setShowCheck] = useState(false);
const dispatchStore = useStoreDispatch();
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
const handleReset = () => {
if (confirm("Are you sure you want to reset the playground?")) {
/*
Close open snackbars if any. This is necessary because when displaying
outputs (Preview or not), we only close previous snackbars if we received
new messages, which is needed in order to display "Bad URL" or success
messages when loading Playground for the first time. Otherwise, messages
such as "Bad URL" will be closed by the outputs calling `closeSnackbar`.
*/
closeSnackbar();
dispatchStore({ type: "setStore", payload: { store: defaultStore } });
}
};
const handleShare = () => {
navigator.clipboard.writeText(location.href).then(() => {
enqueueSnackbar("URL copied to clipboard");
setShowCheck(true);
// Show the check mark icon briefly after URL is copied
setTimeout(() => setShowCheck(false), 1000);
});
};
return (
<div className="fixed z-10 flex items-center justify-between w-screen px-5 py-3 bg-white border-b border-gray-200 h-14">
<div className="flex items-center flex-none h-full gap-2 text-lg">
<Logo
className={clsx(
"w-8 h-8 text-link",
process.env.NODE_ENV === "development" && "text-yellow-600"
)}
/>
<p className="hidden select-none sm:block">React Compiler Playground</p>
</div>
<div className="flex items-center text-[15px] gap-4">
<button
title="Reset Playground"
aria-label="Reset Playground"
className="flex items-center gap-1 transition-colors duration-150 ease-in text-secondary hover:text-link"
onClick={handleReset}
>
<RefreshIcon className="w-5 h-5" />
<p className="hidden sm:block">Reset</p>
</button>
<button
title="Copy sharable URL"
aria-label="Copy sharable URL"
className="flex items-center gap-1 transition-colors duration-150 ease-in text-secondary hover:text-link"
onClick={handleShare}
disabled={showCheck}
>
{!showCheck ? (
<ShareIcon className="w-5 h-5" />
) : (
<CheckIcon className="w-5 h-5 fill-blue-50" />
)}
<p className="hidden sm:block">Share</p>
</button>
<Link
href="https://github.com/facebook/react"
target="_blank"
rel="noreferrer noopener"
aria-label="Open on GitHub"
className="flex items-center gap-1 transition-colors duration-150 ease-in text-secondary hover:text-link"
>
<IconGitHub />
</Link>
</div>
</div>
);
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { memo } from "react";
export const IconGitHub = memo<JSX.IntrinsicElements["svg"]>(
function IconGitHub(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1.5em"
height="1.5em"
viewBox="0 -2 24 24"
fill="currentColor"
{...props}
>
<path d="M10 0a10 10 0 0 0-3.16 19.49c.5.1.68-.22.68-.48l-.01-1.7c-2.78.6-3.37-1.34-3.37-1.34-.46-1.16-1.11-1.47-1.11-1.47-.9-.62.07-.6.07-.6 1 .07 1.53 1.03 1.53 1.03.9 1.52 2.34 1.08 2.91.83.1-.65.35-1.09.63-1.34-2.22-.25-4.55-1.11-4.55-4.94 0-1.1.39-1.99 1.03-2.69a3.6 3.6 0 0 1 .1-2.64s.84-.27 2.75 1.02a9.58 9.58 0 0 1 5 0c1.91-1.3 2.75-1.02 2.75-1.02.55 1.37.2 2.4.1 2.64.64.7 1.03 1.6 1.03 2.69 0 3.84-2.34 4.68-4.57 4.93.36.31.68.92.68 1.85l-.01 2.75c0 .26.18.58.69.48A10 10 0 0 0 10 0"></path>
</svg>
);
}
);

View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// https://github.com/reactjs/reactjs.org/blob/main/beta/src/components/Logo.tsx
export default function Logo(props: JSX.IntrinsicElements["svg"]) {
return (
<svg
viewBox="0 0 410 369"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M204.995 224.552C226.56 224.552 244.042 207.07 244.042 185.506C244.042 163.941 226.56 146.459 204.995 146.459C183.43 146.459 165.948 163.941 165.948 185.506C165.948 207.07 183.43 224.552 204.995 224.552Z"
fill="currentColor"
/>
<path
d="M409.99 184.505C409.99 153.707 381.437 126.667 335.996 108.925C343.342 60.6535 334.19 22.3878 307.492 6.98883C283.649 -6.77511 250.631 -0.0395641 214.512 25.9753C211.316 28.2692 208.143 30.7097 204.97 33.2477C201.822 30.7097 198.65 28.2692 195.477 25.9753C159.359 -0.0395641 126.34 -6.79951 102.497 6.98883C75.8237 22.3878 66.6721 60.6291 74.0422 108.852C28.5529 126.618 0 153.682 0 184.505C0 215.303 28.5528 242.342 73.9934 260.084C66.6477 308.356 75.7993 346.621 102.497 362.02C110.575 366.682 119.727 369 129.684 369C149.085 369 171.61 360.215 195.477 343.034C198.674 340.74 201.847 338.3 205.019 335.762C208.167 338.3 211.34 340.74 214.512 343.034C238.38 360.239 260.905 369 280.306 369C290.263 369 299.415 366.682 307.492 362.02C331.335 348.256 342 316.287 337.534 271.993C337.143 268.089 336.631 264.135 335.996 260.109C381.461 242.367 409.99 215.327 409.99 184.505ZM225.934 41.8136C246.238 27.1955 265.127 19.5814 280.306 19.5814C286.871 19.5814 292.728 20.9968 297.731 23.8765C315.204 33.9798 322.672 62.9475 317.327 102.433C299.756 97.0401 280.306 92.9158 259.392 90.2802C246.872 73.8074 233.597 58.9453 220.003 46.2551C221.98 44.7421 223.957 43.229 225.934 41.8136ZM112.259 23.8765C117.262 20.9968 123.119 19.5814 129.684 19.5814C144.863 19.5814 163.752 27.1711 184.056 41.8136C186.033 43.229 188.01 44.7176 189.986 46.2551C176.393 58.9453 163.142 73.783 150.622 90.2558C129.732 92.8914 110.258 97.0401 92.687 102.409C87.3424 62.9475 94.7857 33.9798 112.259 23.8765ZM19.5233 184.505C19.5233 164.322 40.9014 143.359 77.776 128.253C81.9003 146.141 88.0502 165.054 96.1768 184.456C88.0014 203.881 81.8515 222.819 77.7272 240.732C40.9014 225.626 19.5233 204.687 19.5233 184.505ZM184.056 327.196C154.966 348.134 128.805 354.675 112.259 345.133C94.7857 335.029 87.3181 306.062 92.6626 266.576C110.234 271.969 129.684 276.093 150.598 278.729C163.117 295.202 176.393 310.064 189.986 322.754C188.01 324.292 186.033 325.78 184.056 327.196ZM204.995 310.04C180.591 287.685 157.138 257.815 137.347 223.551C132.051 214.4 121.344 191.396 117 182.489C113.535 190.786 110.112 198.398 107.427 206.5C109.623 210.575 118.092 229.213 120.434 233.288C125.071 241.317 129.928 249.127 134.931 256.692C120.898 254.227 107.915 251.055 96.1035 247.321C102.815 217.011 116.213 182.064 137.347 145.458C142.545 136.453 153.838 116.346 159.5 108C150.568 109.147 143.395 108.767 135 110.5C132.56 114.453 122.777 131.645 120.434 135.721C115.749 143.823 111.454 151.925 107.427 159.978C102.546 146.581 98.8124 133.744 96.1524 121.64C125.755 112.293 162.727 106.411 204.995 106.411C215.562 106.411 237.63 106.197 247.49 106.905C242.048 99.7544 237.38 93.2819 231.694 86.888C227.082 86.7416 209.705 86.888 204.995 86.888C195.672 86.888 186.545 87.2053 177.589 87.7422C186.472 77.1752 195.672 67.5111 204.995 58.9697C229.375 81.3239 252.851 111.195 272.643 145.458C277.841 154.463 289.073 175.426 293.49 184.505C296.98 176.207 300.281 168.64 302.99 160.489C300.793 156.389 291.898 139.747 289.555 135.696C284.918 127.667 280.062 119.858 275.059 112.317C289.092 114.782 302.075 117.954 313.886 121.688C307.175 151.998 293.777 186.945 272.643 223.551C267.445 232.556 252.651 253.178 246.99 261.524C255.922 260.377 265.595 258.663 273.99 256.93C276.43 252.976 287.212 237.364 289.555 233.288C294.216 225.235 298.512 217.182 302.489 209.153C307.224 222.185 310.982 234.997 313.715 247.394C284.138 256.741 247.214 262.598 204.995 262.598C194.428 262.598 169.859 261.208 160 260.5C165.442 267.65 171.304 275.095 176.99 281.489C181.602 281.635 200.285 282.121 204.995 282.121C214.317 282.121 223.444 281.804 232.401 281.267C223.493 291.834 214.317 301.498 204.995 310.04ZM297.731 345.133C281.185 354.699 254.999 348.159 225.934 327.196C223.957 325.78 221.98 324.292 220.003 322.754C233.597 310.064 246.848 295.226 259.367 278.753C280.233 276.118 299.659 271.993 317.205 266.625C317.547 269.089 317.888 271.554 318.132 273.97C321.72 309.649 314.277 335.566 297.731 345.133ZM332.262 240.756C328.065 222.599 321.842 203.686 313.813 184.578C321.988 165.152 328.138 146.215 332.262 128.302C369.088 143.408 390.466 164.322 390.466 184.505C390.466 204.687 369.113 225.626 332.262 240.756Z"
fill="currentColor"
/>
</svg>
);
}

View File

@@ -0,0 +1,84 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {
BanIcon,
ExclamationIcon,
InformationCircleIcon,
XIcon,
} from "@heroicons/react/solid";
import { CustomContentProps, SnackbarContent, useSnackbar } from "notistack";
import { forwardRef } from "react";
import { MessageLevel, MessageSource } from "../lib/stores";
// https://notistack.com/examples/advanced/custom-component#custom-variant-(typescript)
declare module "notistack" {
interface VariantOverrides {
message: {
title: string;
level: MessageLevel;
codeframe: string | undefined;
};
}
}
interface MessageProps extends CustomContentProps {
title: string;
level: MessageLevel;
source: MessageSource;
codeframe: string | undefined;
}
const Message = forwardRef<HTMLDivElement, MessageProps>(
({ id, title, level, source, codeframe }, ref) => {
const { closeSnackbar } = useSnackbar();
const isDismissible = source !== MessageSource.Playground;
return (
<SnackbarContent
ref={ref}
className="flex items-start justify-between gap-3 px-4 py-3 text-sm bg-white border rounded-md shadow w-toast"
>
<div className="flex gap-3 w-toast-body">
{level === MessageLevel.Warning ? (
<div className="flex items-center justify-center flex-none rounded-md w-7 h-7 bg-amber-100">
<ExclamationIcon className="w-5 h-5 text-amber-600" />
</div>
) : level === MessageLevel.Error ? (
<div className="flex items-center justify-center flex-none bg-red-100 rounded-md w-7 h-7">
<BanIcon className="w-5 h-5 text-red-600" />
</div>
) : (
<div className="flex items-center justify-center flex-none rounded-md bg-sky-100 w-7 h-7">
<InformationCircleIcon className="w-5 h-5 text-sky-600" />
</div>
)}
<div className="flex flex-col justify-center gap-1 w-toast-title">
<p className="w-full">{title}</p>
{codeframe ? (
<pre className="overflow-x-auto break-words whitespace-pre-wrap">
<code className="text-xs">{codeframe}</code>
</pre>
) : null}
</div>
</div>
{isDismissible ? (
<button
className="flex items-center justify-center flex-none transition-colors duration-150 ease-in rounded-md justify-self-end group w-7 h-7 hover:bg-gray-200"
onClick={() => closeSnackbar(id)}
>
<XIcon className="w-5 h-5 fill-gray-500 group-hover:fill-gray-800" />
</button>
) : null}
</SnackbarContent>
);
}
);
Message.displayName = "MessageComponent";
export default Message;

View File

@@ -0,0 +1,78 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { Dispatch, ReactNode } from "react";
import { useReducer } from "react";
import createContext from "../lib/createContext";
import { emptyStore } from "../lib/defaultStore";
import type { Store } from "../lib/stores";
import { saveStore } from "../lib/stores";
const StoreContext = createContext<Store>();
/**
* Hook to access the store.
*/
export const useStore = StoreContext.useContext;
const StoreDispatchContext = createContext<Dispatch<ReducerAction>>();
/**
* Hook to access the store dispatch function.
*/
export const useStoreDispatch = StoreDispatchContext.useContext;
/**
* Make Store and dispatch function available to all sub-components in children.
*/
export function StoreProvider({ children }: { children: ReactNode }) {
const [store, dispatch] = useReducer(storeReducer, emptyStore);
return (
<StoreContext.Provider value={store}>
<StoreDispatchContext.Provider value={dispatch}>
{children}
</StoreDispatchContext.Provider>
</StoreContext.Provider>
);
}
type ReducerAction =
| {
type: "setStore";
payload: {
store: Store;
};
}
| {
type: "updateFile";
payload: {
source: string;
};
};
function storeReducer(store: Store, action: ReducerAction): Store {
switch (action.type) {
case "setStore": {
const newStore = action.payload.store;
saveStore(newStore);
return newStore;
}
case "updateFile": {
const { source } = action.payload;
const newStore = {
...store,
source,
};
saveStore(newStore);
return newStore;
}
}
}

View File

@@ -0,0 +1,102 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { Resizable } from "re-resizable";
import React, { useCallback } from "react";
type TabsRecord = Map<string, React.ReactNode>;
export default function TabbedWindow(props: {
defaultTab: string | null;
tabs: TabsRecord;
tabsOpen: Set<string>;
setTabsOpen: (newTab: Set<string>) => void;
changedPasses: Set<string>;
}): React.ReactElement {
if (props.tabs.size === 0) {
return (
<div
className="flex items-center justify-center"
style={{ width: "calc(100vw - 650px)" }}
>
No compiler output detected, see errors below
</div>
);
}
return (
<div className="flex flex-row">
{Array.from(props.tabs.keys()).map((name) => {
return (
<TabbedWindowItem
name={name}
key={name}
tabs={props.tabs}
tabsOpen={props.tabsOpen}
setTabsOpen={props.setTabsOpen}
hasChanged={props.changedPasses.has(name)}
/>
);
})}
</div>
);
}
function TabbedWindowItem({
name,
tabs,
tabsOpen,
setTabsOpen,
hasChanged,
}: {
name: string;
tabs: TabsRecord;
tabsOpen: Set<string>;
setTabsOpen: (newTab: Set<string>) => void;
hasChanged: boolean;
}): React.ReactElement {
const isShow = tabsOpen.has(name);
const toggleTabs = useCallback(() => {
const nextState = new Set(tabsOpen);
if (nextState.has(name)) {
nextState.delete(name);
} else {
nextState.add(name);
}
setTabsOpen(nextState);
}, [tabsOpen, name, setTabsOpen]);
return (
<div key={name} className="flex flex-row">
{isShow ? (
<Resizable className="border-r" minWidth={550} enable={{ right: true }}>
<h2
title="Minimize tab"
aria-label="Minimize tab"
onClick={toggleTabs}
className={`p-4 duration-150 ease-in border-b cursor-pointer border-grey-200 ${hasChanged ? "font-bold" : "font-light"} text-secondary hover:text-link`}
>
- {name}
</h2>
{tabs.get(name) ?? <div>No output for {name}</div>}
</Resizable>
) : (
<div className="relative items-center h-full px-1 py-6 align-middle border-r border-grey-200">
<button
title={`Expand compiler tab: ${name}`}
aria-label={`Expand compiler tab: ${name}`}
style={{ transform: "rotate(90deg) translate(-50%)" }}
onClick={toggleTabs}
className={`flex-grow-0 w-5 transition-colors duration-150 ease-in ${hasChanged ? "font-bold" : "font-light"} text-secondary hover:text-link`}
>
{name}
</button>
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,10 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export { default as Editor } from "./Editor";
export { default as Header } from "./Header";
export { StoreProvider } from "./StoreContext";

View File

@@ -0,0 +1,8 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export { default as useMountEffect } from "./useMountEffect";

View File

@@ -0,0 +1,13 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { EffectCallback } from "react";
import { useEffect } from "react";
export default function useMountEffect(effect: EffectCallback) {
return useEffect(effect, []);
}

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React from "react";
/**
* Replacement to React.createContext.
*
* Does not take any default value and avoids non-null assertions when using
* the value of the context, like the following scenario.
*
* ```ts
* const StoreDispatchContext = useContext<Dispatch<ReducerAction>>(null);
* const dispatchStore = useContext(StoreDispatchContext);
* ...
* dipatchStore!({ ... });
* ```
*
* Instead, it throws an error when `useContext` is not called within a
* Provider with a value.
*/
export default function createContext<T>() {
const context = React.createContext<T | null>(null);
function useContext() {
const c = React.useContext(context);
if (!c)
throw new Error("useContext must be within a Provider with a value");
return c;
}
return { useContext, Provider: context.Provider };
}

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { Store } from "./stores";
const index = `\
export default function MyApp() {
return <div>Hello World</div>;
}
`;
export const defaultStore: Store = {
source: index,
};
export const emptyStore: Store = {
source: "",
};

View File

@@ -0,0 +1,89 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { Monaco } from "@monaco-editor/react";
import {
CompilerErrorDetail,
ErrorSeverity,
} from "babel-plugin-react-compiler/src";
import { MarkerSeverity, type editor } from "monaco-editor";
function mapReactCompilerSeverityToMonaco(
level: ErrorSeverity,
monaco: Monaco
): MarkerSeverity {
switch (level) {
case ErrorSeverity.Todo:
return monaco.MarkerSeverity.Warning;
default:
return monaco.MarkerSeverity.Error;
}
}
function mapReactCompilerDiagnosticToMonacoMarker(
detail: CompilerErrorDetail,
monaco: Monaco
): editor.IMarkerData | null {
if (detail.loc == null || typeof detail.loc === "symbol") {
return null;
}
const severity = mapReactCompilerSeverityToMonaco(detail.severity, monaco);
let message = detail.printErrorMessage();
return {
severity,
message,
startLineNumber: detail.loc.start.line,
startColumn: detail.loc.start.column + 1,
endLineNumber: detail.loc.end.line,
endColumn: detail.loc.end.column + 1,
};
}
type ReactCompilerMarkerConfig = {
monaco: Monaco;
model: editor.ITextModel;
details: CompilerErrorDetail[];
};
let decorations: string[] = [];
export function renderReactCompilerMarkers({
monaco,
model,
details,
}: ReactCompilerMarkerConfig): void {
let markers = [];
for (const detail of details) {
const marker = mapReactCompilerDiagnosticToMonacoMarker(detail, monaco);
if (marker == null) {
continue;
}
markers.push(marker);
}
if (markers.length > 0) {
monaco.editor.setModelMarkers(model, "owner", markers);
const newDecorations = markers.map((marker) => {
return {
range: new monaco.Range(
marker.startLineNumber,
marker.startColumn,
marker.endLineNumber,
marker.endColumn
),
options: {
isWholeLine: true,
glyphMarginClassName: "bg-red-300",
},
};
});
decorations = model.deltaDecorations(decorations, newDecorations);
} else {
monaco.editor.setModelMarkers(model, "owner", []);
decorations = model.deltaDecorations(
model.getAllDecorations().map((d) => d.id),
[]
);
}
}

View File

@@ -0,0 +1,9 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export * from "./messages";
export * from "./store";

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export enum MessageSource {
Babel,
Forget,
Playground,
}
export enum MessageLevel {
Info,
Warning,
Error,
}
export interface Message {
title: string;
level: MessageLevel;
source: MessageSource; // Can be used to further style messages differently.
codeframe: string | undefined;
}
export function createMessage(
message: string,
level: MessageLevel,
source: MessageSource
): Message {
const [title, ...body] = message.split("\n");
const codeframe = body.length > 0 ? body.join("\n") : undefined;
return {
source,
level,
title,
codeframe,
};
}

View File

@@ -0,0 +1,67 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import invariant from "invariant";
import {
compressToEncodedURIComponent,
decompressFromEncodedURIComponent,
} from "lz-string";
import { defaultStore } from "../defaultStore";
/**
* Global Store for Playground
*/
export interface Store {
source: string;
}
export function encodeStore(store: Store): string {
return compressToEncodedURIComponent(JSON.stringify(store));
}
export function decodeStore(hash: string): Store {
return JSON.parse(decompressFromEncodedURIComponent(hash));
}
/**
* Serialize, encode, and save @param store to localStorage and update URL.
*/
export function saveStore(store: Store) {
const hash = encodeStore(store);
localStorage.setItem("playgroundStore", hash);
history.replaceState({}, "", `#${hash}`);
}
/**
* Check if @param raw is a valid Store by if
* - it has a `source` property and is a string
*/
function isValidStore(raw: unknown): raw is Store {
return (
raw != null &&
typeof raw == "object" &&
"source" in raw &&
typeof raw["source"] === "string"
);
}
/**
* Deserialize, decode, and initialize @param store from URL and then
* localStorage. Throw an error if Store is malformed.
*/
export function initStoreFromUrlOrLocalStorage(): Store {
const encodedSourceFromUrl = location.hash.slice(1);
const encodedSourceFromLocal = localStorage.getItem("playgroundStore");
const encodedSource = encodedSourceFromUrl || encodedSourceFromLocal;
// No data in the URL and no data in the localStorage to fallback to.
// Initialize with the default store.
if (!encodedSource) return defaultStore;
const raw = decodeStore(encodedSource);
invariant(isValidStore(raw), "Invalid Store");
return raw;
}

View File

@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
const path = require("path");
const nextConfig = {
reactStrictMode: true,
webpack: (config, options) => {
// Load *.d.ts files as strings using https://webpack.js.org/guides/asset-modules/#source-assets.
config.module.rules.push({
test: /\.d\.ts/,
type: "asset/source",
});
// Monaco Editor
if (!options.isServer) {
config.plugins.push(
new MonacoWebpackPlugin({
languages: ["typescript", "javascript"],
filename: "static/[name].worker.js",
})
);
}
config.resolve.alias = {
...config.resolve.alias,
"react-compiler-runtime": path.resolve(
__dirname,
"../../packages/react-compiler-runtime"
),
};
return config;
},
transpilePackages: ["monaco-editor"],
};
module.exports = nextConfig;

View File

@@ -0,0 +1,61 @@
{
"name": "playground",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "NODE_ENV=development next dev",
"build": "next build && node ./scripts/downloadFonts.js",
"vercel-build": "yarn workspaces run build",
"start": "next start",
"lint": "next lint",
"postinstall": "yarn playwright install",
"test": "playwright test"
},
"dependencies": {
"@babel/core": "^7.19.1",
"@babel/parser": "^7.19.1",
"@babel/plugin-syntax-typescript": "^7.18.6",
"@babel/plugin-transform-block-scoping": "^7.18.9",
"@babel/plugin-transform-modules-commonjs": "^7.18.6",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@babel/traverse": "^7.19.1",
"@babel/types": "^7.19.0",
"@heroicons/react": "^1.0.6",
"@monaco-editor/react": "^4.4.6",
"@playwright/test": "^1.42.1",
"@use-gesture/react": "^10.2.22",
"hermes-eslint": "^0.14.0",
"invariant": "^2.2.4",
"lz-string": "^1.5.0",
"monaco-editor": "^0.34.1",
"next": "^13.5.6",
"notistack": "^3.0.0-alpha.7",
"prettier": "3.0.3",
"pretty-format": "^29.3.1",
"re-resizable": "^6.9.16",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-compiler-runtime": "*"
},
"devDependencies": {
"@types/node": "18.11.9",
"@types/prettier": "^2.7.1",
"@types/react": "18.0.25",
"@types/react-dom": "18.0.9",
"autoprefixer": "^10.4.13",
"clsx": "^1.2.1",
"eslint": "^8.28.0",
"eslint-config-next": "^13.5.6",
"monaco-editor-webpack-plugin": "^7.1.0",
"postcss": "^8.4.31",
"tailwindcss": "^3.2.4"
},
"resolutions": {
"./**/@babel/parser": "7.7.4",
"./**/@babel/types": "7.7.4",
"@babel/core": "7.2.0",
"@babel/traverse": "7.1.6",
"@babel/generator": "7.2.0"
}
}

View File

@@ -0,0 +1,84 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { defineConfig, devices } from "@playwright/test";
import path from "path";
// Use process.env.PORT by default and fallback to port 3000
const PORT = process.env.PORT || 3000;
// Set webServer.url and use.baseURL with the location of the WebServer respecting the correct set port
const baseURL = `http://localhost:${PORT}`;
// Reference: https://playwright.dev/docs/test-configuration
export default defineConfig({
// Timeout per test
timeout: 30 * 1000,
// Test directory
testDir: path.join(__dirname, "__tests__/e2e"),
// If a test fails, retry it additional 2 times
retries: 2,
// Artifacts folder where screenshots, videos, and traces are stored.
outputDir: "test-results/",
// Note: we only use text snapshots, so its safe to omit the host environment name
snapshotPathTemplate: "{testDir}/__snapshots__/{testFilePath}/{arg}{ext}",
// Run your local dev server before starting the tests:
// https://playwright.dev/docs/test-advanced#launching-a-development-web-server-during-the-tests
webServer: {
command:
"yarn workspace babel-plugin-react-compiler build && yarn workspace react-compiler-runtime build && yarn dev",
url: baseURL,
timeout: 300 * 1000,
reuseExistingServer: !process.env.CI,
},
use: {
// Use baseURL so to make navigations relative.
// More information: https://playwright.dev/docs/api/class-testoptions#test-options-base-url
baseURL,
// Retry a test if its failing with enabled tracing. This allows you to analyze the DOM, console logs, network traffic etc.
// More information: https://playwright.dev/docs/trace-viewer
trace: "retry-with-trace",
// All available context options: https://playwright.dev/docs/api/class-browser#browser-new-context
// contextOptions: {
// ignoreHTTPSErrors: true,
// },
},
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
// {
// name: 'Desktop Firefox',
// use: {
// ...devices['Desktop Firefox'],
// },
// },
// {
// name: 'Desktop Safari',
// use: {
// ...devices['Desktop Safari'],
// },
// },
// Test against mobile viewports.
// {
// name: "Mobile Chrome",
// use: {
// ...devices["Pixel 5"],
// },
// },
// {
// name: "Mobile Safari",
// use: devices["iPhone 12"],
// },
],
});

View File

@@ -0,0 +1,13 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@@ -0,0 +1,16 @@
{
"name": "React Compiler Playground",
"short_name": "React REPL",
"display": "standalone",
"start_url": "/",
"theme_color": "#fff",
"background_color": "#000000",
"orientation": "portrait",
"icons": [
{
"src": "/icon-180x180.png",
"type": "image/png",
"sizes": "180x180"
}
]
}

View File

@@ -0,0 +1,4 @@
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,20 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const { execSync } = require("child_process");
// So that we don't need to check them into the repo.
// See https://github.com/reactjs/reactjs.org/blob/main/beta/scripts/downloadFonts.js.
execSync(
"curl https://conf.reactjs.org/fonts/Optimistic_Display_W_Lt.woff2 --output public/fonts/Optimistic_Display_W_Lt.woff2"
);
execSync(
"curl https://conf.reactjs.org/fonts/Optimistic_Display_W_Md.woff2 --output public/fonts/Optimistic_Display_W_Md.woff2"
);
execSync(
"curl https://conf.reactjs.org/fonts/Optimistic_Display_W_Bd.woff2 --output public/fonts/Optimistic_Display_W_Bd.woff2"
);

View File

@@ -0,0 +1,71 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
@font-face {
font-family: "Source Code Pro";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("/fonts/Source-Code-Pro-Regular.woff2") format("woff2");
}
@font-face {
font-family: "Optimistic Display";
src: url("/fonts/Optimistic_Display_W_Lt.woff2") format("woff2");
font-weight: 300;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Optimistic Display";
src: url("/fonts/Optimistic_Display_W_Md.woff2") format("woff2");
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Optimistic Display";
src: url("/fonts/Optimistic_Display_W_Bd.woff2") format("woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
}
html,
body {
padding: 0;
margin: 0;
}
a {
color: inherit;
text-decoration: none;
}
* {
box-sizing: border-box;
}
}
@layer utilities {
/* Chrome, Safari, Opera */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none; /* IE, Edge */
scrollbar-width: none; /* Firefox */
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const defaultTheme = require("tailwindcss/defaultTheme");
const colors = require("./colors");
module.exports = {
content: [
"./app/**/*.{js,ts,jsx,tsx}",
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
"./lib/forgetMonacoDiagnostics.ts",
],
theme: {
extend: {
colors,
width: {
toast: "min(900px, 100vw - 40px)",
"toast-body": "calc(100% - 60px)",
"toast-title": "calc(100% - 40px)",
},
height: {
content: "calc(100vh - 45px)",
monaco: "calc(100vh - 93px)",
monaco_small: "calc(100vh - 129px)",
},
fontFamily: {
sans: [
"Optimistic Display",
"-apple-system",
...defaultTheme.fontFamily.sans,
],
},
},
},
plugins: [],
};

View File

@@ -0,0 +1,36 @@
{
"compilerOptions": {
"target": "ES2015",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
]
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}

View File

@@ -0,0 +1,13 @@
{
"headers": [
{
"source": "/fonts/(.*).woff2",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
}
]
}

View File

@@ -0,0 +1,21 @@
[package]
name = "react_build_hir"
version = "0.1.0"
publish = false
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
repository.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
react_hir = { workspace = true }
react_estree = { workspace = true}
indexmap = { workspace = true }
react_diagnostics = { workspace = true }
react_semantic_analysis = { workspace = true }
thiserror = { workspace = true }

View File

@@ -0,0 +1,3 @@
# Build-HIR
This crate converts from `react_estree` into React Compiler's HIR format as the first phase of compilation.

View File

@@ -0,0 +1,746 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use std::collections::HashSet;
use react_diagnostics::Diagnostic;
use react_estree::{
AssignmentPropertyOrRestElement, AssignmentTarget, BlockStatement, Expression,
ExpressionOrSpread, ExpressionOrSuper, ForInit, Function, IntoFunction, JsValue, Pattern,
Statement, VariableDeclaration, VariableDeclarationKind,
};
use react_hir::{
ArrayDestructureItem, BlockKind, BranchTerminal, Destructure, DestructurePattern, Environment,
ForTerminal, GotoKind, Identifier, IdentifierOperand, InstructionKind, InstructionValue,
JSXAttribute, JSXElement, LValue, LoadGlobal, LoadLocal, ObjectDestructureItem,
ObjectDestructureProperty, PlaceOrSpread, TerminalValue,
};
use crate::builder::{Builder, LoopScope};
use crate::context::get_context_identifiers;
use crate::error::BuildHIRError;
/// Converts a React function in ESTree format into HIR. Returns the HIR
/// if it was constructed sucessfully, otherwise a list of diagnostics
/// if the input could be not be converted to HIR.
///
/// Failures generally include nonsensical input (`delete 1`) or syntax
/// that is not yet supported.
pub fn build(env: &Environment, fun: &Function) -> Result<Box<react_hir::Function>, Diagnostic> {
let mut builder = Builder::new(env);
let mut params = Vec::with_capacity(fun.params.len());
for param in &fun.params {
match param {
Pattern::Identifier(param) => {
let identifier = lower_identifier_for_assignment(
env,
&mut builder,
InstructionKind::Let,
param,
)?;
params.push(identifier);
}
_ => {
return Err(Diagnostic::todo(
"Support non-identifier params",
param.range(),
));
}
}
}
match &fun.body {
Some(react_estree::FunctionBody::BlockStatement(body)) => {
lower_block_statement(env, &mut builder, body)?
}
Some(react_estree::FunctionBody::Expression(body)) => {
lower_expression(env, &mut builder, body)?;
}
None => {
return Err(Diagnostic::invalid_syntax(
BuildHIRError::EmptyFunction,
fun.range,
));
}
}
// In case the function did not explicitly return, terminate the final
// block with an explicit `return undefined`. If the function *did* return,
// this will be unreachable and get pruned later.
let implicit_return_value = builder.push(InstructionValue::Primitive(react_hir::Primitive {
value: JsValue::Undefined,
}));
builder.terminate(
TerminalValue::Return(react_hir::ReturnTerminal {
value: implicit_return_value,
}),
react_hir::BlockKind::Block,
);
let body = builder.build()?;
Ok(Box::new(react_hir::Function {
id: fun.id.as_ref().map(|id| id.name.clone()),
body,
params,
// TODO: populate context!
context: Default::default(),
is_async: fun.is_async,
is_generator: fun.is_generator,
}))
}
fn lower_block_statement(
env: &Environment,
builder: &mut Builder,
stmt: &BlockStatement,
) -> Result<(), Diagnostic> {
for stmt in &stmt.body {
lower_statement(env, builder, stmt, None)?;
}
Ok(())
}
/// Convert a statement to HIR. This will often result in multiple instructions and blocks
/// being created as statements often describe control flow.
fn lower_statement(
env: &Environment,
builder: &mut Builder,
stmt: &Statement,
label: Option<String>,
) -> Result<(), Diagnostic> {
match stmt {
Statement::BlockStatement(stmt) => {
lower_block_statement(env, builder, stmt)?;
}
Statement::BreakStatement(stmt) => {
let block = builder.resolve_break(stmt.label.as_ref())?;
builder.terminate(
TerminalValue::Goto(react_hir::GotoTerminal {
block,
kind: GotoKind::Break,
}),
BlockKind::Block,
);
}
Statement::ContinueStatement(stmt) => {
let block = builder.resolve_continue(stmt.label.as_ref())?;
builder.terminate(
TerminalValue::Goto(react_hir::GotoTerminal {
block,
kind: GotoKind::Continue,
}),
BlockKind::Block,
);
}
Statement::ReturnStatement(stmt) => {
let value = match &stmt.argument {
Some(argument) => lower_expression(env, builder, argument)?,
None => builder.push(InstructionValue::Primitive(react_hir::Primitive {
value: JsValue::Undefined,
})),
};
builder.terminate(
TerminalValue::Return(react_hir::ReturnTerminal { value }),
BlockKind::Block,
);
}
Statement::ExpressionStatement(stmt) => {
lower_expression(env, builder, &stmt.expression)?;
}
Statement::EmptyStatement(_) => {
// no-op
}
Statement::VariableDeclaration(stmt) => {
lower_variable_declaration(env, builder, stmt)?;
}
Statement::IfStatement(stmt) => {
// block for what follows the if statement, though this may
// not be reachable
let fallthrough_block = builder.reserve(BlockKind::Block);
let consequent_block = builder.enter(BlockKind::Block, |builder| {
lower_statement(env, builder, &stmt.consequent, None)?;
Ok(TerminalValue::Goto(react_hir::GotoTerminal {
block: fallthrough_block.id,
kind: GotoKind::Break,
}))
})?;
let alternate_block = builder.enter(BlockKind::Block, |builder| {
if let Some(alternate) = &stmt.alternate {
lower_statement(env, builder, alternate, None)?;
}
Ok(TerminalValue::Goto(react_hir::GotoTerminal {
block: fallthrough_block.id,
kind: GotoKind::Break,
}))
})?;
let test = lower_expression(env, builder, &stmt.test)?;
let terminal = TerminalValue::If(react_hir::IfTerminal {
test,
consequent: consequent_block,
alternate: alternate_block,
fallthrough: Some(fallthrough_block.id),
});
builder.terminate_with_fallthrough(terminal, fallthrough_block);
}
Statement::ForStatement(stmt) => {
// Block for the loop's test condition
let test_block = builder.reserve(BlockKind::Loop);
// Block for code following the loop
let fallthrough_block = builder.reserve(BlockKind::Block);
let init_block = builder.enter(BlockKind::Loop, |builder| {
if let Some(ForInit::VariableDeclaration(decl)) = &stmt.init {
lower_variable_declaration(env, builder, decl)?;
Ok(TerminalValue::Goto(react_hir::GotoTerminal {
block: test_block.id,
kind: GotoKind::Break,
}))
} else {
Err(Diagnostic::todo(
BuildHIRError::ForStatementIsMissingInitializer,
None,
))
}
})?;
let update_block = stmt
.update
.as_ref()
.map(|update| {
builder.enter(BlockKind::Loop, |builder| {
lower_expression(env, builder, update)?;
Ok(TerminalValue::Goto(react_hir::GotoTerminal {
block: test_block.id,
kind: GotoKind::Break,
}))
})
})
.transpose()?;
let body_block = builder.enter(BlockKind::Block, |builder| {
let loop_ = LoopScope {
label,
continue_block: update_block.unwrap_or(test_block.id),
break_block: fallthrough_block.id,
};
builder.enter_loop(loop_, |builder| {
lower_statement(env, builder, &stmt.body, None)?;
Ok(TerminalValue::Goto(react_hir::GotoTerminal {
block: update_block.unwrap_or(test_block.id),
kind: GotoKind::Continue,
}))
})
})?;
let terminal = TerminalValue::For(ForTerminal {
body: body_block,
init: init_block,
test: test_block.id,
fallthrough: fallthrough_block.id,
update: update_block,
});
builder.terminate_with_fallthrough(terminal, test_block);
if let Some(test) = &stmt.test {
let test_value = lower_expression(env, builder, test)?;
let terminal = TerminalValue::Branch(BranchTerminal {
test: test_value,
consequent: body_block,
alternate: fallthrough_block.id,
});
builder.terminate_with_fallthrough(terminal, fallthrough_block);
} else {
return Err(Diagnostic::todo(
BuildHIRError::ForStatementIsMissingTest,
stmt.range,
));
}
}
_ => todo!("Lower {stmt:#?}"),
}
Ok(())
}
fn lower_variable_declaration(
env: &Environment,
builder: &mut Builder,
stmt: &VariableDeclaration,
) -> Result<(), Diagnostic> {
let kind = match stmt.kind {
VariableDeclarationKind::Const => InstructionKind::Const,
VariableDeclarationKind::Let => InstructionKind::Let,
VariableDeclarationKind::Var => {
return Err(Diagnostic::unsupported(
BuildHIRError::VariableDeclarationKindIsVar,
stmt.range,
));
}
};
for declaration in &stmt.declarations {
if let Some(init) = &declaration.init {
let value = lower_expression(env, builder, init)?;
lower_assignment_pattern(env, builder, kind, &declaration.id, value)?;
} else {
match &declaration.id {
Pattern::Identifier(id) => {
let identifier = env.resolve_variable_declaration(id.as_ref(), &id.name);
if let Some(identifier) = identifier {
builder.push(InstructionValue::DeclareLocal(react_hir::DeclareLocal {
lvalue: LValue {
identifier: IdentifierOperand {
identifier,
effect: None,
},
kind,
},
}));
} else {
return Err(Diagnostic::invariant(
BuildHIRError::VariableDeclarationBindingIsNonLocal,
id.range,
));
}
}
_ => {
return Err(Diagnostic::invalid_syntax(
"Expected an identifier for variable declaration without an intializer. Destructuring requires an initial value",
declaration.range,
));
}
}
}
}
Ok(())
}
/// Converts an ESTree Expression into an HIR InstructionValue. Note that while only a single
/// InstructionValue is returned, this function is recursive and may cause multiple instructions
/// to be emitted, possibly across multiple basic blocks (in the case of expressions with control
/// flow semenatics such as logical, conditional, and optional expressions).
fn lower_expression(
env: &Environment,
builder: &mut Builder,
expr: &Expression,
) -> Result<IdentifierOperand, Diagnostic> {
let value = match expr {
Expression::Identifier(expr) => {
let identifier = env.resolve_variable_reference(expr.as_ref());
if let Some(identifier) = identifier {
let place = IdentifierOperand {
effect: None,
identifier,
};
InstructionValue::LoadLocal(LoadLocal { place })
} else {
InstructionValue::LoadGlobal(LoadGlobal {
name: expr.name.clone(),
})
}
}
Expression::Literal(expr) => InstructionValue::Primitive(react_hir::Primitive {
value: expr.value.clone(),
}),
Expression::NumericLiteral(expr) => InstructionValue::Primitive(react_hir::Primitive {
value: JsValue::Number(expr.value),
}),
Expression::BooleanLiteral(expr) => InstructionValue::Primitive(react_hir::Primitive {
value: JsValue::Boolean(expr.value),
}),
Expression::StringLiteral(expr) => InstructionValue::Primitive(react_hir::Primitive {
value: JsValue::String(expr.value.clone()),
}),
Expression::NullLiteral(_expr) => InstructionValue::Primitive(react_hir::Primitive {
value: JsValue::Null,
}),
Expression::ArrayExpression(expr) => {
let mut elements = Vec::with_capacity(expr.elements.len());
for expr in &expr.elements {
let element = match expr {
Some(react_estree::ExpressionOrSpread::SpreadElement(expr)) => Some(
PlaceOrSpread::Spread(lower_expression(env, builder, &expr.argument)?),
),
Some(react_estree::ExpressionOrSpread::Expression(expr)) => {
Some(PlaceOrSpread::Place(lower_expression(env, builder, expr)?))
}
None => None,
};
elements.push(element);
}
InstructionValue::Array(react_hir::Array { elements })
}
Expression::AssignmentExpression(expr) => match expr.operator {
react_estree::AssignmentOperator::Equals => {
let right = lower_expression(env, builder, &expr.right)?;
return lower_assignment(
env,
builder,
InstructionKind::Reassign,
&expr.left,
right,
);
}
_ => todo!("lower assignment expr {:#?}", expr),
},
Expression::BinaryExpression(expr) => {
let left = lower_expression(env, builder, &expr.left)?;
let right = lower_expression(env, builder, &expr.right)?;
InstructionValue::Binary(react_hir::Binary {
left,
operator: expr.operator,
right,
})
}
Expression::FunctionExpression(expr) => {
InstructionValue::Function(lower_function(env, builder, expr.as_ref())?)
}
Expression::ArrowFunctionExpression(expr) => {
InstructionValue::Function(lower_function(env, builder, expr.as_ref())?)
}
Expression::CallExpression(expr) => {
let callee_expr = match &expr.callee {
ExpressionOrSuper::Super(callee) => {
return Err(Diagnostic::unsupported(
BuildHIRError::UnsupportedSuperExpression,
callee.range,
));
}
ExpressionOrSuper::Expression(callee) => callee,
};
if matches!(&callee_expr, Expression::MemberExpression(_)) {
return Err(Diagnostic::todo("Support method calls", expr.range));
}
let callee = lower_expression(env, builder, callee_expr)?;
let arguments = lower_arguments(env, builder, &expr.arguments)?;
InstructionValue::Call(react_hir::Call { callee, arguments })
}
Expression::JSXElement(expr) => {
InstructionValue::JSXElement(lower_jsx_element(env, builder, expr)?)
}
_ => todo!("Lower expr {expr:#?}"),
};
Ok(builder.push(value))
}
fn lower_arguments(
env: &Environment,
builder: &mut Builder,
args: &[ExpressionOrSpread],
) -> Result<Vec<PlaceOrSpread>, Diagnostic> {
let mut arguments = Vec::with_capacity(args.len());
for arg in args {
let element = match arg {
react_estree::ExpressionOrSpread::SpreadElement(arg) => {
PlaceOrSpread::Spread(lower_expression(env, builder, &arg.argument)?)
}
react_estree::ExpressionOrSpread::Expression(arg) => {
PlaceOrSpread::Place(lower_expression(env, builder, arg)?)
}
};
arguments.push(element);
}
Ok(arguments)
}
fn lower_function<T: IntoFunction>(
env: &Environment,
_builder: &mut Builder,
function: &T,
) -> Result<react_hir::FunctionExpression, Diagnostic> {
let context_identifiers = get_context_identifiers(env, function);
let mut context = Vec::new();
let mut seen = HashSet::new();
for declaration_id in context_identifiers {
if let Some(identifier) = env.resolve_declaration_id(declaration_id) {
if !seen.insert(identifier.id) {
continue;
}
context.push(IdentifierOperand {
effect: None,
identifier,
});
}
}
let mut fun = build(env, function.function())?;
fun.context = context;
Ok(react_hir::FunctionExpression {
// TODO: collect dependencies!
dependencies: Default::default(),
lowered_function: fun,
})
}
fn lower_jsx_element(
env: &Environment,
builder: &mut Builder,
expr: &react_estree::JSXElement,
) -> Result<JSXElement, Diagnostic> {
let props: Result<Vec<JSXAttribute>, Diagnostic> = expr
.opening_element
.attributes
.iter()
.map(|attr| lower_jsx_attribute(env, builder, attr))
.collect();
let _props = props?;
let children: Result<Vec<IdentifierOperand>, Diagnostic> = expr
.children
.iter()
.map(|child| {
let child = lower_jsx_child(env, builder, child)?;
Ok(child)
})
.collect();
let _children = children?;
todo!("lower jsx element");
// Ok(JSXElement {
// tag: todo!(),
// props,
// children: if children.is_empty() {
// None
// } else {
// Some(children)
// },
// })
}
fn lower_jsx_attribute(
_env: &Environment,
_builder: &mut Builder,
_attr: &react_estree::JSXAttributeOrSpread,
) -> Result<JSXAttribute, Diagnostic> {
todo!("lower jsx attribute")
}
fn lower_jsx_child(
_env: &Environment,
_builder: &mut Builder,
_child: &react_estree::JSXChildItem,
) -> Result<IdentifierOperand, Diagnostic> {
todo!("lower jsx child")
}
fn lower_assignment(
env: &Environment,
builder: &mut Builder,
kind: InstructionKind,
lvalue: &AssignmentTarget,
value: IdentifierOperand,
) -> Result<IdentifierOperand, Diagnostic> {
Ok(match lvalue {
AssignmentTarget::Pattern(lvalue) => {
lower_assignment_pattern(env, builder, kind, lvalue, value)?
}
_ => todo!("lower assignment for {:#?}", lvalue),
})
}
// TODO: change the success type to void, no caller uses it
fn lower_assignment_pattern(
env: &Environment,
builder: &mut Builder,
kind: InstructionKind,
lvalue: &Pattern,
value: IdentifierOperand,
) -> Result<IdentifierOperand, Diagnostic> {
Ok(match lvalue {
Pattern::Identifier(lvalue) => {
let identifier = lower_identifier_for_assignment(env, builder, kind, lvalue)?;
builder.push(InstructionValue::StoreLocal(react_hir::StoreLocal {
lvalue: LValue { identifier, kind },
value,
}))
}
Pattern::ArrayPattern(lvalue) => {
let mut items = Vec::with_capacity(lvalue.elements.len());
let mut followups: Vec<(Identifier, &Pattern)> = Vec::new();
for element in &lvalue.elements {
match element {
None => items.push(ArrayDestructureItem::Hole),
Some(Pattern::Identifier(element)) => {
let identifier =
lower_identifier_for_assignment(env, builder, kind, element)?;
items.push(ArrayDestructureItem::Value(identifier));
}
Some(Pattern::RestElement(element)) => {
if let Pattern::Identifier(element) = &element.argument {
let identifier = lower_identifier_for_assignment(
env,
builder,
kind,
element.as_ref(),
)?;
items.push(ArrayDestructureItem::Spread(identifier));
} else {
let temporary = env.new_temporary();
items.push(ArrayDestructureItem::Spread(IdentifierOperand {
identifier: temporary.clone(),
effect: None,
}));
followups.push((temporary, &element.argument));
}
}
Some(element) => {
let temporary = env.new_temporary();
items.push(ArrayDestructureItem::Value(IdentifierOperand {
identifier: temporary.clone(),
effect: None,
}));
followups.push((temporary, element));
}
}
}
let temporary = builder.push(InstructionValue::Destructure(Destructure {
kind,
pattern: DestructurePattern::Array(items),
value,
}));
for (temporary, pattern) in followups {
lower_assignment_pattern(
env,
builder,
kind,
pattern,
IdentifierOperand {
identifier: temporary,
effect: None,
},
)?;
}
temporary
}
Pattern::ObjectPattern(lvalue) => {
let mut properties = Vec::with_capacity(lvalue.properties.len());
let mut followups: Vec<(Identifier, &Pattern)> = Vec::new();
for property in &lvalue.properties {
match property {
AssignmentPropertyOrRestElement::RestElement(property) => {
if let Pattern::Identifier(element) = &property.argument {
let identifier = lower_identifier_for_assignment(
env,
builder,
kind,
element.as_ref(),
)?;
properties.push(ObjectDestructureItem::Spread(identifier));
} else {
let temporary = env.new_temporary();
properties.push(ObjectDestructureItem::Spread(IdentifierOperand {
identifier: temporary.clone(),
effect: None,
}));
followups.push((temporary, &property.argument));
}
}
AssignmentPropertyOrRestElement::AssignmentProperty(property) => {
if property.is_computed {
return Err(Diagnostic::todo(
"Handle computed properties in ObjectPattern",
property.range,
));
}
let key = if let Expression::Identifier(key) = &property.key {
key.name.as_str()
} else {
return Err(Diagnostic::todo(
"Support non-identifier object keys in non-computed ObjectPattern",
property.range,
));
};
if let Pattern::Identifier(value) = &property.value {
let value = lower_identifier_for_assignment(env, builder, kind, value)?;
properties.push(ObjectDestructureItem::Property(
ObjectDestructureProperty {
name: key.to_string(),
value,
},
));
} else {
let temporary = env.new_temporary();
properties.push(ObjectDestructureItem::Property(
ObjectDestructureProperty {
name: key.to_string(),
value: IdentifierOperand {
identifier: temporary.clone(),
effect: None,
},
},
));
followups.push((temporary, &property.value));
}
}
}
}
let temporary = builder.push(InstructionValue::Destructure(Destructure {
kind,
pattern: DestructurePattern::Object(properties),
value,
}));
for (temporary, pattern) in followups {
lower_assignment_pattern(
env,
builder,
kind,
pattern,
IdentifierOperand {
identifier: temporary,
effect: None,
},
)?;
}
temporary
}
_ => todo!("lower assignment pattern for {:#?}", lvalue),
})
}
fn lower_identifier_for_assignment(
env: &Environment,
_builder: &mut Builder,
kind: InstructionKind,
node: &react_estree::Identifier,
) -> Result<IdentifierOperand, Diagnostic> {
match kind {
InstructionKind::Reassign => {
let identifier = env.resolve_variable_reference(node);
if let Some(identifier) = identifier {
Ok(IdentifierOperand {
identifier,
effect: None,
})
} else {
// Reassigning a global
Err(
Diagnostic::invalid_react(BuildHIRError::ReassignedGlobal, node.range)
.annotate(format!("Cannot reassign `{}`", &node.name), node.range),
)
}
}
_ => {
// Declaration
let identifier = env.resolve_variable_declaration(node, &node.name).unwrap();
Ok(IdentifierOperand {
identifier,
effect: None,
})
}
}
}

View File

@@ -0,0 +1,322 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use std::cell::RefCell;
use std::rc::Rc;
use react_diagnostics::Diagnostic;
use react_hir::{
initialize_hir, BasicBlock, BlockId, BlockKind, Blocks, Environment, GotoKind, IdentifierData,
IdentifierOperand, InstrIx, Instruction, InstructionIdGenerator, InstructionValue, Terminal,
TerminalValue, Type, HIR,
};
use crate::BuildHIRError;
/// Helper struct used when converting from ESTree to HIR. Includes:
/// - Variable resolution
/// - Label resolution (for labeled statements and break/continue)
/// - Access to the environment
///
/// As well as representing the incomplete form of the HIR. Usage
/// generally involves driving calls to enter/exit blocks, resolve
/// labels and variables, and then calling `build()` when the HIR
/// is complete.
pub(crate) struct Builder<'e> {
#[allow(dead_code)]
environment: &'e Environment,
completed: Blocks,
instructions: Vec<Instruction>,
entry: BlockId,
wip: WipBlock,
id_gen: InstructionIdGenerator,
scopes: Vec<ControlFlowScope>,
}
pub(crate) struct WipBlock {
pub id: BlockId,
pub kind: BlockKind,
pub instructions: Vec<InstrIx>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
enum ControlFlowScope {
Loop(LoopScope),
// Switch(SwitchScope),
#[allow(dead_code)]
Label(LabelScope),
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub(crate) struct LoopScope {
pub label: Option<String>,
pub continue_block: BlockId,
pub break_block: BlockId,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub(crate) struct LabelScope {
pub label: String,
pub block: BlockId,
}
impl ControlFlowScope {
fn label(&self) -> Option<&String> {
match self {
Self::Loop(scope) => scope.label.as_ref(),
Self::Label(scope) => Some(&scope.label),
}
}
fn break_block(&self) -> BlockId {
match self {
Self::Loop(scope) => scope.break_block,
Self::Label(scope) => scope.block,
}
}
}
impl<'e> Builder<'e> {
pub(crate) fn new(environment: &'e Environment) -> Self {
let entry = environment.next_block_id();
let current = WipBlock {
id: entry,
kind: BlockKind::Block,
instructions: Default::default(),
};
Self {
environment,
completed: Default::default(),
instructions: Default::default(),
entry,
wip: current,
id_gen: InstructionIdGenerator::new(),
scopes: Default::default(),
}
}
/// Completes the builder and returns the HIR if it was valid,
/// or a Diagnostic if a validation error occured.
///
/// TODO: refine the type, only invariants should be possible here,
/// not other types of errors
pub(crate) fn build(self) -> Result<HIR, Diagnostic> {
let mut hir = HIR {
entry: self.entry,
blocks: self.completed,
instructions: self.instructions,
};
// Run all the initialization passes
initialize_hir(&mut hir)?;
Ok(hir)
}
/// Adds a new instruction to the end of the work in progress block
pub(crate) fn push(&mut self, value: InstructionValue) -> IdentifierOperand {
let lvalue = IdentifierOperand {
identifier: self.environment.new_temporary(),
effect: None,
};
let instr = Instruction {
id: self.id_gen.next(),
lvalue: lvalue.clone(),
value,
};
let ix = InstrIx::new(self.instructions.len() as u32);
self.instructions.push(instr);
self.wip.instructions.push(ix);
lvalue
}
/// Terminates the work in progress block with the given terminal, and starts a new
/// work in progress block with the given kind
pub(crate) fn terminate(&mut self, terminal: TerminalValue, next_kind: BlockKind) {
let next_wip = WipBlock {
id: self.environment.next_block_id(),
kind: next_kind,
instructions: Default::default(),
};
self.terminate_with_fallthrough(terminal, next_wip)
}
pub(crate) fn terminate_with_fallthrough(
&mut self,
terminal: TerminalValue,
fallthrough: WipBlock,
) {
let prev_wip = std::mem::replace(&mut self.wip, fallthrough);
self.completed.insert(Box::new(BasicBlock {
id: prev_wip.id,
kind: prev_wip.kind,
instructions: prev_wip.instructions,
terminal: Terminal {
id: self.id_gen.next(),
value: terminal,
},
predecessors: Default::default(),
phis: Default::default(),
}));
}
pub(crate) fn reserve(&mut self, kind: BlockKind) -> WipBlock {
WipBlock {
id: self.environment.next_block_id(),
kind,
instructions: Default::default(),
}
}
pub(crate) fn enter<F>(&mut self, kind: BlockKind, f: F) -> Result<BlockId, Diagnostic>
where
F: FnOnce(&mut Self) -> Result<TerminalValue, Diagnostic>,
{
let wip = self.reserve(kind);
let id = wip.id;
self.enter_reserved(wip, f)?;
Ok(id)
}
fn enter_reserved<F>(&mut self, wip: WipBlock, f: F) -> Result<(), Diagnostic>
where
F: FnOnce(&mut Self) -> Result<TerminalValue, Diagnostic>,
{
let current = std::mem::replace(&mut self.wip, wip);
let (result, terminal) = match f(self) {
Ok(terminal) => (Ok(()), terminal),
Err(error) => (
Err(error),
// TODO: add a `Terminal::Error` variant
TerminalValue::Goto(react_hir::GotoTerminal {
block: current.id,
kind: GotoKind::Break,
}),
),
};
let completed = std::mem::replace(&mut self.wip, current);
self.completed.insert(Box::new(BasicBlock {
id: completed.id,
kind: completed.kind,
instructions: completed.instructions,
terminal: Terminal {
id: self.id_gen.next(),
value: terminal,
},
predecessors: Default::default(),
phis: Default::default(),
}));
result
}
pub(crate) fn enter_loop<F>(
&mut self,
scope: LoopScope,
f: F,
) -> Result<TerminalValue, Diagnostic>
where
F: FnOnce(&mut Self) -> Result<TerminalValue, Diagnostic>,
{
self.scopes.push(ControlFlowScope::Loop(scope.clone()));
let terminal = f(self);
let last = self.scopes.pop().unwrap();
assert_eq!(last, ControlFlowScope::Loop(scope));
terminal
}
/// Returns a new temporary identifier
/// This may be necessary for destructuring with default values. there
/// we synthesize a temporary identifier to store the possibly-missing value
/// into, and emit a later StoreLocal for the original identifier
#[allow(dead_code)]
pub(crate) fn make_temporary(&self) -> react_hir::Identifier {
react_hir::Identifier {
id: self.environment.next_identifier_id(),
name: None,
data: Rc::new(RefCell::new(IdentifierData {
mutable_range: Default::default(),
scope: None,
type_: Type::Var(self.environment.next_type_var_id()),
})),
}
}
/// Resolves the target for the given break label (if present), or returns the default
/// break target given the current context. Returns a diagnostic if the label is
/// provided but cannot be resolved.
pub(crate) fn resolve_break(
&self,
label: Option<&react_estree::Identifier>,
) -> Result<BlockId, Diagnostic> {
for scope in self.scopes.iter().rev() {
match (label, scope.label()) {
// If this is an unlabeled break, return the most recent break target
(None, _) => return Ok(scope.break_block()),
// If the break is labeled and matches the current scope, return its break target
(Some(label), Some(scope_label)) if &label.name == scope_label => {
return Ok(scope.break_block());
}
// Otherwise keep searching
_ => continue,
}
}
Err(Diagnostic::invalid_syntax(
BuildHIRError::UnresolvedBreakTarget,
None,
))
}
/// Resolves the target for the given continue label (if present), or returns the default
/// continue target given the current context. Returns a diagnostic if the label is
/// provided but cannot be resolved.
pub(crate) fn resolve_continue(
&self,
label: Option<&react_estree::Identifier>,
) -> Result<BlockId, Diagnostic> {
for scope in self.scopes.iter().rev() {
match scope {
ControlFlowScope::Loop(scope) => {
match (label, &scope.label) {
// If this is an unlabeled continue, return the first matching loop
(None, _) => return Ok(scope.continue_block),
// If the continue is labeled and matches the current scope, return its continue target
(Some(label), Some(scope_label))
if label.name.as_str() == scope_label.as_str() =>
{
return Ok(scope.continue_block);
}
// Otherwise keep searching
_ => continue,
}
}
_ => {
match (label, scope.label()) {
(Some(label), Some(scope_label)) if label.name.as_str() == scope_label => {
// Error, the continue referred to a label that is not a loop
return Err(Diagnostic::invalid_syntax(
BuildHIRError::ContinueTargetIsNotALoop,
None,
));
}
_ => continue,
}
}
}
}
Err(Diagnostic::invalid_syntax(
BuildHIRError::UnresolvedContinueTarget,
None,
))
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use std::collections::HashSet;
use react_estree::IntoFunction;
use react_hir::Environment;
use react_semantic_analysis::{DeclarationId, ScopeView};
pub(crate) fn get_context_identifiers<T: IntoFunction>(
env: &Environment,
node: &T,
) -> Vec<DeclarationId> {
let function_scope = env.scope(node.function()).unwrap();
let mut free = FreeVariables::default();
let mut seen = HashSet::new();
populate_free_variable_references(&mut free, &mut seen, function_scope);
free
}
type FreeVariables = Vec<DeclarationId>;
fn populate_free_variable_references(
free: &mut FreeVariables,
seen: &mut HashSet<DeclarationId>,
scope: ScopeView<'_>,
) {
for reference in scope.references() {
if !seen.insert(reference.declaration().id()) {
continue;
}
let declaration_scope = reference.declaration().scope();
if !declaration_scope.is_descendant_of(scope) {
free.push(reference.declaration().id())
}
}
for child in scope.children() {
populate_free_variable_references(free, seen, child);
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use thiserror::Error;
/// Errors which can occur during HIR construction
#[derive(Error, Debug)]
pub enum BuildHIRError {
/// ErrorSeverity::Unsupported
#[error(
"Variable declarations must be `let` or `const`, `var` declarations are not supported"
)]
VariableDeclarationKindIsVar,
/// ErrorSeverity::Invariant
#[error("Invariant: Expected variable declaration to declare a fresh binding")]
VariableDeclarationBindingIsNonLocal,
/// ErrorSeverity::Todo
#[error("`for` statements must have an initializer, eg `for (**let i = 0**; ...)`")]
ForStatementIsMissingInitializer,
/// ErrorSeverity::Todo
#[error(
"`for` statements must have a test condition, eg `for (let i = 0; **i < count**; ...)`"
)]
ForStatementIsMissingTest,
/// ErrorSeverity::Invariant
#[error("Invariant: Expected an expression node")]
NonExpressionInExpressionPosition,
/// ErrorSeverity::InvalidReact
#[error("React functions may not reassign variables defined outside of the component or hook")]
ReassignedGlobal,
/// ErrorSeverity::InvalidSyntax
#[error("Could not resolve a target for `break` statement")]
UnresolvedBreakTarget,
/// ErrorSeverity::InvalidSyntax
#[error("Could not resolve a target for `continue` statement")]
UnresolvedContinueTarget,
/// ErrorSeverity::InvalidSyntax
#[error("Labeled `continue` statements must use the label of a loop statement")]
ContinueTargetIsNotALoop,
/// ErrorSeverity::Invariant
#[error("Invariant: Identifier was not resolved (did name resolution run successfully?)")]
UnknownIdentifier,
/// ErrorSeverity::InvalidSyntax
#[error("Expected function to have a body")]
EmptyFunction,
#[error("`super` is not suppported")]
UnsupportedSuperExpression,
}

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
mod build;
mod builder;
mod context;
mod error;
pub use build::build;
pub use error::*;

View File

@@ -0,0 +1,23 @@
[package]
name = "react_diagnostics"
version = "0.1.0"
publish = false
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
repository.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
# TODO: extract SourceRange into a separate crate so that
# we don't depend on full estree here
react_estree = { workspace = true }
# TODO: consider extracting a separate react_miette crate which does
# the translation from react_diagnostics::Diagnostic to miette::Diagnostic
miette = { workspace = true }
thiserror = { workspace = true }
static_assertions = { workspace = true }

View File

@@ -0,0 +1,10 @@
# react_diagnostics
Types for representing compiler diagnostics. Includes a general-purpose representation
of diagnostics with related information which can be converted into `miette::Diagnostic` to exploit miette's pretty printing of errors.
Unlike miette, lsp_types, and other diagnostic libraries, the error severities match
React Compiler's semantics. The intent is that a given diagnostic may be displayed as
an error, warning, or not displayed at all depending on the context in which the
compiler is being used. For example, an ESLint plugin powered by React Compiler may ignore
InvalidSyntax diagnostics, whereas the regular compiler may report them as errors.

View File

@@ -0,0 +1,285 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use std::error::Error;
use std::fmt::{Debug, Display, Write};
use miette::SourceSpan;
use react_estree::SourceRange;
use static_assertions::assert_impl_all;
use thiserror::Error;
pub type Diagnostics = Vec<Diagnostic>;
pub type DiagnosticsResult<T> = Result<T, Diagnostics>;
#[derive(Debug)]
pub struct WithDiagnostics<T> {
pub item: T,
pub diagnostics: Vec<Diagnostic>,
}
impl<T> From<WithDiagnostics<T>> for Result<T, Diagnostics> {
fn from(s: WithDiagnostics<T>) -> Result<T, Diagnostics> {
if s.diagnostics.is_empty() {
Ok(s.item)
} else {
Err(s.diagnostics)
}
}
}
pub fn diagnostics_result<T>(result: T, diagnostics: Diagnostics) -> DiagnosticsResult<T> {
if diagnostics.is_empty() {
Ok(result)
} else {
Err(diagnostics)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Error)]
pub enum DiagnosticSeverity {
/// A feature that is intended to work but not yet implemented
#[error("Not implemented")]
Todo,
/// Syntax that is valid but intentionally not supported
#[error("Unsupported")]
Unsupported,
/// Invalid syntax
#[error("Invalid JavaScript")]
InvalidSyntax,
/// Valid syntax, but invalid React
#[error("Invalid React")]
InvalidReact,
/// Internal compiler error (ICE)
#[error("Internal error")]
Invariant,
}
/// A diagnostic message as a result of validating some code. This struct is
/// modeled after the LSP Diagnostic type:
/// https://microsoft.github.io/language-server-protocol/specification#diagnostic
///
/// Changes from LSP:
/// - `location` is different from LSP in that it's a file + span instead of
/// just a span.
/// - Unused fields are omitted.
/// - Severity is a custom enum that represents React-specific categories of error.
/// The translation to an LSP error/warning/etc depends on compiler settings and
/// invocation context.
#[derive(Debug)]
pub struct Diagnostic(Box<DiagnosticData>);
impl Diagnostic {
fn with_severity<T: 'static + DiagnosticDisplay>(
severity: DiagnosticSeverity,
message: T,
range: Option<SourceRange>,
) -> Self {
Self(Box::new(DiagnosticData {
message: Box::new(message),
span: range.map(source_span_from_range),
related_information: Vec::new(),
severity,
data: Vec::new(),
}))
}
/// Creates a new Todo Diagnostic.
/// Additional locations can be added with the `.annotate()` function.
pub fn todo<T: 'static + DiagnosticDisplay>(message: T, range: Option<SourceRange>) -> Self {
Diagnostic::with_severity(DiagnosticSeverity::Todo, message, range)
}
/// Creates a new Unsupported Diagnostic.
/// Additional locations can be added with the `.annotate()` function.
pub fn unsupported<T: 'static + DiagnosticDisplay>(
message: T,
range: Option<SourceRange>,
) -> Self {
Diagnostic::with_severity(DiagnosticSeverity::Unsupported, message, range)
}
/// Creates a new InvalidSyntax Diagnostic.
/// Additional locations can be added with the `.annotate()` function.
pub fn invalid_syntax<T: 'static + DiagnosticDisplay>(
message: T,
range: Option<SourceRange>,
) -> Self {
Diagnostic::with_severity(DiagnosticSeverity::InvalidSyntax, message, range)
}
/// Creates a new InvalidReact Diagnostic.
/// Additional locations can be added with the `.annotate()` function.
pub fn invalid_react<T: 'static + DiagnosticDisplay>(
message: T,
range: Option<SourceRange>,
) -> Self {
Diagnostic::with_severity(DiagnosticSeverity::InvalidReact, message, range)
}
/// Creates a new InvalidReact Diagnostic.
/// Additional locations can be added with the `.annotate()` function.
pub fn invariant<T: 'static + DiagnosticDisplay>(
message: T,
range: Option<SourceRange>,
) -> Self {
Diagnostic::with_severity(DiagnosticSeverity::Invariant, message, range)
}
/// Annotates this error with an additional location and associated message.
pub fn annotate<T: 'static + DiagnosticDisplay>(
mut self,
message: T,
range: Option<SourceRange>,
) -> Self {
self.0
.related_information
.push(DiagnosticRelatedInformation {
message: Box::new(message),
span: range.map(source_span_from_range),
});
self
}
pub fn message(&self) -> &impl DiagnosticDisplay {
&self.0.message
}
pub fn span(&self) -> Option<SourceSpan> {
self.0.span
}
pub fn get_data(&self) -> &[impl DiagnosticDisplay] {
&self.0.data
}
pub fn severity(&self) -> DiagnosticSeverity {
self.0.severity
}
pub fn related_information(&self) -> &[DiagnosticRelatedInformation] {
&self.0.related_information
}
pub fn print_without_source(&self) -> String {
let mut result = String::new();
writeln!(
result,
"{message}:{span:?}",
message = &self.0.message,
span = self.0.span
)
.unwrap();
if !self.0.related_information.is_empty() {
for (ix, related) in self.0.related_information.iter().enumerate() {
writeln!(
result,
"[related {ix}] {message}:{span:?}",
ix = ix + 1,
message = related.message,
span = related.span
)
.unwrap();
}
};
result
}
}
impl Display for Diagnostic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.message)
}
}
impl Error for Diagnostic {}
impl miette::Diagnostic for Diagnostic {
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
Some(Box::new(self.0.message.to_string()))
}
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
let related_items = &self.0.related_information;
let mut spans: Vec<miette::LabeledSpan> = Vec::new();
for related in related_items {
if let Some(span) = related.span {
spans.push(miette::LabeledSpan::new_with_span(
Some(related.message.to_string()),
span,
))
}
}
if spans.is_empty() {
if let Some(span) = self.0.span {
spans.push(miette::LabeledSpan::new_with_span(
Some(self.0.message.to_string()),
span,
))
}
}
Some(Box::new(spans.into_iter()))
}
}
// Ensure Diagnostic is thread-safe
assert_impl_all!(Diagnostic: Send, Sync);
#[derive(Debug)]
struct DiagnosticData {
/// Human readable error message.
message: Box<dyn DiagnosticDisplay>,
/// The primary location of this diagnostic.
span: Option<SourceSpan>,
/// Related diagnostic information, such as other definitions in the case of
/// a duplicate definition error.
related_information: Vec<DiagnosticRelatedInformation>,
severity: DiagnosticSeverity,
/// A list with data that can be passed to the code actions
/// `data` is used in the LSP protocol:
/// @see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic
data: Vec<Box<dyn DiagnosticDisplay>>,
}
/// Secondary locations attached to a diagnostic.
#[derive(Debug)]
pub struct DiagnosticRelatedInformation {
/// The message of this related diagnostic information.
pub message: Box<dyn DiagnosticDisplay>,
/// The location of this related diagnostic information.
pub span: Option<SourceSpan>,
}
/// Trait for diagnostic messages to allow structs that capture
/// some data and can lazily convert it to a message.
pub trait DiagnosticDisplay: Debug + Display + Send + Sync {}
/// Automatically implement the trait if constraints are met, so that
/// implementors don't need to.
impl<T> DiagnosticDisplay for T where T: Debug + Display + Send + Sync {}
impl From<Diagnostic> for Diagnostics {
fn from(diagnostic: Diagnostic) -> Self {
vec![diagnostic]
}
}
fn source_span_from_range(range: SourceRange) -> SourceSpan {
SourceSpan::new(
(range.start as usize).into(),
((u32::from(range.end) - range.start) as usize).into(),
)
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
mod diagnostic;
pub use diagnostic::*;
/// Returns Ok(()) if the condition is true, otherwise returns Err()
/// with the diagnostic produced by the provided callback
pub fn invariant<F>(cond: bool, f: F) -> Result<(), Diagnostic>
where
F: Fn() -> Diagnostic,
{
if cond { Ok(()) } else { Err(f()) }
}

View File

@@ -0,0 +1,22 @@
[package]
name = "react_estree"
version = "0.1.0"
publish = false
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
repository.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
insta = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
static_assertions = { workspace = true }
[build-dependencies]
react_estree_codegen = { workspace = true }

View File

@@ -0,0 +1,17 @@
# react_estree
This crate is a Rust representation of the [ESTree format](https://github.com/estree/estree/tree/master) and
popular extenions including JSX and (eventually) Flow and TypeScript.
This crate is intended as the main interchange format with outside code. A typical integration with React Compiler
will look as follows:
1. Host Compiler parses into the host AST format.
2. Host Compiler converts into `react_estree`.
3. Host Compiler invokes React Compiler to compile the input, which (conceptually)
returns the resulting code in `react_estree` format.
4. Host Compiler convert back from `react_estree` to its host AST format.
Because React Compiler is intended to support JavaScript-based toolchains, `react_estree` is designed to support
accurate serialization to/from estree-compatible JSON. We may also support the Babel AST format
(a variant of ESTree) as well, depending on demand.

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use react_estree_codegen::estree;
// Example custom build script.
fn main() {
// Re-run if the codegen files change
println!("cargo:rerun-if-changed=../react_estree_codegen/src/codegen.rs");
println!("cargo:rerun-if-changed=../react_estree_codegen/src/lib.rs");
println!("cargo:rerun-if-changed=../react_estree_codegen/src/ecmascript.json");
println!("cargo:rerun-if-changed=../react_estree_codegen");
let src = estree();
let copyright = "
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
"
.to_string();
let trimmed_copyright = copyright.trim();
let contents = format!("{trimmed_copyright}\n{src}");
std::fs::write("src/generated.rs", contents).unwrap();
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Binding {
Global,
Module(BindingId),
Local(BindingId),
}
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct BindingId(u32);
impl BindingId {
pub fn new(value: u32) -> Self {
Self(value)
}
}
impl From<BindingId> for u32 {
fn from(value: BindingId) -> Self {
value.0
}
}

View File

@@ -0,0 +1,528 @@
{
"type": "Program",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 7,
"column": 1
}
},
"body": [
{
"type": "FunctionDeclaration",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 7,
"column": 1
}
},
"id": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 9
},
"end": {
"line": 1,
"column": 18
}
},
"name": "Component",
"typeAnnotation": null,
"optional": false,
"range": [
9,
18
]
},
"params": [
{
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 19
},
"end": {
"line": 1,
"column": 24
}
},
"name": "props",
"typeAnnotation": null,
"optional": false,
"range": [
19,
24
]
}
],
"body": {
"type": "BlockStatement",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 26
},
"end": {
"line": 7,
"column": 1
}
},
"body": [
{
"type": "VariableDeclaration",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 2,
"column": 12
}
},
"kind": "let",
"declarations": [
{
"type": "VariableDeclarator",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 6
},
"end": {
"line": 2,
"column": 11
}
},
"init": {
"type": "Literal",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 10
},
"end": {
"line": 2,
"column": 11
}
},
"value": 0,
"range": [
38,
39
],
"raw": "0"
},
"id": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 6
},
"end": {
"line": 2,
"column": 7
}
},
"name": "x",
"typeAnnotation": null,
"optional": false,
"range": [
34,
35
]
},
"range": [
34,
39
]
}
],
"range": [
30,
40
]
},
{
"type": "ForStatement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 2
},
"end": {
"line": 5,
"column": 3
}
},
"init": {
"type": "VariableDeclaration",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 7
},
"end": {
"line": 3,
"column": 16
}
},
"kind": "let",
"declarations": [
{
"type": "VariableDeclarator",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 11
},
"end": {
"line": 3,
"column": 16
}
},
"init": {
"type": "Literal",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 15
},
"end": {
"line": 3,
"column": 16
}
},
"value": 0,
"range": [
56,
57
],
"raw": "0"
},
"id": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 11
},
"end": {
"line": 3,
"column": 12
}
},
"name": "i",
"typeAnnotation": null,
"optional": false,
"range": [
52,
53
]
},
"range": [
52,
57
]
}
],
"range": [
48,
57
]
},
"test": {
"type": "BinaryExpression",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 24
}
},
"left": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 19
}
},
"name": "i",
"typeAnnotation": null,
"optional": false,
"range": [
59,
60
]
},
"right": {
"type": "Literal",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 22
},
"end": {
"line": 3,
"column": 24
}
},
"value": 10,
"range": [
63,
65
],
"raw": "10"
},
"operator": "<",
"range": [
59,
65
]
},
"update": {
"type": "UpdateExpression",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 26
},
"end": {
"line": 3,
"column": 29
}
},
"operator": "++",
"argument": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 26
},
"end": {
"line": 3,
"column": 27
}
},
"name": "i",
"typeAnnotation": null,
"optional": false,
"range": [
67,
68
]
},
"prefix": false,
"range": [
67,
70
]
},
"body": {
"type": "BlockStatement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 31
},
"end": {
"line": 5,
"column": 3
}
},
"body": [
{
"type": "ExpressionStatement",
"loc": {
"source": null,
"start": {
"line": 4,
"column": 4
},
"end": {
"line": 4,
"column": 11
}
},
"expression": {
"type": "AssignmentExpression",
"loc": {
"source": null,
"start": {
"line": 4,
"column": 4
},
"end": {
"line": 4,
"column": 10
}
},
"operator": "+=",
"left": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 4,
"column": 4
},
"end": {
"line": 4,
"column": 5
}
},
"name": "x",
"typeAnnotation": null,
"optional": false,
"range": [
78,
79
]
},
"right": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 4,
"column": 9
},
"end": {
"line": 4,
"column": 10
}
},
"name": "i",
"typeAnnotation": null,
"optional": false,
"range": [
83,
84
]
},
"range": [
78,
84
]
},
"directive": null,
"range": [
78,
85
]
}
],
"range": [
72,
89
]
},
"range": [
43,
89
]
},
{
"type": "ReturnStatement",
"loc": {
"source": null,
"start": {
"line": 6,
"column": 2
},
"end": {
"line": 6,
"column": 11
}
},
"argument": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 6,
"column": 9
},
"end": {
"line": 6,
"column": 10
}
},
"name": "x",
"typeAnnotation": null,
"optional": false,
"range": [
99,
100
]
},
"range": [
92,
101
]
}
],
"range": [
26,
103
]
},
"typeParameters": null,
"returnType": null,
"predicate": null,
"generator": false,
"async": false,
"range": [
0,
103
]
}
],
"comments": [],
"interpreter": null,
"range": [
0,
103
],
"sourceType": "script"
}

View File

@@ -0,0 +1,104 @@
{
"type": "Program",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 26
}
},
"body": [
{
"type": "ImportDeclaration",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 26
}
},
"specifiers": [
{
"type": "ImportDefaultSpecifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 12
}
},
"local": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 12
}
},
"name": "React",
"typeAnnotation": null,
"optional": false,
"range": [
7,
12
]
},
"range": [
7,
12
]
}
],
"source": {
"type": "Literal",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 18
},
"end": {
"line": 1,
"column": 25
}
},
"value": "react",
"range": [
18,
25
],
"raw": "'react'"
},
"attributes": [],
"importKind": "value",
"range": [
0,
26
]
}
],
"comments": [],
"interpreter": null,
"range": [
0,
26
],
"sourceType": "module"
}

View File

@@ -0,0 +1,189 @@
{
"type": "Program",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
0,
51
],
"body": [
{
"type": "FunctionDeclaration",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
0,
51
],
"id": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 9
},
"end": {
"line": 1,
"column": 18
}
},
"range": [
9,
18
],
"name": "Component",
"typeAnnotation": null,
"optional": false
},
"params": [
{
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 19
},
"end": {
"line": 1,
"column": 24
}
},
"range": [
19,
24
],
"name": "props",
"typeAnnotation": null,
"optional": false
}
],
"body": {
"type": "BlockStatement",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 26
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
26,
51
],
"body": [
{
"type": "ReturnStatement",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 2,
"column": 21
}
},
"range": [
30,
49
],
"argument": {
"type": "MemberExpression",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 20
}
},
"range": [
37,
48
],
"object": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 14
}
},
"range": [
37,
42
],
"name": "props",
"typeAnnotation": null,
"optional": false
},
"property": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 2,
"column": 20
}
},
"range": [
43,
48
],
"name": "value",
"typeAnnotation": null,
"optional": false
},
"computed": false
}
}
]
},
"async": false,
"generator": false,
"predicate": null,
"expression": false,
"returnType": null,
"typeParameters": null
}
],
"comments": [],
"errors": []
}

View File

@@ -0,0 +1,351 @@
{
"type": "Program",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"body": [
{
"type": "FunctionDeclaration",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"id": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 12
}
},
"name": "foo",
"typeAnnotation": null,
"optional": false,
"range": [
10,
13
]
},
"params": [],
"body": {
"type": "BlockStatement",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 4,
"column": 1
}
},
"body": [
{
"type": "ReturnStatement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 2
},
"end": {
"line": 3,
"column": 36
}
},
"argument": {
"type": "JSXElement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 36
}
},
"openingElement": {
"type": "JSXOpeningElement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 26
}
},
"name": {
"type": "JSXMemberExpression",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 17
}
},
"object": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 13
}
},
"name": "Foo",
"range": [
28,
31
]
},
"property": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 14
},
"end": {
"line": 3,
"column": 17
}
},
"name": "Bar",
"range": [
32,
35
]
},
"range": [
28,
35
]
},
"attributes": [
{
"type": "JSXAttribute",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 24
}
},
"name": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 19
}
},
"name": "a",
"range": [
36,
37
]
},
"value": {
"type": "JSXExpressionContainer",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 20
},
"end": {
"line": 3,
"column": 24
}
},
"expression": {
"type": "Literal",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 21
},
"end": {
"line": 3,
"column": 23
}
},
"value": 10,
"range": [
39,
41
],
"raw": "10"
},
"range": [
38,
42
]
},
"range": [
36,
42
]
}
],
"selfClosing": false,
"range": [
27,
44
]
},
"children": [],
"closingElement": {
"type": "JSXClosingElement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 26
},
"end": {
"line": 3,
"column": 36
}
},
"name": {
"type": "JSXMemberExpression",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 28
},
"end": {
"line": 3,
"column": 35
}
},
"object": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 28
},
"end": {
"line": 3,
"column": 31
}
},
"name": "Foo",
"range": [
46,
49
]
},
"property": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 32
},
"end": {
"line": 3,
"column": 35
}
},
"name": "Bar",
"range": [
50,
53
]
},
"range": [
46,
53
]
},
"range": [
44,
54
]
},
"range": [
27,
54
]
},
"range": [
20,
54
]
}
],
"range": [
16,
56
]
},
"typeParameters": null,
"returnType": null,
"predicate": null,
"generator": false,
"async": false,
"range": [
1,
56
]
}
],
"comments": [],
"interpreter": null,
"range": [
1,
56
],
"sourceType": "script"
}

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More