Compare commits
119 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ccfd5ecd3e | ||
|
|
9d795d3808 | ||
|
|
12f4cb85c5 | ||
|
|
90a124a980 | ||
|
|
49ea8bf569 | ||
|
|
9a52ad9fd9 | ||
|
|
fa8e3a251e | ||
|
|
408d055a3b | ||
|
|
fbf29ccaa3 | ||
|
|
62960c67c8 | ||
|
|
cd4e4d7599 | ||
|
|
18212ca960 | ||
|
|
88b9767404 | ||
|
|
0038c501a3 | ||
|
|
5dc00d6b2b | ||
|
|
c498bfce8b | ||
|
|
8e9a5fc6c1 | ||
|
|
89e8875ec4 | ||
|
|
2d0a5e399f | ||
|
|
0c28a09eef | ||
|
|
143d3e1b89 | ||
|
|
693803a9bb | ||
|
|
24dfad3abb | ||
|
|
bb74190c26 | ||
|
|
5010364d34 | ||
|
|
9938f83ca2 | ||
|
|
2af218a728 | ||
|
|
b06bb35ce9 | ||
|
|
197d6a0403 | ||
|
|
ad09027c16 | ||
|
|
8b9629c810 | ||
|
|
3a5335676f | ||
|
|
b75af04670 | ||
|
|
f765082996 | ||
|
|
7b21c46489 | ||
|
|
e25e8c7575 | ||
|
|
cd7d236682 | ||
|
|
71d0896a4a | ||
|
|
914319ae59 | ||
|
|
3ef31d196a | ||
|
|
17f88c80ed | ||
|
|
3fbd6b7b50 | ||
|
|
ebf7318e87 | ||
|
|
620c838fb6 | ||
|
|
7213509649 | ||
|
|
4c54da77fb | ||
|
|
efd890422d | ||
|
|
b303610c33 | ||
|
|
fea92d8462 | ||
|
|
bc6184dd99 | ||
|
|
ce578f9c59 | ||
|
|
45d942f94a | ||
|
|
b8bedc267f | ||
|
|
4a36d3eab7 | ||
|
|
2ddf8caa9d | ||
|
|
95ff37f5f5 | ||
|
|
3c75bf21dd | ||
|
|
3e04b2a214 | ||
|
|
fc21d5a7db | ||
|
|
35ab8ffef7 | ||
|
|
68013725ac | ||
|
|
bf39780a06 | ||
|
|
b04254fdce | ||
|
|
539bbdbd86 | ||
|
|
e71d4205ae | ||
|
|
2ed34eba0d | ||
|
|
707b3fc6b2 | ||
|
|
7ff4d057b6 | ||
|
|
08075929f2 | ||
|
|
4eea4fcf41 | ||
|
|
58e9a4b74f | ||
|
|
39cad7afc4 | ||
|
|
1d6c8168db | ||
|
|
961b625ab5 | ||
|
|
8a3c5e1a8d | ||
|
|
5e9b48778c | ||
|
|
c44e4a2505 | ||
|
|
31ecc9804a | ||
|
|
ff697fc58b | ||
|
|
096dd7385d | ||
|
|
717584167b | ||
|
|
3fbfb9baaf | ||
|
|
8571249eb8 | ||
|
|
8da36d0508 | ||
|
|
ea05b750a5 | ||
|
|
3366146796 | ||
|
|
365c031fd2 | ||
|
|
a9d63f3f97 | ||
|
|
6a7650c75c | ||
|
|
efb22d8850 | ||
|
|
540cd65252 | ||
|
|
c0f08ae74a | ||
|
|
b10cb4c01e | ||
|
|
f0c767e2a2 | ||
|
|
b2f6365745 | ||
|
|
b81c92be62 | ||
|
|
040f8286e9 | ||
|
|
450f8df886 | ||
|
|
7a728dffd1 | ||
|
|
e5dd82a79d | ||
|
|
731ae3e0ad | ||
|
|
deca96520f | ||
|
|
0b1a9e90c5 | ||
|
|
8b2046d0ce | ||
|
|
d20c2802b4 | ||
|
|
0a7cf20b22 | ||
|
|
b286430c8a | ||
|
|
d3b8ff6e58 | ||
|
|
a7fa8702ee | ||
|
|
95671b4eb3 | ||
|
|
6377903074 | ||
|
|
095ce8a311 | ||
|
|
18a11339c3 | ||
|
|
d726d692ed | ||
|
|
50c5cdb653 | ||
|
|
deb7859bb0 | ||
|
|
1825990c56 | ||
|
|
1de32a5e75 | ||
|
|
ef4bc8b4f9 |
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"packages": ["packages/react", "packages/react-dom", "packages/scheduler"],
|
||||
"packages": ["packages/react", "packages/react-dom", "packages/react-server-dom-webpack", "packages/scheduler"],
|
||||
"buildCommand": "download-build-in-codesandbox-ci",
|
||||
"node": "18",
|
||||
"publishDirectory": {
|
||||
"react": "build/oss-experimental/react",
|
||||
"react-dom": "build/oss-experimental/react-dom",
|
||||
"react-server-dom-webpack": "build/oss-experimental/react-server-dom-webpack",
|
||||
"scheduler": "build/oss-experimental/scheduler"
|
||||
},
|
||||
"sandboxes": ["new"],
|
||||
|
||||
@@ -615,6 +615,8 @@ module.exports = {
|
||||
GetAnimationsOptions: 'readonly',
|
||||
Animatable: 'readonly',
|
||||
ScrollTimeline: 'readonly',
|
||||
EventListenerOptionsOrUseCapture: 'readonly',
|
||||
FocusOptions: 'readonly',
|
||||
|
||||
spyOnDev: 'readonly',
|
||||
spyOnDevAndProd: 'readonly',
|
||||
|
||||
18
.github/ISSUE_TEMPLATE/19.md
vendored
18
.github/ISSUE_TEMPLATE/19.md
vendored
@@ -1,18 +0,0 @@
|
||||
---
|
||||
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.
|
||||
-->
|
||||
12
.github/workflows/compiler_discord_notify.yml
vendored
12
.github/workflows/compiler_discord_notify.yml
vendored
@@ -10,7 +10,19 @@ on:
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
check_access:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
is_member_or_collaborator: ${{ steps.check_is_member_or_collaborator.outputs.is_member_or_collaborator }}
|
||||
steps:
|
||||
- name: Check is member or collaborator
|
||||
id: check_is_member_or_collaborator
|
||||
if: ${{ github.event.pull_request.author_association == 'MEMBER' || github.event.pull_request.author_association == 'COLLABORATOR' }}
|
||||
run: echo "is_member_or_collaborator=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
check_maintainer:
|
||||
if: ${{ needs.check_access.outputs.is_member_or_collaborator == 'true' || needs.check_access.outputs.is_member_or_collaborator == true }}
|
||||
needs: [check_access]
|
||||
uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
|
||||
permissions:
|
||||
# Used by check_maintainer
|
||||
|
||||
5
.github/workflows/compiler_prereleases.yml
vendored
5
.github/workflows/compiler_prereleases.yml
vendored
@@ -16,6 +16,9 @@ on:
|
||||
version_name:
|
||||
required: true
|
||||
type: string
|
||||
tag_version:
|
||||
required: false
|
||||
type: string
|
||||
secrets:
|
||||
NPM_TOKEN:
|
||||
required: true
|
||||
@@ -55,4 +58,4 @@ jobs:
|
||||
- name: Publish packages to npm
|
||||
run: |
|
||||
cp ./scripts/release/ci-npmrc ~/.npmrc
|
||||
scripts/release/publish.js --frfr --ci --versionName=${{ inputs.version_name }} --tag ${{ inputs.dist_tag }}
|
||||
scripts/release/publish.js --frfr --ci --versionName=${{ inputs.version_name }} --tag=${{ inputs.dist_tag }} ${{ inputs.tag_version && format('--tagVersion={0}', inputs.tag_version) || '' }}
|
||||
|
||||
@@ -14,6 +14,9 @@ on:
|
||||
version_name:
|
||||
required: true
|
||||
type: string
|
||||
tag_version:
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions: {}
|
||||
|
||||
@@ -29,5 +32,6 @@ jobs:
|
||||
release_channel: ${{ inputs.release_channel }}
|
||||
dist_tag: ${{ inputs.dist_tag }}
|
||||
version_name: ${{ inputs.version_name }}
|
||||
tag_version: ${{ inputs.tag_version }}
|
||||
secrets:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
name: (Compiler) Publish Prereleases Weekly
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# At 10 minutes past 9:00 on Mon
|
||||
- cron: 10 9 * * 1
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
|
||||
jobs:
|
||||
publish_prerelease_beta:
|
||||
name: Publish to beta channel
|
||||
uses: facebook/react/.github/workflows/compiler_prereleases.yml@main
|
||||
with:
|
||||
commit_sha: ${{ github.sha }}
|
||||
release_channel: beta
|
||||
dist_tag: beta
|
||||
version_name: '19.0.0'
|
||||
secrets:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
12
.github/workflows/runtime_discord_notify.yml
vendored
12
.github/workflows/runtime_discord_notify.yml
vendored
@@ -10,7 +10,19 @@ on:
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
check_access:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
is_member_or_collaborator: ${{ steps.check_is_member_or_collaborator.outputs.is_member_or_collaborator }}
|
||||
steps:
|
||||
- name: Check is member or collaborator
|
||||
id: check_is_member_or_collaborator
|
||||
if: ${{ github.event.pull_request.author_association == 'MEMBER' || github.event.pull_request.author_association == 'COLLABORATOR' }}
|
||||
run: echo "is_member_or_collaborator=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
check_maintainer:
|
||||
if: ${{ needs.check_access.outputs.is_member_or_collaborator == 'true' || needs.check_access.outputs.is_member_or_collaborator == true }}
|
||||
needs: [check_access]
|
||||
uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
|
||||
permissions:
|
||||
# Used by check_maintainer
|
||||
|
||||
15
.github/workflows/runtime_prereleases.yml
vendored
15
.github/workflows/runtime_prereleases.yml
vendored
@@ -13,7 +13,14 @@ on:
|
||||
dist_tag:
|
||||
required: true
|
||||
type: string
|
||||
enableFailureNotification:
|
||||
description: 'Whether to notify the team on Discord when the release fails. Useful if this workflow is called from an automation.'
|
||||
required: false
|
||||
type: boolean
|
||||
secrets:
|
||||
DISCORD_WEBHOOK_URL:
|
||||
description: 'Discord webhook URL to notify on failure. Only required if enableFailureNotification is true.'
|
||||
required: false
|
||||
GH_TOKEN:
|
||||
required: true
|
||||
NPM_TOKEN:
|
||||
@@ -58,3 +65,11 @@ jobs:
|
||||
GH_TOKEN=${{ secrets.GH_TOKEN }} scripts/release/prepare-release-from-ci.js --skipTests -r ${{ inputs.release_channel }} --commit=${{ inputs.commit_sha }}
|
||||
cp ./scripts/release/ci-npmrc ~/.npmrc
|
||||
scripts/release/publish.js --ci --tags ${{ inputs.dist_tag }}
|
||||
- name: Notify Discord on failure
|
||||
if: failure() && inputs.enableFailureNotification == true
|
||||
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
embed-author-name: "GitHub Actions"
|
||||
embed-title: 'Publish of $${{ inputs.release_channel }} release failed'
|
||||
embed-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }}
|
||||
|
||||
@@ -21,7 +21,9 @@ jobs:
|
||||
commit_sha: ${{ github.sha }}
|
||||
release_channel: stable
|
||||
dist_tag: canary,next
|
||||
enableFailureNotification: true
|
||||
secrets:
|
||||
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -40,6 +42,8 @@ jobs:
|
||||
commit_sha: ${{ github.sha }}
|
||||
release_channel: experimental
|
||||
dist_tag: experimental
|
||||
enableFailureNotification: true
|
||||
secrets:
|
||||
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
13
.github/workflows/shared_label_core_team_prs.yml
vendored
13
.github/workflows/shared_label_core_team_prs.yml
vendored
@@ -2,6 +2,7 @@ name: (Shared) Label Core Team PRs
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
permissions: {}
|
||||
|
||||
@@ -11,7 +12,19 @@ env:
|
||||
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
|
||||
|
||||
jobs:
|
||||
check_access:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
is_member_or_collaborator: ${{ steps.check_is_member_or_collaborator.outputs.is_member_or_collaborator }}
|
||||
steps:
|
||||
- name: Check is member or collaborator
|
||||
id: check_is_member_or_collaborator
|
||||
if: ${{ github.event.pull_request.author_association == 'MEMBER' || github.event.pull_request.author_association == 'COLLABORATOR' }}
|
||||
run: echo "is_member_or_collaborator=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
check_maintainer:
|
||||
if: ${{ needs.check_access.outputs.is_member_or_collaborator == 'true' || needs.check_access.outputs.is_member_or_collaborator == true }}
|
||||
needs: [check_access]
|
||||
uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
|
||||
permissions:
|
||||
# Used by check_maintainer
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
## March 22, 2024 (18.3.0-canary-670811593-20240322)
|
||||
|
||||
## React
|
||||
- Added `useActionState` to replace `useFormState` and added `pending` value ([#28491](https://github.com/facebook/react/pull/28491)).
|
||||
|
||||
## October 5, 2023 (18.3.0-canary-546178f91-20231005)
|
||||
|
||||
### React
|
||||
|
||||
- Added support for async functions to be passed to `startTransition`.
|
||||
- `useTransition` now triggers the nearest error boundary instead of a global error.
|
||||
- Added `useOptimistic`, a new Hook for handling optimistic UI updates. It optimistically updates the UI before receiving confirmation from a server or external source.
|
||||
|
||||
### React DOM
|
||||
|
||||
- Added support for passing async functions to the `action` prop on `<form>`. When the function passed to `action` is marked with [`'use server'`](https://react.dev/reference/react/use-server), the form is [progressively enhanced](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement).
|
||||
- Added `useFormStatus`, a new Hook for checking the submission state of a form.
|
||||
- Added `useFormState`, a new Hook for updating state upon form submission. When the function passed to `useFormState` is marked with [`'use server'`](https://react.dev/reference/react/use-server), the update is [progressively enhanced](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement).
|
||||
47
CHANGELOG.md
47
CHANGELOG.md
@@ -1,3 +1,50 @@
|
||||
## 19.1.0 (March 28, 2025)
|
||||
|
||||
### Owner Stack
|
||||
|
||||
An Owner Stack is a string representing the components that are directly responsible for rendering a particular component. You can log Owner Stacks when debugging or use Owner Stacks to enhance error overlays or other development tools. Owner Stacks are only available in development builds. Component Stacks in production are unchanged.
|
||||
|
||||
* An Owner Stack is a development-only stack trace that helps identify which components are responsible for rendering a particular component. An Owner Stack is distinct from a Component Stacks, which shows the hierarchy of components leading to an error.
|
||||
* The [captureOwnerStack API](https://react.dev/reference/react/captureOwnerStack) is only available in development mode and returns a Owner Stack, if available. The API can be used to enhance error overlays or log component relationships when debugging. [#29923](https://github.com/facebook/react/pull/29923), [#32353](https://github.com/facebook/react/pull/32353), [#30306](https://github.com/facebook/react/pull/30306),
|
||||
[#32538](https://github.com/facebook/react/pull/32538), [#32529](https://github.com/facebook/react/pull/32529), [#32538](https://github.com/facebook/react/pull/32538)
|
||||
|
||||
### React
|
||||
* Enhanced support for Suspense boundaries to be used anywhere, including the client, server, and during hydration. [#32069](https://github.com/facebook/react/pull/32069), [#32163](https://github.com/facebook/react/pull/32163), [#32224](https://github.com/facebook/react/pull/32224), [#32252](https://github.com/facebook/react/pull/32252)
|
||||
* Reduced unnecessary client rendering through improved hydration scheduling [#31751](https://github.com/facebook/react/pull/31751)
|
||||
* Increased priority of client rendered Suspense boundaries [#31776](https://github.com/facebook/react/pull/31776)
|
||||
* Fixed frozen fallback states by rendering unfinished Suspense boundaries on the client. [#31620](https://github.com/facebook/react/pull/31620)
|
||||
* Reduced garbage collection pressure by improving Suspense boundary retries. [#31667](https://github.com/facebook/react/pull/31667)
|
||||
* Fixed erroneous “Waiting for Paint” log when the passive effect phase was not delayed [#31526](https://github.com/facebook/react/pull/31526)
|
||||
* Fixed a regression causing key warnings for flattened positional children in development mode. [#32117](https://github.com/facebook/react/pull/32117)
|
||||
* Updated `useId` to use valid CSS selectors, changing format from `:r123:` to `«r123»`. [#32001](https://github.com/facebook/react/pull/32001)
|
||||
* Added a dev-only warning for null/undefined created in useEffect, useInsertionEffect, and useLayoutEffect. [#32355](https://github.com/facebook/react/pull/32355)
|
||||
* Fixed a bug where dev-only methods were exported in production builds. React.act is no longer available in production builds. [#32200](https://github.com/facebook/react/pull/32200)
|
||||
* Improved consistency across prod and dev to improve compatibility with Google Closure Complier and bindings [#31808](https://github.com/facebook/react/pull/31808)
|
||||
* Improve passive effect scheduling for consistent task yielding. [#31785](https://github.com/facebook/react/pull/31785)
|
||||
* Fixed asserts in React Native when passChildrenWhenCloningPersistedNodes is enabled for OffscreenComponent rendering. [#32528](https://github.com/facebook/react/pull/32528)
|
||||
* Fixed component name resolution for Portal [#32640](https://github.com/facebook/react/pull/32640)
|
||||
* Added support for beforetoggle and toggle events on the dialog element. #32479 [#32479](https://github.com/facebook/react/pull/32479)
|
||||
|
||||
### React DOM
|
||||
* Fixed double warning when the `href` attribute is an empty string [#31783](https://github.com/facebook/react/pull/31783)
|
||||
* Fixed an edge case where `getHoistableRoot()` didn’t work properly when the container was a Document [#32321](https://github.com/facebook/react/pull/32321)
|
||||
* Removed support for using HTML comments (e.g. `<!-- -->`) as a DOM container. [#32250](https://github.com/facebook/react/pull/32250)
|
||||
* Added support for `<script>` and `<template>` tags to be nested within `<select>` tags. [#31837](https://github.com/facebook/react/pull/31837)
|
||||
* Fixed responsive images to be preloaded as HTML instead of headers [#32445](https://github.com/facebook/react/pull/32445)
|
||||
|
||||
### use-sync-external-store
|
||||
* Added `exports` field to `package.json` for `use-sync-external-store` to support various entrypoints. [#25231](https://github.com/facebook/react/pull/25231)
|
||||
|
||||
### React Server Components
|
||||
* Added `unstable_prerender`, a new experimental API for prerendering React Server Components on the server [#31724](https://github.com/facebook/react/pull/31724)
|
||||
* Fixed an issue where streams would hang when receiving new chunks after a global error [#31840](https://github.com/facebook/react/pull/31840), [#31851](https://github.com/facebook/react/pull/31851)
|
||||
* Fixed an issue where pending chunks were counted twice. [#31833](https://github.com/facebook/react/pull/31833)
|
||||
* Added support for streaming in edge environments [#31852](https://github.com/facebook/react/pull/31852)
|
||||
* Added support for sending custom error names from a server so that they are available in the client for console replaying. [#32116](https://github.com/facebook/react/pull/32116)
|
||||
* Updated the server component wire format to remove IDs for hints and console.log because they have no return value [#31671](https://github.com/facebook/react/pull/31671)
|
||||
* Exposed `registerServerReference` in client builds to handle server references in different environments. [#32534](https://github.com/facebook/react/pull/32534)
|
||||
* Added react-server-dom-parcel package which integrates Server Components with the [Parcel bundler](https://parceljs.org/) [#31725](https://github.com/facebook/react/pull/31725), [#32132](https://github.com/facebook/react/pull/32132), [#31799](https://github.com/facebook/react/pull/31799), [#32294](https://github.com/facebook/react/pull/32294), [#31741](https://github.com/facebook/react/pull/31741)
|
||||
|
||||
## 19.0.0 (December 5, 2024)
|
||||
|
||||
Below is a list of all new features, APIs, deprecations, and breaking changes. Read [React 19 release post](https://react.dev/blog/2024/04/25/react-19) and [React 19 upgrade guide](https://react.dev/blog/2024/04/25/react-19-upgrade-guide) for more information.
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
//
|
||||
// 0.0.0-experimental-241c4467e-20200129
|
||||
|
||||
const ReactVersion = '19.1.0';
|
||||
const ReactVersion = '19.2.0';
|
||||
|
||||
// The label used by the @canary channel. Represents the upcoming release's
|
||||
// stability. Most of the time, this will be "canary", but we may temporarily
|
||||
@@ -33,7 +33,7 @@ const canaryChannelLabel = 'canary';
|
||||
const rcNumber = 0;
|
||||
|
||||
const stablePackages = {
|
||||
'eslint-plugin-react-hooks': '5.2.0',
|
||||
'eslint-plugin-react-hooks': '6.1.0',
|
||||
'jest-react': '0.17.0',
|
||||
react: ReactVersion,
|
||||
'react-art': ReactVersion,
|
||||
@@ -42,12 +42,12 @@ const stablePackages = {
|
||||
'react-server-dom-turbopack': ReactVersion,
|
||||
'react-server-dom-parcel': ReactVersion,
|
||||
'react-is': ReactVersion,
|
||||
'react-reconciler': '0.32.0',
|
||||
'react-refresh': '0.17.0',
|
||||
'react-reconciler': '0.33.0',
|
||||
'react-refresh': '0.18.0',
|
||||
'react-test-renderer': ReactVersion,
|
||||
'use-subscription': '1.11.0',
|
||||
'use-sync-external-store': '1.5.0',
|
||||
scheduler: '0.26.0',
|
||||
'use-subscription': '1.12.0',
|
||||
'use-sync-external-store': '1.6.0',
|
||||
scheduler: '0.27.0',
|
||||
};
|
||||
|
||||
// These packages do not exist in the @canary or @latest channel, only
|
||||
|
||||
59
compiler/CHANGELOG.md
Normal file
59
compiler/CHANGELOG.md
Normal file
@@ -0,0 +1,59 @@
|
||||
## 19.1.0-rc.1 (April 21, 2025)
|
||||
|
||||
## eslint-plugin-react-hooks
|
||||
* Temporarily disable ref access in render validation [#32839](https://github.com/facebook/react/pull/32839) by [@poteto](https://github.com/poteto)
|
||||
* Fix type error with recommended config [#32666](https://github.com/facebook/react/pull/32666) by [@niklasholm](https://github.com/niklasholm)
|
||||
* Merge rule from eslint-plugin-react-compiler into `react-hooks` plugin [#32416](https://github.com/facebook/react/pull/32416) by [@michaelfaith](https://github.com/michaelfaith)
|
||||
* Add dev dependencies for typescript migration [#32279](https://github.com/facebook/react/pull/32279) by [@michaelfaith](https://github.com/michaelfaith)
|
||||
* Support v9 context api [#32045](https://github.com/facebook/react/pull/32045) by [@michaelfaith](https://github.com/michaelfaith)
|
||||
* Support eslint 8+ flat plugin syntax out of the box for eslint-plugin-react-compiler [#32120](https://github.com/facebook/react/pull/32120) by [@orta](https://github.com/orta)
|
||||
|
||||
## babel-plugin-react-compiler
|
||||
* Support satisfies operator [#32742](https://github.com/facebook/react/pull/32742) by [@rodrigofariow](https://github.com/rodrigofariow)
|
||||
* Fix inferEffectDependencies lint false positives [#32769](https://github.com/facebook/react/pull/32769) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Fix hoisting of let declarations [#32724](https://github.com/facebook/react/pull/32724) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Avoid failing builds when import specifiers conflict or shadow vars [#32663](https://github.com/facebook/react/pull/32663) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Optimize components declared with arrow function and implicit return and `compilationMode: 'infer'` [#31792](https://github.com/facebook/react/pull/31792) by [@dimaMachina](https://github.com/dimaMachina)
|
||||
* Validate static components [#32683](https://github.com/facebook/react/pull/32683) by [@josephsavona](https://github.com/josephsavona)
|
||||
* Hoist dependencies from functions more conservatively [#32616](https://github.com/facebook/react/pull/32616) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Implement NumericLiteral as ObjectPropertyKey [#31791](https://github.com/facebook/react/pull/31791) by [@dimaMachina](https://github.com/dimaMachina)
|
||||
* Avoid bailouts when inserting gating [#32598](https://github.com/facebook/react/pull/32598) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Stop bailing out early for hoisted gated functions [#32597](https://github.com/facebook/react/pull/32597) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Add shape for Array.from [#32522](https://github.com/facebook/react/pull/32522) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Patch array and argument spread mutability [#32521](https://github.com/facebook/react/pull/32521) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Make CompilerError compatible with reflection [#32539](https://github.com/facebook/react/pull/32539) by [@poteto](https://github.com/poteto)
|
||||
* Add simple walltime measurement [#32331](https://github.com/facebook/react/pull/32331) by [@poteto](https://github.com/poteto)
|
||||
* Improve error messages for unhandled terminal and instruction kinds [#32324](https://github.com/facebook/react/pull/32324) by [@inottn](https://github.com/inottn)
|
||||
* Handle TSInstantiationExpression in lowerExpression [#32302](https://github.com/facebook/react/pull/32302) by [@inottn](https://github.com/inottn)
|
||||
* Fix invalid Array.map type [#32095](https://github.com/facebook/react/pull/32095) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Patch for JSX escape sequences in @babel/generator [#32131](https://github.com/facebook/react/pull/32131) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* `JSXText` emits incorrect with bracket [#32138](https://github.com/facebook/react/pull/32138) by [@himself65](https://github.com/himself65)
|
||||
* Validation against calling impure functions [#31960](https://github.com/facebook/react/pull/31960) by [@josephsavona](https://github.com/josephsavona)
|
||||
* Always target node [#32091](https://github.com/facebook/react/pull/32091) by [@poteto](https://github.com/poteto)
|
||||
* Patch compilationMode:infer object method edge case [#32055](https://github.com/facebook/react/pull/32055) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Generate ts defs [#31994](https://github.com/facebook/react/pull/31994) by [@poteto](https://github.com/poteto)
|
||||
* Relax react peer dep requirement [#31915](https://github.com/facebook/react/pull/31915) by [@poteto](https://github.com/poteto)
|
||||
* Allow type cast expressions with refs [#31871](https://github.com/facebook/react/pull/31871) by [@josephsavona](https://github.com/josephsavona)
|
||||
* Add shape for global Object.keys [#31583](https://github.com/facebook/react/pull/31583) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Optimize method calls w props receiver [#31775](https://github.com/facebook/react/pull/31775) by [@josephsavona](https://github.com/josephsavona)
|
||||
* Fix dropped ref with spread props in InlineJsxTransform [#31726](https://github.com/facebook/react/pull/31726) by [@jackpope](https://github.com/jackpope)
|
||||
* Support for non-declatation for in/of iterators [#31710](https://github.com/facebook/react/pull/31710) by [@mvitousek](https://github.com/mvitousek)
|
||||
* Support for context variable loop iterators [#31709](https://github.com/facebook/react/pull/31709) by [@mvitousek](https://github.com/mvitousek)
|
||||
* Replace deprecated dependency in `eslint-plugin-react-compiler` [#31629](https://github.com/facebook/react/pull/31629) by [@rakleed](https://github.com/rakleed)
|
||||
* Support enableRefAsProp in jsx transform [#31558](https://github.com/facebook/react/pull/31558) by [@jackpope](https://github.com/jackpope)
|
||||
* Fix: ref.current now correctly reactive [#31521](https://github.com/facebook/react/pull/31521) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Outline JSX with non-jsx children [#31442](https://github.com/facebook/react/pull/31442) by [@gsathya](https://github.com/gsathya)
|
||||
* Outline jsx with duplicate attributes [#31441](https://github.com/facebook/react/pull/31441) by [@gsathya](https://github.com/gsathya)
|
||||
* Store original and new prop names [#31440](https://github.com/facebook/react/pull/31440) by [@gsathya](https://github.com/gsathya)
|
||||
* Stabilize compiler output: sort deps and decls by name [#31362](https://github.com/facebook/react/pull/31362) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Bugfix for hoistable deps for nested functions [#31345](https://github.com/facebook/react/pull/31345) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Remove compiler runtime-compat fixture library [#31430](https://github.com/facebook/react/pull/31430) by [@poteto](https://github.com/poteto)
|
||||
* Wrap inline jsx transform codegen in conditional [#31267](https://github.com/facebook/react/pull/31267) by [@jackpope](https://github.com/jackpope)
|
||||
* Check if local identifier is a hook when resolving globals [#31384](https://github.com/facebook/react/pull/31384) by [@poteto](https://github.com/poteto)
|
||||
* Handle member expr as computed property [#31344](https://github.com/facebook/react/pull/31344) by [@gsathya](https://github.com/gsathya)
|
||||
* Fix to ref access check to ban ref?.current [#31360](https://github.com/facebook/react/pull/31360) by [@mvitousek](https://github.com/mvitousek)
|
||||
* InlineJSXTransform transforms jsx inside function expressions [#31282](https://github.com/facebook/react/pull/31282) by [@josephsavona](https://github.com/josephsavona)
|
||||
|
||||
## Other
|
||||
* Add shebang to banner [#32225](https://github.com/facebook/react/pull/32225) by [@Jeremy-Hibiki](https://github.com/Jeremy-Hibiki)
|
||||
* remove terser from react-compiler-runtime build [#31326](https://github.com/facebook/react/pull/31326) by [@henryqdineen](https://github.com/henryqdineen)
|
||||
@@ -27,7 +27,7 @@
|
||||
"@babel/types": "7.26.3",
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@monaco-editor/react": "^4.4.6",
|
||||
"@playwright/test": "^1.42.1",
|
||||
"@playwright/test": "^1.51.1",
|
||||
"@use-gesture/react": "^10.2.22",
|
||||
"hermes-eslint": "^0.25.0",
|
||||
"hermes-parser": "^0.25.0",
|
||||
|
||||
@@ -781,12 +781,12 @@
|
||||
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
|
||||
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
|
||||
|
||||
"@playwright/test@^1.42.1":
|
||||
version "1.47.2"
|
||||
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.47.2.tgz#dbe7051336bfc5cc599954214f9111181dbc7475"
|
||||
integrity sha512-jTXRsoSPONAs8Za9QEQdyjFn+0ZQFjCiIztAIF6bi1HqhBzG9Ma7g1WotyiGqFSBRZjIEqMdT8RUlbk1QVhzCQ==
|
||||
"@playwright/test@^1.51.1":
|
||||
version "1.51.1"
|
||||
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.51.1.tgz#75357d513221a7be0baad75f01e966baf9c41a2e"
|
||||
integrity sha512-nM+kEaTSAoVlXmMPH10017vn3FSiFqr/bh4fKg9vmAdMfd9SDqRZNvPSiAHADc/itWak+qPvMPZQOPwCBW7k7Q==
|
||||
dependencies:
|
||||
playwright "1.47.2"
|
||||
playwright "1.51.1"
|
||||
|
||||
"@rtsao/scc@^1.1.0":
|
||||
version "1.1.0"
|
||||
@@ -1249,14 +1249,14 @@ camelcase-css@^2.0.1:
|
||||
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
|
||||
|
||||
caniuse-lite@^1.0.30001579:
|
||||
version "1.0.30001669"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz#fda8f1d29a8bfdc42de0c170d7f34a9cf19ed7a3"
|
||||
integrity sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==
|
||||
version "1.0.30001715"
|
||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz"
|
||||
integrity sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==
|
||||
|
||||
caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001663:
|
||||
version "1.0.30001664"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz#d588d75c9682d3301956b05a3749652a80677df4"
|
||||
integrity sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g==
|
||||
version "1.0.30001715"
|
||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz"
|
||||
integrity sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==
|
||||
|
||||
chalk@^2.4.2:
|
||||
version "2.4.2"
|
||||
@@ -3008,17 +3008,17 @@ pirates@^4.0.1:
|
||||
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9"
|
||||
integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==
|
||||
|
||||
playwright-core@1.47.2:
|
||||
version "1.47.2"
|
||||
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.47.2.tgz#7858da9377fa32a08be46ba47d7523dbd9460a4e"
|
||||
integrity sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ==
|
||||
playwright-core@1.51.1:
|
||||
version "1.51.1"
|
||||
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.51.1.tgz#d57f0393e02416f32a47cf82b27533656a8acce1"
|
||||
integrity sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw==
|
||||
|
||||
playwright@1.47.2:
|
||||
version "1.47.2"
|
||||
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.47.2.tgz#155688aa06491ee21fb3e7555b748b525f86eb20"
|
||||
integrity sha512-nx1cLMmQWqmA3UsnjaaokyoUpdVaaDhJhMoxX2qj3McpjnsqFHs516QAKYhqHAgOP+oCFTEOCOAaD1RgD/RQfA==
|
||||
playwright@1.51.1:
|
||||
version "1.51.1"
|
||||
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.51.1.tgz#ae1467ee318083968ad28d6990db59f47a55390f"
|
||||
integrity sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw==
|
||||
dependencies:
|
||||
playwright-core "1.47.2"
|
||||
playwright-core "1.51.1"
|
||||
optionalDependencies:
|
||||
fsevents "2.3.2"
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-hermes-parser": "^0.26.0",
|
||||
"prompt-promise": "^1.0.3",
|
||||
"rimraf": "^5.0.10",
|
||||
"rimraf": "^6.0.1",
|
||||
"to-fast-properties": "^2.0.0",
|
||||
"tsup": "^8.4.0",
|
||||
"typescript": "^5.4.3",
|
||||
@@ -45,7 +45,6 @@
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"rimraf": "5.0.10",
|
||||
"@babel/types": "7.26.3"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22"
|
||||
|
||||
@@ -182,7 +182,9 @@ export type LoggerEvent =
|
||||
| CompileDiagnosticEvent
|
||||
| CompileSkipEvent
|
||||
| PipelineErrorEvent
|
||||
| TimingEvent;
|
||||
| TimingEvent
|
||||
| AutoDepsDecorationsEvent
|
||||
| AutoDepsEligibleEvent;
|
||||
|
||||
export type CompileErrorEvent = {
|
||||
kind: 'CompileError';
|
||||
@@ -219,6 +221,16 @@ export type TimingEvent = {
|
||||
kind: 'Timing';
|
||||
measurement: PerformanceMeasure;
|
||||
};
|
||||
export type AutoDepsDecorationsEvent = {
|
||||
kind: 'AutoDepsDecorations';
|
||||
fnLoc: t.SourceLocation;
|
||||
decorations: Array<t.SourceLocation>;
|
||||
};
|
||||
export type AutoDepsEligibleEvent = {
|
||||
kind: 'AutoDepsEligible';
|
||||
fnLoc: t.SourceLocation;
|
||||
depArrayLoc: t.SourceLocation;
|
||||
};
|
||||
|
||||
export type Logger = {
|
||||
logEvent: (filename: string | null, event: LoggerEvent) => void;
|
||||
|
||||
@@ -392,6 +392,11 @@ function runWithEnvironment(
|
||||
|
||||
if (env.config.inferEffectDependencies) {
|
||||
inferEffectDependencies(hir);
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'InferEffectDependencies',
|
||||
value: hir,
|
||||
});
|
||||
}
|
||||
|
||||
if (env.config.inlineJsxTransform) {
|
||||
|
||||
@@ -469,6 +469,23 @@ export function compileProgram(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Otherwise if 'use no forget/memo' is present, we still run the code through the compiler
|
||||
* for validation but we don't mutate the babel AST. This allows us to flag if there is an
|
||||
* unused 'use no forget/memo' directive.
|
||||
*/
|
||||
if (pass.opts.ignoreUseNoForget === false && optOutDirectives.length > 0) {
|
||||
for (const directive of optOutDirectives) {
|
||||
pass.opts.logger?.logEvent(pass.filename, {
|
||||
kind: 'CompileSkip',
|
||||
fnLoc: fn.node.body.loc ?? null,
|
||||
reason: `Skipped due to '${directive.value.value}' directive.`,
|
||||
loc: directive.loc ?? null,
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pass.opts.logger?.logEvent(pass.filename, {
|
||||
kind: 'CompileSuccess',
|
||||
fnLoc: fn.node.loc ?? null,
|
||||
@@ -492,23 +509,6 @@ export function compileProgram(
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Otherwise if 'use no forget/memo' is present, we still run the code through the compiler
|
||||
* for validation but we don't mutate the babel AST. This allows us to flag if there is an
|
||||
* unused 'use no forget/memo' directive.
|
||||
*/
|
||||
if (pass.opts.ignoreUseNoForget === false && optOutDirectives.length > 0) {
|
||||
for (const directive of optOutDirectives) {
|
||||
pass.opts.logger?.logEvent(pass.filename, {
|
||||
kind: 'CompileSkip',
|
||||
fnLoc: fn.node.body.loc ?? null,
|
||||
reason: `Skipped due to '${directive.value.value}' directive.`,
|
||||
loc: directive.loc ?? null,
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!pass.opts.noEmit) {
|
||||
return compileResult.compiledFn;
|
||||
}
|
||||
|
||||
@@ -1,3 +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.
|
||||
*/
|
||||
|
||||
import type * as BabelCore from '@babel/core';
|
||||
import {hasOwnProperty} from '../Utils/utils';
|
||||
import {PluginOptions} from './Options';
|
||||
|
||||
@@ -1,3 +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.
|
||||
*/
|
||||
|
||||
import {NodePath} from '@babel/core';
|
||||
import * as t from '@babel/types';
|
||||
|
||||
|
||||
@@ -1,3 +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.
|
||||
*/
|
||||
|
||||
import {CompilerError} from '..';
|
||||
import {
|
||||
BlockId,
|
||||
|
||||
@@ -2406,6 +2406,19 @@ function lowerExpression(
|
||||
kind: 'TypeCastExpression',
|
||||
value: lowerExpressionToTemporary(builder, expr.get('expression')),
|
||||
typeAnnotation: typeAnnotation.node,
|
||||
typeAnnotationKind: 'cast',
|
||||
type: lowerType(typeAnnotation.node),
|
||||
loc: exprLoc,
|
||||
};
|
||||
}
|
||||
case 'TSSatisfiesExpression': {
|
||||
let expr = exprPath as NodePath<t.TSSatisfiesExpression>;
|
||||
const typeAnnotation = expr.get('typeAnnotation');
|
||||
return {
|
||||
kind: 'TypeCastExpression',
|
||||
value: lowerExpressionToTemporary(builder, expr.get('expression')),
|
||||
typeAnnotation: typeAnnotation.node,
|
||||
typeAnnotationKind: 'satisfies',
|
||||
type: lowerType(typeAnnotation.node),
|
||||
loc: exprLoc,
|
||||
};
|
||||
@@ -2417,6 +2430,7 @@ function lowerExpression(
|
||||
kind: 'TypeCastExpression',
|
||||
value: lowerExpressionToTemporary(builder, expr.get('expression')),
|
||||
typeAnnotation: typeAnnotation.node,
|
||||
typeAnnotationKind: 'as',
|
||||
type: lowerType(typeAnnotation.node),
|
||||
loc: exprLoc,
|
||||
};
|
||||
@@ -3595,31 +3609,40 @@ function lowerAssignment(
|
||||
|
||||
let temporary;
|
||||
if (builder.isContextIdentifier(lvalue)) {
|
||||
if (kind !== InstructionKind.Reassign && !isHoistedIdentifier) {
|
||||
if (kind === InstructionKind.Const) {
|
||||
builder.errors.push({
|
||||
reason: `Expected \`const\` declaration not to be reassigned`,
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
loc: lvalue.node.loc ?? null,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
lowerValueToTemporary(builder, {
|
||||
kind: 'DeclareContext',
|
||||
lvalue: {
|
||||
kind: InstructionKind.Let,
|
||||
place: {...place},
|
||||
},
|
||||
loc: place.loc,
|
||||
if (kind === InstructionKind.Const && !isHoistedIdentifier) {
|
||||
builder.errors.push({
|
||||
reason: `Expected \`const\` declaration not to be reassigned`,
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
loc: lvalue.node.loc ?? null,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
|
||||
temporary = lowerValueToTemporary(builder, {
|
||||
kind: 'StoreContext',
|
||||
lvalue: {place: {...place}, kind: InstructionKind.Reassign},
|
||||
value,
|
||||
loc,
|
||||
});
|
||||
if (
|
||||
kind !== InstructionKind.Const &&
|
||||
kind !== InstructionKind.Reassign &&
|
||||
kind !== InstructionKind.Let &&
|
||||
kind !== InstructionKind.Function
|
||||
) {
|
||||
builder.errors.push({
|
||||
reason: `Unexpected context variable kind`,
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
loc: lvalue.node.loc ?? null,
|
||||
suggestions: null,
|
||||
});
|
||||
temporary = lowerValueToTemporary(builder, {
|
||||
kind: 'UnsupportedNode',
|
||||
node: lvalueNode,
|
||||
loc: lvalueNode.loc ?? GeneratedSource,
|
||||
});
|
||||
} else {
|
||||
temporary = lowerValueToTemporary(builder, {
|
||||
kind: 'StoreContext',
|
||||
lvalue: {place: {...place}, kind},
|
||||
value,
|
||||
loc,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const typeAnnotation = lvalue.get('typeAnnotation');
|
||||
let type: t.FlowType | t.TSType | null;
|
||||
|
||||
@@ -1,3 +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.
|
||||
*/
|
||||
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {getScopes, recursivelyTraverseItems} from './AssertValidBlockNesting';
|
||||
import {Environment} from './Environment';
|
||||
|
||||
@@ -1,3 +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.
|
||||
*/
|
||||
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {inRange} from '../ReactiveScopes/InferReactiveScopeVariables';
|
||||
import {printDependency} from '../ReactiveScopes/PrintReactiveFunction';
|
||||
@@ -12,6 +19,7 @@ import {
|
||||
BasicBlock,
|
||||
BlockId,
|
||||
DependencyPathEntry,
|
||||
FunctionExpression,
|
||||
GeneratedSource,
|
||||
getHookKind,
|
||||
HIRFunction,
|
||||
@@ -23,6 +31,7 @@ import {
|
||||
PropertyLiteral,
|
||||
ReactiveScopeDependency,
|
||||
ScopeId,
|
||||
TInstruction,
|
||||
} from './HIR';
|
||||
|
||||
const DEBUG_PRINT = false;
|
||||
@@ -120,6 +129,33 @@ export function collectHoistablePropertyLoads(
|
||||
});
|
||||
}
|
||||
|
||||
export function collectHoistablePropertyLoadsInInnerFn(
|
||||
fnInstr: TInstruction<FunctionExpression>,
|
||||
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
|
||||
hoistableFromOptionals: ReadonlyMap<BlockId, ReactiveScopeDependency>,
|
||||
): ReadonlyMap<BlockId, BlockInfo> {
|
||||
const fn = fnInstr.value.loweredFunc.func;
|
||||
const initialContext: CollectHoistablePropertyLoadsContext = {
|
||||
temporaries,
|
||||
knownImmutableIdentifiers: new Set(),
|
||||
hoistableFromOptionals,
|
||||
registry: new PropertyPathRegistry(),
|
||||
nestedFnImmutableContext: null,
|
||||
assumedInvokedFns: fn.env.config.enableTreatFunctionDepsAsConditional
|
||||
? new Set()
|
||||
: getAssumedInvokedFunctions(fn),
|
||||
};
|
||||
const nestedFnImmutableContext = new Set(
|
||||
fn.context
|
||||
.filter(place =>
|
||||
isImmutableAtInstr(place.identifier, fnInstr.id, initialContext),
|
||||
)
|
||||
.map(place => place.identifier.id),
|
||||
);
|
||||
initialContext.nestedFnImmutableContext = nestedFnImmutableContext;
|
||||
return collectHoistablePropertyLoadsImpl(fn, initialContext);
|
||||
}
|
||||
|
||||
type CollectHoistablePropertyLoadsContext = {
|
||||
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>;
|
||||
knownImmutableIdentifiers: ReadonlySet<IdentifierId>;
|
||||
|
||||
@@ -1,3 +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.
|
||||
*/
|
||||
|
||||
import {CompilerError} from '..';
|
||||
import {assertNonNull} from './CollectHoistablePropertyLoads';
|
||||
import {
|
||||
|
||||
@@ -9,6 +9,7 @@ import {Effect, ValueKind, ValueReason} from './HIR';
|
||||
import {
|
||||
BUILTIN_SHAPES,
|
||||
BuiltInArrayId,
|
||||
BuiltInFireFunctionId,
|
||||
BuiltInFireId,
|
||||
BuiltInMapId,
|
||||
BuiltInMixedReadonlyId,
|
||||
@@ -674,7 +675,12 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
{
|
||||
positionalParams: [],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Primitive'},
|
||||
returnType: {
|
||||
kind: 'Function',
|
||||
return: {kind: 'Poly'},
|
||||
shapeId: BuiltInFireFunctionId,
|
||||
isConstructor: false,
|
||||
},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
},
|
||||
|
||||
@@ -746,6 +746,27 @@ export enum InstructionKind {
|
||||
Function = 'Function',
|
||||
}
|
||||
|
||||
export function convertHoistedLValueKind(
|
||||
kind: InstructionKind,
|
||||
): InstructionKind | null {
|
||||
switch (kind) {
|
||||
case InstructionKind.HoistedLet:
|
||||
return InstructionKind.Let;
|
||||
case InstructionKind.HoistedConst:
|
||||
return InstructionKind.Const;
|
||||
case InstructionKind.HoistedFunction:
|
||||
return InstructionKind.Function;
|
||||
case InstructionKind.Let:
|
||||
case InstructionKind.Const:
|
||||
case InstructionKind.Function:
|
||||
case InstructionKind.Reassign:
|
||||
case InstructionKind.Catch:
|
||||
return null;
|
||||
default:
|
||||
assertExhaustive(kind, 'Unexpected lvalue kind');
|
||||
}
|
||||
}
|
||||
|
||||
function _staticInvariantInstructionValueHasLocation(
|
||||
value: InstructionValue,
|
||||
): SourceLocation {
|
||||
@@ -880,8 +901,20 @@ export type InstructionValue =
|
||||
| StoreLocal
|
||||
| {
|
||||
kind: 'StoreContext';
|
||||
/**
|
||||
* StoreContext kinds:
|
||||
* Reassign: context variable reassignment in source
|
||||
* Const: const declaration + assignment in source
|
||||
* ('const' context vars are ones whose declarations are hoisted)
|
||||
* Let: let declaration + assignment in source
|
||||
* Function: function declaration in source (similar to `const`)
|
||||
*/
|
||||
lvalue: {
|
||||
kind: InstructionKind.Reassign;
|
||||
kind:
|
||||
| InstructionKind.Reassign
|
||||
| InstructionKind.Const
|
||||
| InstructionKind.Let
|
||||
| InstructionKind.Function;
|
||||
place: Place;
|
||||
};
|
||||
value: Place;
|
||||
@@ -910,13 +943,21 @@ export type InstructionValue =
|
||||
value: Place;
|
||||
loc: SourceLocation;
|
||||
}
|
||||
| {
|
||||
| ({
|
||||
kind: 'TypeCastExpression';
|
||||
value: Place;
|
||||
typeAnnotation: t.FlowType | t.TSType;
|
||||
type: Type;
|
||||
loc: SourceLocation;
|
||||
}
|
||||
} & (
|
||||
| {
|
||||
typeAnnotation: t.FlowType;
|
||||
typeAnnotationKind: 'cast';
|
||||
}
|
||||
| {
|
||||
typeAnnotation: t.TSType;
|
||||
typeAnnotationKind: 'as' | 'satisfies';
|
||||
}
|
||||
))
|
||||
| JsxExpression
|
||||
| {
|
||||
kind: 'ObjectExpression';
|
||||
@@ -1714,6 +1755,12 @@ export function isDispatcherType(id: Identifier): boolean {
|
||||
return id.type.kind === 'Function' && id.type.shapeId === 'BuiltInDispatch';
|
||||
}
|
||||
|
||||
export function isFireFunctionType(id: Identifier): boolean {
|
||||
return (
|
||||
id.type.kind === 'Function' && id.type.shapeId === 'BuiltInFireFunction'
|
||||
);
|
||||
}
|
||||
|
||||
export function isStableType(id: Identifier): boolean {
|
||||
return (
|
||||
isSetStateType(id) ||
|
||||
@@ -1724,6 +1771,40 @@ export function isStableType(id: Identifier): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
export function isStableTypeContainer(id: Identifier): boolean {
|
||||
const type_ = id.type;
|
||||
if (type_.kind !== 'Object') {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
isUseStateType(id) || // setState
|
||||
type_.shapeId === 'BuiltInUseActionState' || // setActionState
|
||||
isUseReducerType(id) || // dispatcher
|
||||
type_.shapeId === 'BuiltInUseTransition' // startTransition
|
||||
);
|
||||
}
|
||||
|
||||
export function evaluatesToStableTypeOrContainer(
|
||||
env: Environment,
|
||||
{value}: Instruction,
|
||||
): boolean {
|
||||
if (value.kind === 'CallExpression' || value.kind === 'MethodCall') {
|
||||
const callee =
|
||||
value.kind === 'CallExpression' ? value.callee : value.property;
|
||||
|
||||
const calleeHookKind = getHookKind(env, callee.identifier);
|
||||
switch (calleeHookKind) {
|
||||
case 'useState':
|
||||
case 'useReducer':
|
||||
case 'useActionState':
|
||||
case 'useRef':
|
||||
case 'useTransition':
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isUseEffectHookType(id: Identifier): boolean {
|
||||
return (
|
||||
id.type.kind === 'Function' && id.type.shapeId === 'BuiltInUseEffectHook'
|
||||
|
||||
@@ -1,3 +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.
|
||||
*/
|
||||
|
||||
import {
|
||||
HIRFunction,
|
||||
InstructionId,
|
||||
|
||||
@@ -223,6 +223,7 @@ export const BuiltInUseContextHookId = 'BuiltInUseContextHook';
|
||||
export const BuiltInUseTransitionId = 'BuiltInUseTransition';
|
||||
export const BuiltInStartTransitionId = 'BuiltInStartTransition';
|
||||
export const BuiltInFireId = 'BuiltInFire';
|
||||
export const BuiltInFireFunctionId = 'BuiltInFireFunction';
|
||||
|
||||
// ShapeRegistry with default definitions for built-ins.
|
||||
export const BUILTIN_SHAPES: ShapeRegistry = new Map();
|
||||
|
||||
@@ -1,3 +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.
|
||||
*/
|
||||
|
||||
import {
|
||||
ScopeId,
|
||||
HIRFunction,
|
||||
@@ -23,6 +30,7 @@ import {
|
||||
FunctionExpression,
|
||||
ObjectMethod,
|
||||
PropertyLiteral,
|
||||
convertHoistedLValueKind,
|
||||
} from './HIR';
|
||||
import {
|
||||
collectHoistablePropertyLoads,
|
||||
@@ -109,7 +117,7 @@ export function propagateScopeDependenciesHIR(fn: HIRFunction): void {
|
||||
}
|
||||
}
|
||||
|
||||
function findTemporariesUsedOutsideDeclaringScope(
|
||||
export function findTemporariesUsedOutsideDeclaringScope(
|
||||
fn: HIRFunction,
|
||||
): ReadonlySet<DeclarationId> {
|
||||
/*
|
||||
@@ -239,12 +247,18 @@ function isLoadContextMutable(
|
||||
id: InstructionId,
|
||||
): instrValue is LoadContext {
|
||||
if (instrValue.kind === 'LoadContext') {
|
||||
CompilerError.invariant(instrValue.place.identifier.scope != null, {
|
||||
reason:
|
||||
'[PropagateScopeDependencies] Expected all context variables to be assigned a scope',
|
||||
loc: instrValue.loc,
|
||||
});
|
||||
return id >= instrValue.place.identifier.scope.range.end;
|
||||
/**
|
||||
* Not all context variables currently have scopes due to limitations of
|
||||
* mutability analysis for function expressions.
|
||||
*
|
||||
* Currently, many function expressions references are inferred to be
|
||||
* 'Read' | 'Freeze' effects which don't replay mutable effects of captured
|
||||
* context.
|
||||
*/
|
||||
return (
|
||||
instrValue.place.identifier.scope != null &&
|
||||
id >= instrValue.place.identifier.scope.range.end
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -371,7 +385,7 @@ type Decl = {
|
||||
scope: Stack<ReactiveScope>;
|
||||
};
|
||||
|
||||
class Context {
|
||||
export class DependencyCollectionContext {
|
||||
#declarations: Map<DeclarationId, Decl> = new Map();
|
||||
#reassignments: Map<Identifier, Decl> = new Map();
|
||||
|
||||
@@ -464,6 +478,9 @@ class Context {
|
||||
}
|
||||
this.#reassignments.set(identifier, decl);
|
||||
}
|
||||
hasDeclared(identifier: Identifier): boolean {
|
||||
return this.#declarations.has(identifier.declarationId);
|
||||
}
|
||||
|
||||
// Checks if identifier is a valid dependency in the current scope
|
||||
#checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean {
|
||||
@@ -638,7 +655,10 @@ enum HIRValue {
|
||||
Terminal,
|
||||
}
|
||||
|
||||
function handleInstruction(instr: Instruction, context: Context): void {
|
||||
export function handleInstruction(
|
||||
instr: Instruction,
|
||||
context: DependencyCollectionContext,
|
||||
): void {
|
||||
const {id, value, lvalue} = instr;
|
||||
context.declare(lvalue.identifier, {
|
||||
id,
|
||||
@@ -662,21 +682,21 @@ function handleInstruction(instr: Instruction, context: Context): void {
|
||||
});
|
||||
} else if (value.kind === 'DeclareLocal' || value.kind === 'DeclareContext') {
|
||||
/*
|
||||
* Some variables may be declared and never initialized. We need
|
||||
* to retain (and hoist) these declarations if they are included
|
||||
* in a reactive scope. One approach is to simply add all `DeclareLocal`s
|
||||
* as scope declarations.
|
||||
* Some variables may be declared and never initialized. We need to retain
|
||||
* (and hoist) these declarations if they are included in a reactive scope.
|
||||
* One approach is to simply add all `DeclareLocal`s as scope declarations.
|
||||
*
|
||||
* Context variables with hoisted declarations only become live after their
|
||||
* first assignment. We only declare real DeclareLocal / DeclareContext
|
||||
* instructions (not hoisted ones) to avoid generating dependencies on
|
||||
* hoisted declarations.
|
||||
*/
|
||||
|
||||
/*
|
||||
* We add context variable declarations here, not at `StoreContext`, since
|
||||
* context Store / Loads are modeled as reads and mutates to the underlying
|
||||
* variable reference (instead of through intermediate / inlined temporaries)
|
||||
*/
|
||||
context.declare(value.lvalue.place.identifier, {
|
||||
id,
|
||||
scope: context.currentScope,
|
||||
});
|
||||
if (convertHoistedLValueKind(value.lvalue.kind) === null) {
|
||||
context.declare(value.lvalue.place.identifier, {
|
||||
id,
|
||||
scope: context.currentScope,
|
||||
});
|
||||
}
|
||||
} else if (value.kind === 'Destructure') {
|
||||
context.visitOperand(value.value);
|
||||
for (const place of eachPatternOperand(value.lvalue.pattern)) {
|
||||
@@ -688,6 +708,26 @@ function handleInstruction(instr: Instruction, context: Context): void {
|
||||
scope: context.currentScope,
|
||||
});
|
||||
}
|
||||
} else if (value.kind === 'StoreContext') {
|
||||
/**
|
||||
* Some StoreContext variables have hoisted declarations. If we're storing
|
||||
* to a context variable that hasn't yet been declared, the StoreContext is
|
||||
* the declaration.
|
||||
* (see corresponding logic in PruneHoistedContext)
|
||||
*/
|
||||
if (
|
||||
!context.hasDeclared(value.lvalue.place.identifier) ||
|
||||
value.lvalue.kind !== InstructionKind.Reassign
|
||||
) {
|
||||
context.declare(value.lvalue.place.identifier, {
|
||||
id,
|
||||
scope: context.currentScope,
|
||||
});
|
||||
}
|
||||
|
||||
for (const operand of eachInstructionValueOperand(value)) {
|
||||
context.visitOperand(operand);
|
||||
}
|
||||
} else {
|
||||
for (const operand of eachInstructionValueOperand(value)) {
|
||||
context.visitOperand(operand);
|
||||
@@ -701,7 +741,7 @@ function collectDependencies(
|
||||
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
|
||||
processedInstrsInOptional: ReadonlySet<Instruction | Terminal>,
|
||||
): Map<ReactiveScope, Array<ReactiveScopeDependency>> {
|
||||
const context = new Context(
|
||||
const context = new DependencyCollectionContext(
|
||||
usedOutsideDeclaringScope,
|
||||
temporaries,
|
||||
processedInstrsInOptional,
|
||||
|
||||
@@ -1,3 +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.
|
||||
*/
|
||||
|
||||
import {CompilerError} from '..';
|
||||
import {BlockId, GotoVariant, HIRFunction} from './HIR';
|
||||
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
/**
|
||||
* 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 * as t from '@babel/types';
|
||||
import {CompilerError, SourceLocation} from '..';
|
||||
import {
|
||||
ArrayExpression,
|
||||
@@ -14,17 +22,30 @@ import {
|
||||
ScopeId,
|
||||
ReactiveScopeDependency,
|
||||
Place,
|
||||
ReactiveScope,
|
||||
ReactiveScopeDependencies,
|
||||
Terminal,
|
||||
isUseRefType,
|
||||
isSetStateType,
|
||||
isFireFunctionType,
|
||||
makeScopeId,
|
||||
} from '../HIR';
|
||||
import {collectHoistablePropertyLoadsInInnerFn} from '../HIR/CollectHoistablePropertyLoads';
|
||||
import {collectOptionalChainSidemap} from '../HIR/CollectOptionalChainDependencies';
|
||||
import {ReactiveScopeDependencyTreeHIR} from '../HIR/DeriveMinimalDependenciesHIR';
|
||||
import {DEFAULT_EXPORT} from '../HIR/Environment';
|
||||
import {
|
||||
createTemporaryPlace,
|
||||
fixScopeAndIdentifierRanges,
|
||||
markInstructionIds,
|
||||
} from '../HIR/HIRBuilder';
|
||||
import {
|
||||
collectTemporariesSidemap,
|
||||
DependencyCollectionContext,
|
||||
handleInstruction,
|
||||
} from '../HIR/PropagateScopeDependenciesHIR';
|
||||
import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors';
|
||||
import {empty} from '../Utils/Stack';
|
||||
import {getOrInsertWith} from '../Utils/utils';
|
||||
|
||||
/**
|
||||
@@ -53,10 +74,7 @@ export function inferEffectDependencies(fn: HIRFunction): void {
|
||||
const autodepFnLoads = new Map<IdentifierId, number>();
|
||||
const autodepModuleLoads = new Map<IdentifierId, Map<string, number>>();
|
||||
|
||||
const scopeInfos = new Map<
|
||||
ScopeId,
|
||||
{pruned: boolean; deps: ReactiveScopeDependencies; hasSingleInstr: boolean}
|
||||
>();
|
||||
const scopeInfos = new Map<ScopeId, ReactiveScopeDependencies>();
|
||||
|
||||
const loadGlobals = new Set<IdentifierId>();
|
||||
|
||||
@@ -70,19 +88,18 @@ export function inferEffectDependencies(fn: HIRFunction): void {
|
||||
const reactiveIds = inferReactiveIdentifiers(fn);
|
||||
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
if (
|
||||
block.terminal.kind === 'scope' ||
|
||||
block.terminal.kind === 'pruned-scope'
|
||||
) {
|
||||
if (block.terminal.kind === 'scope') {
|
||||
const scopeBlock = fn.body.blocks.get(block.terminal.block)!;
|
||||
scopeInfos.set(block.terminal.scope.id, {
|
||||
pruned: block.terminal.kind === 'pruned-scope',
|
||||
deps: block.terminal.scope.dependencies,
|
||||
hasSingleInstr:
|
||||
scopeBlock.instructions.length === 1 &&
|
||||
scopeBlock.terminal.kind === 'goto' &&
|
||||
scopeBlock.terminal.block === block.terminal.fallthrough,
|
||||
});
|
||||
if (
|
||||
scopeBlock.instructions.length === 1 &&
|
||||
scopeBlock.terminal.kind === 'goto' &&
|
||||
scopeBlock.terminal.block === block.terminal.fallthrough
|
||||
) {
|
||||
scopeInfos.set(
|
||||
block.terminal.scope.id,
|
||||
block.terminal.scope.dependencies,
|
||||
);
|
||||
}
|
||||
}
|
||||
const rewriteInstrs = new Map<InstructionId, Array<Instruction>>();
|
||||
for (const instr of block.instructions) {
|
||||
@@ -164,22 +181,12 @@ export function inferEffectDependencies(fn: HIRFunction): void {
|
||||
fnExpr.lvalue.identifier.scope != null
|
||||
? scopeInfos.get(fnExpr.lvalue.identifier.scope.id)
|
||||
: null;
|
||||
CompilerError.invariant(scopeInfo != null, {
|
||||
reason: 'Expected function expression scope to exist',
|
||||
loc: value.loc,
|
||||
});
|
||||
if (scopeInfo.pruned || !scopeInfo.hasSingleInstr) {
|
||||
/**
|
||||
* TODO: retry pipeline that ensures effect function expressions
|
||||
* are placed into their own scope
|
||||
*/
|
||||
CompilerError.throwTodo({
|
||||
reason:
|
||||
'[InferEffectDependencies] Expected effect function to have non-pruned scope and its scope to have exactly one instruction',
|
||||
loc: fnExpr.loc,
|
||||
});
|
||||
let minimalDeps: Set<ReactiveScopeDependency>;
|
||||
if (scopeInfo != null) {
|
||||
minimalDeps = new Set(scopeInfo);
|
||||
} else {
|
||||
minimalDeps = inferMinimalDependencies(fnExpr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 1: push dependencies to the effect deps array
|
||||
*
|
||||
@@ -187,11 +194,14 @@ export function inferEffectDependencies(fn: HIRFunction): void {
|
||||
* the `infer-effect-deps/pruned-nonreactive-obj` fixture for an
|
||||
* explanation.
|
||||
*/
|
||||
for (const dep of scopeInfo.deps) {
|
||||
|
||||
const usedDeps = [];
|
||||
for (const dep of minimalDeps) {
|
||||
if (
|
||||
(isUseRefType(dep.identifier) ||
|
||||
((isUseRefType(dep.identifier) ||
|
||||
isSetStateType(dep.identifier)) &&
|
||||
!reactiveIds.has(dep.identifier.id)
|
||||
!reactiveIds.has(dep.identifier.id)) ||
|
||||
isFireFunctionType(dep.identifier)
|
||||
) {
|
||||
// exclude non-reactive hook results, which will never be in a memo block
|
||||
continue;
|
||||
@@ -205,6 +215,23 @@ export function inferEffectDependencies(fn: HIRFunction): void {
|
||||
);
|
||||
newInstructions.push(...instructions);
|
||||
effectDeps.push(place);
|
||||
usedDeps.push(dep);
|
||||
}
|
||||
|
||||
// For LSP autodeps feature.
|
||||
const decorations: Array<t.SourceLocation> = [];
|
||||
for (const loc of collectDepUsages(usedDeps, fnExpr.value)) {
|
||||
if (typeof loc === 'symbol') {
|
||||
continue;
|
||||
}
|
||||
decorations.push(loc);
|
||||
}
|
||||
if (typeof value.loc !== 'symbol') {
|
||||
fn.env.logger?.logEvent(fn.env.filename, {
|
||||
kind: 'AutoDepsDecorations',
|
||||
fnLoc: value.loc,
|
||||
decorations,
|
||||
});
|
||||
}
|
||||
|
||||
newInstructions.push({
|
||||
@@ -230,6 +257,31 @@ export function inferEffectDependencies(fn: HIRFunction): void {
|
||||
rewriteInstrs.set(instr.id, newInstructions);
|
||||
fn.env.inferredEffectLocations.add(callee.loc);
|
||||
}
|
||||
} else if (
|
||||
value.args.length >= 2 &&
|
||||
value.args.length - 1 === autodepFnLoads.get(callee.identifier.id) &&
|
||||
value.args[0] != null &&
|
||||
value.args[0].kind === 'Identifier'
|
||||
) {
|
||||
const penultimateArg = value.args[value.args.length - 2];
|
||||
const depArrayArg = value.args[value.args.length - 1];
|
||||
if (
|
||||
depArrayArg.kind !== 'Spread' &&
|
||||
penultimateArg.kind !== 'Spread' &&
|
||||
typeof depArrayArg.loc !== 'symbol' &&
|
||||
typeof penultimateArg.loc !== 'symbol' &&
|
||||
typeof value.loc !== 'symbol'
|
||||
) {
|
||||
fn.env.logger?.logEvent(fn.env.filename, {
|
||||
kind: 'AutoDepsEligible',
|
||||
fnLoc: value.loc,
|
||||
depArrayLoc: {
|
||||
...depArrayArg.loc,
|
||||
start: penultimateArg.loc.end,
|
||||
end: depArrayArg.loc.end,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -338,3 +390,163 @@ function inferReactiveIdentifiers(fn: HIRFunction): Set<IdentifierId> {
|
||||
}
|
||||
return reactiveIds;
|
||||
}
|
||||
|
||||
function collectDepUsages(
|
||||
deps: Array<ReactiveScopeDependency>,
|
||||
fnExpr: FunctionExpression,
|
||||
): Array<SourceLocation> {
|
||||
const identifiers: Map<IdentifierId, ReactiveScopeDependency> = new Map();
|
||||
const loadedDeps: Set<IdentifierId> = new Set();
|
||||
const sourceLocations = [];
|
||||
for (const dep of deps) {
|
||||
identifiers.set(dep.identifier.id, dep);
|
||||
}
|
||||
|
||||
for (const [, block] of fnExpr.loweredFunc.func.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
if (
|
||||
instr.value.kind === 'LoadLocal' &&
|
||||
identifiers.has(instr.value.place.identifier.id)
|
||||
) {
|
||||
loadedDeps.add(instr.lvalue.identifier.id);
|
||||
}
|
||||
for (const place of eachInstructionOperand(instr)) {
|
||||
if (loadedDeps.has(place.identifier.id)) {
|
||||
// TODO(@jbrown215): handle member exprs!!
|
||||
sourceLocations.push(place.identifier.loc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sourceLocations;
|
||||
}
|
||||
|
||||
function inferMinimalDependencies(
|
||||
fnInstr: TInstruction<FunctionExpression>,
|
||||
): Set<ReactiveScopeDependency> {
|
||||
const fn = fnInstr.value.loweredFunc.func;
|
||||
|
||||
const temporaries = collectTemporariesSidemap(fn, new Set());
|
||||
const {
|
||||
hoistableObjects,
|
||||
processedInstrsInOptional,
|
||||
temporariesReadInOptional,
|
||||
} = collectOptionalChainSidemap(fn);
|
||||
|
||||
const hoistablePropertyLoads = collectHoistablePropertyLoadsInInnerFn(
|
||||
fnInstr,
|
||||
temporaries,
|
||||
hoistableObjects,
|
||||
);
|
||||
const hoistableToFnEntry = hoistablePropertyLoads.get(fn.body.entry);
|
||||
CompilerError.invariant(hoistableToFnEntry != null, {
|
||||
reason:
|
||||
'[InferEffectDependencies] Internal invariant broken: missing entry block',
|
||||
loc: fnInstr.loc,
|
||||
});
|
||||
|
||||
const dependencies = inferDependencies(
|
||||
fnInstr,
|
||||
new Map([...temporaries, ...temporariesReadInOptional]),
|
||||
processedInstrsInOptional,
|
||||
);
|
||||
|
||||
const tree = new ReactiveScopeDependencyTreeHIR(
|
||||
[...hoistableToFnEntry.assumedNonNullObjects].map(o => o.fullPath),
|
||||
);
|
||||
for (const dep of dependencies) {
|
||||
tree.addDependency({...dep});
|
||||
}
|
||||
|
||||
return tree.deriveMinimalDependencies();
|
||||
}
|
||||
|
||||
function inferDependencies(
|
||||
fnInstr: TInstruction<FunctionExpression>,
|
||||
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
|
||||
processedInstrsInOptional: ReadonlySet<Instruction | Terminal>,
|
||||
): Set<ReactiveScopeDependency> {
|
||||
const fn = fnInstr.value.loweredFunc.func;
|
||||
const context = new DependencyCollectionContext(
|
||||
new Set(),
|
||||
temporaries,
|
||||
processedInstrsInOptional,
|
||||
);
|
||||
for (const dep of fn.context) {
|
||||
context.declare(dep.identifier, {
|
||||
id: makeInstructionId(0),
|
||||
scope: empty(),
|
||||
});
|
||||
}
|
||||
const placeholderScope: ReactiveScope = {
|
||||
id: makeScopeId(0),
|
||||
range: {
|
||||
start: fnInstr.id,
|
||||
end: makeInstructionId(fnInstr.id + 1),
|
||||
},
|
||||
dependencies: new Set(),
|
||||
reassignments: new Set(),
|
||||
declarations: new Map(),
|
||||
earlyReturnValue: null,
|
||||
merged: new Set(),
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
context.enterScope(placeholderScope);
|
||||
inferDependenciesInFn(fn, context, temporaries);
|
||||
context.exitScope(placeholderScope, false);
|
||||
const resultUnfiltered = context.deps.get(placeholderScope);
|
||||
CompilerError.invariant(resultUnfiltered != null, {
|
||||
reason:
|
||||
'[InferEffectDependencies] Internal invariant broken: missing scope dependencies',
|
||||
loc: fn.loc,
|
||||
});
|
||||
|
||||
const fnContext = new Set(fn.context.map(dep => dep.identifier.id));
|
||||
const result = new Set<ReactiveScopeDependency>();
|
||||
for (const dep of resultUnfiltered) {
|
||||
if (fnContext.has(dep.identifier.id)) {
|
||||
result.add(dep);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function inferDependenciesInFn(
|
||||
fn: HIRFunction,
|
||||
context: DependencyCollectionContext,
|
||||
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
|
||||
): void {
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
// Record referenced optional chains in phis
|
||||
for (const phi of block.phis) {
|
||||
for (const operand of phi.operands) {
|
||||
const maybeOptionalChain = temporaries.get(operand[1].identifier.id);
|
||||
if (maybeOptionalChain) {
|
||||
context.visitDependency(maybeOptionalChain);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const instr of block.instructions) {
|
||||
if (
|
||||
instr.value.kind === 'FunctionExpression' ||
|
||||
instr.value.kind === 'ObjectMethod'
|
||||
) {
|
||||
context.declare(instr.lvalue.identifier, {
|
||||
id: instr.id,
|
||||
scope: context.currentScope,
|
||||
});
|
||||
/**
|
||||
* Recursively visit the inner function to extract dependencies
|
||||
*/
|
||||
const innerFn = instr.value.loweredFunc.func;
|
||||
context.enterInnerFn(instr as TInstruction<FunctionExpression>, () => {
|
||||
inferDependenciesInFn(innerFn, context, temporaries);
|
||||
});
|
||||
} else {
|
||||
handleInstruction(instr, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,9 +176,15 @@ export function inferMutableLifetimes(
|
||||
if (
|
||||
instr.value.kind === 'DeclareContext' ||
|
||||
(instr.value.kind === 'StoreContext' &&
|
||||
instr.value.lvalue.kind !== InstructionKind.Reassign)
|
||||
instr.value.lvalue.kind !== InstructionKind.Reassign &&
|
||||
!contextVariableDeclarationInstructions.has(
|
||||
instr.value.lvalue.place.identifier,
|
||||
))
|
||||
) {
|
||||
// Save declarations of context variables
|
||||
/**
|
||||
* Save declarations of context variables if they hasn't already been
|
||||
* declared (due to hoisted declarations).
|
||||
*/
|
||||
contextVariableDeclarationInstructions.set(
|
||||
instr.value.lvalue.place.identifier,
|
||||
instr.id,
|
||||
|
||||
@@ -9,14 +9,19 @@ import {CompilerError} from '..';
|
||||
import {
|
||||
BlockId,
|
||||
Effect,
|
||||
Environment,
|
||||
HIRFunction,
|
||||
Identifier,
|
||||
IdentifierId,
|
||||
Instruction,
|
||||
Place,
|
||||
computePostDominatorTree,
|
||||
evaluatesToStableTypeOrContainer,
|
||||
getHookKind,
|
||||
isStableType,
|
||||
isStableTypeContainer,
|
||||
isUseOperator,
|
||||
isUseRefType,
|
||||
} from '../HIR';
|
||||
import {PostDominator} from '../HIR/Dominator';
|
||||
import {
|
||||
@@ -31,6 +36,103 @@ import {
|
||||
import DisjointSet from '../Utils/DisjointSet';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
|
||||
/**
|
||||
* Side map to track and propagate sources of stability (i.e. hook calls such as
|
||||
* `useRef()` and property reads such as `useState()[1]). Note that this
|
||||
* requires forward data flow analysis since stability is not part of React
|
||||
* Compiler's type system.
|
||||
*/
|
||||
class StableSidemap {
|
||||
map: Map<IdentifierId, {isStable: boolean}> = new Map();
|
||||
env: Environment;
|
||||
|
||||
constructor(env: Environment) {
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
handleInstruction(instr: Instruction): void {
|
||||
const {value, lvalue} = instr;
|
||||
|
||||
switch (value.kind) {
|
||||
case 'CallExpression':
|
||||
case 'MethodCall': {
|
||||
/**
|
||||
* Sources of stability are known hook calls
|
||||
*/
|
||||
if (evaluatesToStableTypeOrContainer(this.env, instr)) {
|
||||
if (isStableType(lvalue.identifier)) {
|
||||
this.map.set(lvalue.identifier.id, {
|
||||
isStable: true,
|
||||
});
|
||||
} else {
|
||||
this.map.set(lvalue.identifier.id, {
|
||||
isStable: false,
|
||||
});
|
||||
}
|
||||
} else if (
|
||||
this.env.config.enableTreatRefLikeIdentifiersAsRefs &&
|
||||
isUseRefType(lvalue.identifier)
|
||||
) {
|
||||
this.map.set(lvalue.identifier.id, {
|
||||
isStable: true,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'Destructure':
|
||||
case 'PropertyLoad': {
|
||||
/**
|
||||
* PropertyLoads may from stable containers may also produce stable
|
||||
* values. ComputedLoads are technically safe for now (as all stable
|
||||
* containers have differently-typed elements), but are not handled as
|
||||
* they should be rare anyways.
|
||||
*/
|
||||
const source =
|
||||
value.kind === 'Destructure'
|
||||
? value.value.identifier.id
|
||||
: value.object.identifier.id;
|
||||
const entry = this.map.get(source);
|
||||
if (entry) {
|
||||
for (const lvalue of eachInstructionLValue(instr)) {
|
||||
if (isStableTypeContainer(lvalue.identifier)) {
|
||||
this.map.set(lvalue.identifier.id, {
|
||||
isStable: false,
|
||||
});
|
||||
} else if (isStableType(lvalue.identifier)) {
|
||||
this.map.set(lvalue.identifier.id, {
|
||||
isStable: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'StoreLocal': {
|
||||
const entry = this.map.get(value.value.identifier.id);
|
||||
if (entry) {
|
||||
this.map.set(lvalue.identifier.id, entry);
|
||||
this.map.set(value.lvalue.place.identifier.id, entry);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'LoadLocal': {
|
||||
const entry = this.map.get(value.place.identifier.id);
|
||||
if (entry) {
|
||||
this.map.set(lvalue.identifier.id, entry);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isStable(id: IdentifierId): boolean {
|
||||
const entry = this.map.get(id);
|
||||
return entry != null ? entry.isStable : false;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Infers which `Place`s are reactive, ie may *semantically* change
|
||||
* over the course of the component/hook's lifetime. Places are reactive
|
||||
@@ -111,6 +213,7 @@ import {assertExhaustive} from '../Utils/utils';
|
||||
*/
|
||||
export function inferReactivePlaces(fn: HIRFunction): void {
|
||||
const reactiveIdentifiers = new ReactivityMap(findDisjointMutableValues(fn));
|
||||
const stableIdentifierSources = new StableSidemap(fn.env);
|
||||
for (const param of fn.params) {
|
||||
const place = param.kind === 'Identifier' ? param : param.place;
|
||||
reactiveIdentifiers.markReactive(place);
|
||||
@@ -184,6 +287,7 @@ export function inferReactivePlaces(fn: HIRFunction): void {
|
||||
}
|
||||
}
|
||||
for (const instruction of block.instructions) {
|
||||
stableIdentifierSources.handleInstruction(instruction);
|
||||
const {value} = instruction;
|
||||
let hasReactiveInput = false;
|
||||
/*
|
||||
@@ -218,7 +322,13 @@ export function inferReactivePlaces(fn: HIRFunction): void {
|
||||
|
||||
if (hasReactiveInput) {
|
||||
for (const lvalue of eachInstructionLValue(instruction)) {
|
||||
if (isStableType(lvalue.identifier)) {
|
||||
/**
|
||||
* Note that it's not correct to mark all stable-typed identifiers
|
||||
* as non-reactive, since ternaries and other value blocks can
|
||||
* produce reactive identifiers typed as these.
|
||||
* (e.g. `props.cond ? setState1 : setState2`)
|
||||
*/
|
||||
if (stableIdentifierSources.isStable(lvalue.identifier.id)) {
|
||||
continue;
|
||||
}
|
||||
reactiveIdentifiers.markReactive(lvalue);
|
||||
|
||||
@@ -111,7 +111,10 @@ export default function inferReferenceEffects(
|
||||
* Initial state contains function params
|
||||
* TODO: include module declarations here as well
|
||||
*/
|
||||
const initialState = InferenceState.empty(fn.env);
|
||||
const initialState = InferenceState.empty(
|
||||
fn.env,
|
||||
options.isFunctionExpression,
|
||||
);
|
||||
const value: InstructionValue = {
|
||||
kind: 'Primitive',
|
||||
loc: fn.loc,
|
||||
@@ -255,6 +258,7 @@ type FreezeAction = {values: Set<InstructionValue>; reason: Set<ValueReason>};
|
||||
// Maintains a mapping of top-level variables to the kind of value they hold
|
||||
class InferenceState {
|
||||
env: Environment;
|
||||
#isFunctionExpression: boolean;
|
||||
|
||||
// The kind of each value, based on its allocation site
|
||||
#values: Map<InstructionValue, AbstractValue>;
|
||||
@@ -267,16 +271,25 @@ class InferenceState {
|
||||
|
||||
constructor(
|
||||
env: Environment,
|
||||
isFunctionExpression: boolean,
|
||||
values: Map<InstructionValue, AbstractValue>,
|
||||
variables: Map<IdentifierId, Set<InstructionValue>>,
|
||||
) {
|
||||
this.env = env;
|
||||
this.#isFunctionExpression = isFunctionExpression;
|
||||
this.#values = values;
|
||||
this.#variables = variables;
|
||||
}
|
||||
|
||||
static empty(env: Environment): InferenceState {
|
||||
return new InferenceState(env, new Map(), new Map());
|
||||
static empty(
|
||||
env: Environment,
|
||||
isFunctionExpression: boolean,
|
||||
): InferenceState {
|
||||
return new InferenceState(env, isFunctionExpression, new Map(), new Map());
|
||||
}
|
||||
|
||||
get isFunctionExpression(): boolean {
|
||||
return this.#isFunctionExpression;
|
||||
}
|
||||
|
||||
// (Re)initializes a @param value with its default @param kind.
|
||||
@@ -394,9 +407,14 @@ class InferenceState {
|
||||
|
||||
freezeValues(values: Set<InstructionValue>, reason: Set<ValueReason>): void {
|
||||
for (const value of values) {
|
||||
if (value.kind === 'DeclareContext') {
|
||||
if (
|
||||
value.kind === 'DeclareContext' ||
|
||||
(value.kind === 'StoreContext' &&
|
||||
(value.lvalue.kind === InstructionKind.Let ||
|
||||
value.lvalue.kind === InstructionKind.Const))
|
||||
) {
|
||||
/**
|
||||
* Avoid freezing hoisted context declarations
|
||||
* Avoid freezing context variable declarations, hoisted or otherwise
|
||||
* function Component() {
|
||||
* const cb = useBar(() => foo(2)); // produces a hoisted context declaration
|
||||
* const foo = useFoo(); // reassigns to the context variable
|
||||
@@ -613,6 +631,7 @@ class InferenceState {
|
||||
} else {
|
||||
return new InferenceState(
|
||||
this.env,
|
||||
this.#isFunctionExpression,
|
||||
nextValues ?? new Map(this.#values),
|
||||
nextVariables ?? new Map(this.#variables),
|
||||
);
|
||||
@@ -627,6 +646,7 @@ class InferenceState {
|
||||
clone(): InferenceState {
|
||||
return new InferenceState(
|
||||
this.env,
|
||||
this.#isFunctionExpression,
|
||||
new Map(this.#values),
|
||||
new Map(this.#variables),
|
||||
);
|
||||
@@ -1591,6 +1611,14 @@ function inferBlock(
|
||||
);
|
||||
|
||||
const lvalue = instr.lvalue;
|
||||
if (instrValue.lvalue.kind !== InstructionKind.Reassign) {
|
||||
state.initialize(instrValue, {
|
||||
kind: ValueKind.Mutable,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(),
|
||||
});
|
||||
state.define(instrValue.lvalue.place, instrValue);
|
||||
}
|
||||
state.alias(lvalue, instrValue.value);
|
||||
lvalue.effect = Effect.Store;
|
||||
continuation = {kind: 'funeffects'};
|
||||
@@ -1781,8 +1809,15 @@ function inferBlock(
|
||||
if (block.terminal.kind === 'return' || block.terminal.kind === 'throw') {
|
||||
if (
|
||||
state.isDefined(operand) &&
|
||||
state.kind(operand).kind === ValueKind.Context
|
||||
((operand.identifier.type.kind === 'Function' &&
|
||||
state.isFunctionExpression) ||
|
||||
state.kind(operand).kind === ValueKind.Context)
|
||||
) {
|
||||
/**
|
||||
* Returned values should only be typed as 'frozen' if they are both (1)
|
||||
* local and (2) not a function expression which may capture and mutate
|
||||
* this function's outer context.
|
||||
*/
|
||||
effect = Effect.ConditionallyMutate;
|
||||
} else {
|
||||
effect = Effect.Freeze;
|
||||
|
||||
@@ -1000,6 +1000,14 @@ function codegenTerminal(
|
||||
lval = codegenLValue(cx, iterableItem.value.lvalue.pattern);
|
||||
break;
|
||||
}
|
||||
case 'StoreContext': {
|
||||
CompilerError.throwTodo({
|
||||
reason: 'Support non-trivial for..in inits',
|
||||
description: null,
|
||||
loc: terminal.init.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
default:
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Expected a StoreLocal or Destructure to be assigned to the collection`,
|
||||
@@ -1092,6 +1100,14 @@ function codegenTerminal(
|
||||
lval = codegenLValue(cx, iterableItem.value.lvalue.pattern);
|
||||
break;
|
||||
}
|
||||
case 'StoreContext': {
|
||||
CompilerError.throwTodo({
|
||||
reason: 'Support non-trivial for..of inits',
|
||||
description: null,
|
||||
loc: terminal.init.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
default:
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Expected a StoreLocal or Destructure to be assigned to the collection`,
|
||||
@@ -2115,10 +2131,17 @@ function codegenInstructionValue(
|
||||
}
|
||||
case 'TypeCastExpression': {
|
||||
if (t.isTSType(instrValue.typeAnnotation)) {
|
||||
value = t.tsAsExpression(
|
||||
codegenPlaceToExpression(cx, instrValue.value),
|
||||
instrValue.typeAnnotation,
|
||||
);
|
||||
if (instrValue.typeAnnotationKind === 'satisfies') {
|
||||
value = t.tsSatisfiesExpression(
|
||||
codegenPlaceToExpression(cx, instrValue.value),
|
||||
instrValue.typeAnnotation,
|
||||
);
|
||||
} else {
|
||||
value = t.tsAsExpression(
|
||||
codegenPlaceToExpression(cx, instrValue.value),
|
||||
instrValue.typeAnnotation,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
value = t.typeCastExpression(
|
||||
codegenPlaceToExpression(cx, instrValue.value),
|
||||
|
||||
@@ -7,12 +7,17 @@
|
||||
|
||||
import {CompilerError} from '..';
|
||||
import {
|
||||
DeclarationId,
|
||||
convertHoistedLValueKind,
|
||||
IdentifierId,
|
||||
InstructionId,
|
||||
InstructionKind,
|
||||
Place,
|
||||
ReactiveFunction,
|
||||
ReactiveInstruction,
|
||||
ReactiveScopeBlock,
|
||||
ReactiveStatement,
|
||||
} from '../HIR';
|
||||
import {empty, Stack} from '../Utils/Stack';
|
||||
import {
|
||||
ReactiveFunctionTransform,
|
||||
Transformed,
|
||||
@@ -22,138 +27,144 @@ import {
|
||||
/*
|
||||
* Prunes DeclareContexts lowered for HoistedConsts, and transforms any references back to its
|
||||
* original instruction kind.
|
||||
*
|
||||
* Also detects and bails out on context variables which are:
|
||||
* - function declarations, which are hoisted by JS engines to the nearest block scope
|
||||
* - referenced before they are defined (i.e. having a `DeclareContext HoistedConst`)
|
||||
* - declared
|
||||
*
|
||||
* This is because React Compiler converts a `function foo()` function declaration to
|
||||
* 1. a `let foo;` declaration before reactive memo blocks
|
||||
* 2. a `foo = function foo() {}` assignment within the block
|
||||
*
|
||||
* This means references before the assignment are invalid (see fixture
|
||||
* error.todo-functiondecl-hoisting)
|
||||
*/
|
||||
export function pruneHoistedContexts(fn: ReactiveFunction): void {
|
||||
const hoistedIdentifiers: HoistedIdentifiers = new Map();
|
||||
visitReactiveFunction(fn, new Visitor(), hoistedIdentifiers);
|
||||
visitReactiveFunction(fn, new Visitor(), {
|
||||
activeScopes: empty(),
|
||||
uninitialized: new Map(),
|
||||
});
|
||||
}
|
||||
|
||||
const REWRITTEN_HOISTED_CONST: unique symbol = Symbol(
|
||||
'REWRITTEN_HOISTED_CONST',
|
||||
);
|
||||
const REWRITTEN_HOISTED_LET: unique symbol = Symbol('REWRITTEN_HOISTED_LET');
|
||||
type VisitorState = {
|
||||
activeScopes: Stack<Set<IdentifierId>>;
|
||||
uninitialized: Map<
|
||||
IdentifierId,
|
||||
| {
|
||||
kind: 'unknown-kind';
|
||||
}
|
||||
| {
|
||||
kind: 'func';
|
||||
definition: Place | null;
|
||||
}
|
||||
>;
|
||||
};
|
||||
|
||||
type HoistedIdentifiers = Map<
|
||||
DeclarationId,
|
||||
| InstructionKind
|
||||
| typeof REWRITTEN_HOISTED_CONST
|
||||
| typeof REWRITTEN_HOISTED_LET
|
||||
>;
|
||||
|
||||
class Visitor extends ReactiveFunctionTransform<HoistedIdentifiers> {
|
||||
class Visitor extends ReactiveFunctionTransform<VisitorState> {
|
||||
override visitScope(scope: ReactiveScopeBlock, state: VisitorState): void {
|
||||
state.activeScopes = state.activeScopes.push(
|
||||
new Set(scope.scope.declarations.keys()),
|
||||
);
|
||||
/**
|
||||
* Add declared but not initialized / assigned variables. This may include
|
||||
* function declarations that escape the memo block.
|
||||
*/
|
||||
for (const decl of scope.scope.declarations.values()) {
|
||||
state.uninitialized.set(decl.identifier.id, {kind: 'unknown-kind'});
|
||||
}
|
||||
this.traverseScope(scope, state);
|
||||
state.activeScopes.pop();
|
||||
for (const decl of scope.scope.declarations.values()) {
|
||||
state.uninitialized.delete(decl.identifier.id);
|
||||
}
|
||||
}
|
||||
override visitPlace(
|
||||
_id: InstructionId,
|
||||
place: Place,
|
||||
state: VisitorState,
|
||||
): void {
|
||||
const maybeHoistedFn = state.uninitialized.get(place.identifier.id);
|
||||
if (
|
||||
maybeHoistedFn?.kind === 'func' &&
|
||||
maybeHoistedFn.definition !== place
|
||||
) {
|
||||
CompilerError.throwTodo({
|
||||
reason: '[PruneHoistedContexts] Rewrite hoisted function references',
|
||||
loc: place.loc,
|
||||
});
|
||||
}
|
||||
}
|
||||
override transformInstruction(
|
||||
instruction: ReactiveInstruction,
|
||||
state: HoistedIdentifiers,
|
||||
state: VisitorState,
|
||||
): Transformed<ReactiveStatement> {
|
||||
this.visitInstruction(instruction, state);
|
||||
|
||||
/**
|
||||
* Remove hoisted declarations to preserve TDZ
|
||||
*/
|
||||
if (
|
||||
instruction.value.kind === 'DeclareContext' &&
|
||||
instruction.value.lvalue.kind === 'HoistedConst'
|
||||
) {
|
||||
state.set(
|
||||
instruction.value.lvalue.place.identifier.declarationId,
|
||||
InstructionKind.Const,
|
||||
if (instruction.value.kind === 'DeclareContext') {
|
||||
const maybeNonHoisted = convertHoistedLValueKind(
|
||||
instruction.value.lvalue.kind,
|
||||
);
|
||||
return {kind: 'remove'};
|
||||
}
|
||||
|
||||
if (
|
||||
instruction.value.kind === 'DeclareContext' &&
|
||||
instruction.value.lvalue.kind === 'HoistedLet'
|
||||
) {
|
||||
state.set(
|
||||
instruction.value.lvalue.place.identifier.declarationId,
|
||||
InstructionKind.Let,
|
||||
);
|
||||
return {kind: 'remove'};
|
||||
}
|
||||
|
||||
if (
|
||||
instruction.value.kind === 'DeclareContext' &&
|
||||
instruction.value.lvalue.kind === 'HoistedFunction'
|
||||
) {
|
||||
state.set(
|
||||
instruction.value.lvalue.place.identifier.declarationId,
|
||||
InstructionKind.Function,
|
||||
);
|
||||
return {kind: 'remove'};
|
||||
}
|
||||
|
||||
if (instruction.value.kind === 'StoreContext') {
|
||||
const kind = state.get(
|
||||
instruction.value.lvalue.place.identifier.declarationId,
|
||||
);
|
||||
if (kind != null) {
|
||||
CompilerError.invariant(kind !== REWRITTEN_HOISTED_CONST, {
|
||||
reason: 'Expected exactly one store to a hoisted const variable',
|
||||
loc: instruction.loc,
|
||||
});
|
||||
if (maybeNonHoisted != null) {
|
||||
if (
|
||||
kind === InstructionKind.Const ||
|
||||
kind === InstructionKind.Function
|
||||
maybeNonHoisted === InstructionKind.Function &&
|
||||
state.uninitialized.has(instruction.value.lvalue.place.identifier.id)
|
||||
) {
|
||||
state.set(
|
||||
instruction.value.lvalue.place.identifier.declarationId,
|
||||
REWRITTEN_HOISTED_CONST,
|
||||
);
|
||||
return {
|
||||
kind: 'replace',
|
||||
value: {
|
||||
kind: 'instruction',
|
||||
instruction: {
|
||||
...instruction,
|
||||
value: {
|
||||
...instruction.value,
|
||||
lvalue: {
|
||||
...instruction.value.lvalue,
|
||||
kind,
|
||||
},
|
||||
type: null,
|
||||
kind: 'StoreLocal',
|
||||
},
|
||||
},
|
||||
state.uninitialized.set(
|
||||
instruction.value.lvalue.place.identifier.id,
|
||||
{
|
||||
kind: 'func',
|
||||
definition: null,
|
||||
},
|
||||
};
|
||||
} else if (kind !== REWRITTEN_HOISTED_LET) {
|
||||
/**
|
||||
* Context variables declared with let may have reassignments. Only
|
||||
* insert a `DeclareContext` for the first encountered `StoreContext`
|
||||
* instruction.
|
||||
*/
|
||||
state.set(
|
||||
instruction.value.lvalue.place.identifier.declarationId,
|
||||
REWRITTEN_HOISTED_LET,
|
||||
);
|
||||
return {
|
||||
kind: 'replace-many',
|
||||
value: [
|
||||
{
|
||||
kind: 'instruction',
|
||||
instruction: {
|
||||
id: instruction.id,
|
||||
lvalue: null,
|
||||
value: {
|
||||
kind: 'DeclareContext',
|
||||
lvalue: {
|
||||
kind: InstructionKind.Let,
|
||||
place: {...instruction.value.lvalue.place},
|
||||
},
|
||||
loc: instruction.value.loc,
|
||||
},
|
||||
loc: instruction.loc,
|
||||
},
|
||||
},
|
||||
{kind: 'instruction', instruction},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {kind: 'remove'};
|
||||
}
|
||||
}
|
||||
if (
|
||||
instruction.value.kind === 'StoreContext' &&
|
||||
instruction.value.lvalue.kind !== InstructionKind.Reassign
|
||||
) {
|
||||
/**
|
||||
* Rewrite StoreContexts let/const that will be pre-declared in
|
||||
* codegen to reassignments.
|
||||
*/
|
||||
const lvalueId = instruction.value.lvalue.place.identifier.id;
|
||||
const isDeclaredByScope = state.activeScopes.find(scope =>
|
||||
scope.has(lvalueId),
|
||||
);
|
||||
if (isDeclaredByScope) {
|
||||
if (
|
||||
instruction.value.lvalue.kind === InstructionKind.Let ||
|
||||
instruction.value.lvalue.kind === InstructionKind.Const
|
||||
) {
|
||||
instruction.value.lvalue.kind = InstructionKind.Reassign;
|
||||
} else if (instruction.value.lvalue.kind === InstructionKind.Function) {
|
||||
const maybeHoistedFn = state.uninitialized.get(lvalueId);
|
||||
if (maybeHoistedFn != null) {
|
||||
CompilerError.invariant(maybeHoistedFn.kind === 'func', {
|
||||
reason: '[PruneHoistedContexts] Unexpected hoisted function',
|
||||
loc: instruction.loc,
|
||||
});
|
||||
maybeHoistedFn.definition = instruction.value.lvalue.place;
|
||||
/**
|
||||
* References to hoisted functions are now "safe" as variable assignments
|
||||
* have finished.
|
||||
*/
|
||||
state.uninitialized.delete(lvalueId);
|
||||
}
|
||||
} else {
|
||||
CompilerError.throwTodo({
|
||||
reason: '[PruneHoistedContexts] Unexpected kind',
|
||||
description: `(${instruction.value.lvalue.kind})`,
|
||||
loc: instruction.loc,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.visitInstruction(instruction, state);
|
||||
return {kind: 'keep'};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +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.
|
||||
*/
|
||||
|
||||
import {
|
||||
BlockId,
|
||||
ReactiveFunction,
|
||||
|
||||
@@ -34,7 +34,11 @@ import {
|
||||
} from '../HIR';
|
||||
import {createTemporaryPlace, markInstructionIds} from '../HIR/HIRBuilder';
|
||||
import {getOrInsertWith} from '../Utils/utils';
|
||||
import {BuiltInFireId, DefaultNonmutatingHook} from '../HIR/ObjectShape';
|
||||
import {
|
||||
BuiltInFireFunctionId,
|
||||
BuiltInFireId,
|
||||
DefaultNonmutatingHook,
|
||||
} from '../HIR/ObjectShape';
|
||||
import {eachInstructionOperand} from '../HIR/visitors';
|
||||
import {printSourceLocationLine} from '../HIR/PrintHIR';
|
||||
import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment';
|
||||
@@ -633,6 +637,13 @@ class Context {
|
||||
() => createTemporaryPlace(this.#env, GeneratedSource),
|
||||
);
|
||||
|
||||
fireFunctionBinding.identifier.type = {
|
||||
kind: 'Function',
|
||||
shapeId: BuiltInFireFunctionId,
|
||||
return: {kind: 'Poly'},
|
||||
isConstructor: false,
|
||||
};
|
||||
|
||||
this.#capturedCalleeIdentifierIds.set(callee.identifier.id, {
|
||||
fireFunctionBinding,
|
||||
capturedCalleeIdentifier: callee.identifier,
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
export {transformFire} from './TransformFire';
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerError, EnvironmentConfig, ErrorSeverity} from '..';
|
||||
import {HIRFunction, IdentifierId} from '../HIR';
|
||||
import {DEFAULT_GLOBALS} from '../HIR/Globals';
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Fixture currently fails with
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok) <div>{"result":{"value":2},"fn":{"kind":"Function","result":{"value":2}},"shouldInvokeFns":true}</div>
|
||||
* Forget:
|
||||
* (kind: exception) bar is not a function
|
||||
*/
|
||||
function Foo({value}) {
|
||||
const result = bar();
|
||||
function bar() {
|
||||
return {value};
|
||||
}
|
||||
return <Stringify result={result} fn={bar} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{value: 2}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Fixture currently fails with
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok) <div>{"result":{"value":2},"fn":{"kind":"Function","result":{"value":2}},"shouldInvokeFns":true}</div>
|
||||
* Forget:
|
||||
* (kind: exception) bar is not a function
|
||||
*/
|
||||
function Foo(t0) {
|
||||
const $ = _c(6);
|
||||
const { value } = t0;
|
||||
let bar;
|
||||
let result;
|
||||
if ($[0] !== value) {
|
||||
result = bar();
|
||||
bar = function bar() {
|
||||
return { value };
|
||||
};
|
||||
$[0] = value;
|
||||
$[1] = bar;
|
||||
$[2] = result;
|
||||
} else {
|
||||
bar = $[1];
|
||||
result = $[2];
|
||||
}
|
||||
let t1;
|
||||
if ($[3] !== bar || $[4] !== result) {
|
||||
t1 = <Stringify result={result} fn={bar} shouldInvokeFns={true} />;
|
||||
$[3] = bar;
|
||||
$[4] = result;
|
||||
$[5] = t1;
|
||||
} else {
|
||||
t1 = $[5];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ value: 2 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
@@ -34,8 +34,7 @@ function bar(a, b) {
|
||||
if ($[0] !== a || $[1] !== b) {
|
||||
const x = [a, b];
|
||||
y = {};
|
||||
let t;
|
||||
t = {};
|
||||
let t = {};
|
||||
|
||||
y = x[0][1];
|
||||
t = x[1][0];
|
||||
|
||||
@@ -35,8 +35,7 @@ function bar(a, b) {
|
||||
if ($[0] !== a || $[1] !== b) {
|
||||
const x = [a, b];
|
||||
y = {};
|
||||
let t;
|
||||
t = {};
|
||||
let t = {};
|
||||
const f0 = function () {
|
||||
y = x[0][1];
|
||||
t = x[1][0];
|
||||
|
||||
@@ -33,8 +33,7 @@ function useTest() {
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
let w;
|
||||
w = {};
|
||||
let w = {};
|
||||
|
||||
const t1 = (w = 42);
|
||||
const t2 = w;
|
||||
|
||||
@@ -30,8 +30,7 @@ function Component(props) {
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
let x;
|
||||
x = null;
|
||||
let x = null;
|
||||
const callback = () => {
|
||||
console.log(x);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Fixture currently fails with
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok) <div>{"result":{"value":2},"fn":{"kind":"Function","result":{"value":2}},"shouldInvokeFns":true}</div>
|
||||
* Forget:
|
||||
* (kind: exception) bar is not a function
|
||||
*/
|
||||
function Foo({value}) {
|
||||
const result = bar();
|
||||
function bar() {
|
||||
return {value};
|
||||
}
|
||||
return <Stringify result={result} fn={bar} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{value: 2}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
10 | */
|
||||
11 | function Foo({value}) {
|
||||
> 12 | const result = bar();
|
||||
| ^^^ Todo: [PruneHoistedContexts] Rewrite hoisted function references (12:12)
|
||||
13 | function bar() {
|
||||
14 | return {value};
|
||||
15 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {Stringify} from 'shared-runtime';
|
||||
/**
|
||||
* Also see error.todo-functiondecl-hoisting.tsx which shows *invalid*
|
||||
* compilation cases.
|
||||
*
|
||||
* This bailout specifically is a false positive for since this function's only
|
||||
* reference-before-definition are within other functions which are not invoked.
|
||||
*/
|
||||
function Foo() {
|
||||
'use memo';
|
||||
|
||||
function foo() {
|
||||
return bar();
|
||||
}
|
||||
function bar() {
|
||||
return 42;
|
||||
}
|
||||
|
||||
return <Stringify fn1={foo} fn2={bar} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
13 | return bar();
|
||||
14 | }
|
||||
> 15 | function bar() {
|
||||
| ^^^ Todo: [PruneHoistedContexts] Rewrite hoisted function references (15:15)
|
||||
16 | return 42;
|
||||
17 | }
|
||||
18 |
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import {Stringify} from 'shared-runtime';
|
||||
/**
|
||||
* Also see error.todo-functiondecl-hoisting.tsx which shows *invalid*
|
||||
* compilation cases.
|
||||
*
|
||||
* This bailout specifically is a false positive for since this function's only
|
||||
* reference-before-definition are within other functions which are not invoked.
|
||||
*/
|
||||
function Foo() {
|
||||
'use memo';
|
||||
|
||||
function foo() {
|
||||
return bar();
|
||||
}
|
||||
function bar() {
|
||||
return 42;
|
||||
}
|
||||
|
||||
return <Stringify fn1={foo} fn2={bar} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [],
|
||||
};
|
||||
@@ -2,13 +2,22 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
function Component() {
|
||||
const data = useData();
|
||||
const data = useIdentity(
|
||||
new Map([
|
||||
[0, 'value0'],
|
||||
[1, 'value1'],
|
||||
])
|
||||
);
|
||||
const items = [];
|
||||
// NOTE: `i` is a context variable because it's reassigned and also referenced
|
||||
// within a closure, the `onClick` handler of each item
|
||||
for (let i = MIN; i <= MAX; i += INCREMENT) {
|
||||
items.push(<div key={i} onClick={() => data.set(i)} />);
|
||||
items.push(
|
||||
<Stringify key={i} onClick={() => data.get(i)} shouldInvokeFns={true} />
|
||||
);
|
||||
}
|
||||
return <>{items}</>;
|
||||
}
|
||||
@@ -17,10 +26,6 @@ const MIN = 0;
|
||||
const MAX = 3;
|
||||
const INCREMENT = 1;
|
||||
|
||||
function useData() {
|
||||
return new Map();
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
params: [],
|
||||
fn: Component,
|
||||
@@ -32,41 +37,47 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { Stringify, useIdentity } from "shared-runtime";
|
||||
|
||||
function Component() {
|
||||
const $ = _c(2);
|
||||
const data = useData();
|
||||
const $ = _c(3);
|
||||
let t0;
|
||||
if ($[0] !== data) {
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = new Map([
|
||||
[0, "value0"],
|
||||
[1, "value1"],
|
||||
]);
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
const data = useIdentity(t0);
|
||||
let t1;
|
||||
if ($[1] !== data) {
|
||||
const items = [];
|
||||
for (let i = MIN; i <= MAX; i = i + INCREMENT, i) {
|
||||
items.push(<div key={i} onClick={() => data.set(i)} />);
|
||||
items.push(
|
||||
<Stringify
|
||||
key={i}
|
||||
onClick={() => data.get(i)}
|
||||
shouldInvokeFns={true}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
t0 = <>{items}</>;
|
||||
$[0] = data;
|
||||
$[1] = t0;
|
||||
t1 = <>{items}</>;
|
||||
$[1] = data;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
t1 = $[2];
|
||||
}
|
||||
return t0;
|
||||
return t1;
|
||||
}
|
||||
|
||||
const MIN = 0;
|
||||
const MAX = 3;
|
||||
const INCREMENT = 1;
|
||||
|
||||
function useData() {
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = new Map();
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
params: [],
|
||||
fn: Component,
|
||||
@@ -75,4 +86,4 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div></div><div></div><div></div><div></div>
|
||||
(kind: ok) <div>{"onClick":{"kind":"Function","result":"value0"},"shouldInvokeFns":true}</div><div>{"onClick":{"kind":"Function","result":"value1"},"shouldInvokeFns":true}</div><div>{"onClick":{"kind":"Function"},"shouldInvokeFns":true}</div><div>{"onClick":{"kind":"Function"},"shouldInvokeFns":true}</div>
|
||||
@@ -1,10 +1,19 @@
|
||||
import {Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
function Component() {
|
||||
const data = useData();
|
||||
const data = useIdentity(
|
||||
new Map([
|
||||
[0, 'value0'],
|
||||
[1, 'value1'],
|
||||
])
|
||||
);
|
||||
const items = [];
|
||||
// NOTE: `i` is a context variable because it's reassigned and also referenced
|
||||
// within a closure, the `onClick` handler of each item
|
||||
for (let i = MIN; i <= MAX; i += INCREMENT) {
|
||||
items.push(<div key={i} onClick={() => data.set(i)} />);
|
||||
items.push(
|
||||
<Stringify key={i} onClick={() => data.get(i)} shouldInvokeFns={true} />
|
||||
);
|
||||
}
|
||||
return <>{items}</>;
|
||||
}
|
||||
@@ -13,10 +22,6 @@ const MIN = 0;
|
||||
const MAX = 3;
|
||||
const INCREMENT = 1;
|
||||
|
||||
function useData() {
|
||||
return new Map();
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
params: [],
|
||||
fn: Component,
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {CONST_TRUE, useIdentity} from 'shared-runtime';
|
||||
|
||||
const hidden = CONST_TRUE;
|
||||
function useFoo() {
|
||||
const makeCb = useIdentity(() => {
|
||||
const logIntervalId = () => {
|
||||
log(intervalId);
|
||||
};
|
||||
|
||||
let intervalId;
|
||||
if (!hidden) {
|
||||
intervalId = 2;
|
||||
}
|
||||
return () => {
|
||||
logIntervalId();
|
||||
};
|
||||
});
|
||||
|
||||
return <Stringify fn={makeCb()} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { CONST_TRUE, useIdentity } from "shared-runtime";
|
||||
|
||||
const hidden = CONST_TRUE;
|
||||
function useFoo() {
|
||||
const $ = _c(4);
|
||||
const makeCb = useIdentity(_temp);
|
||||
let t0;
|
||||
if ($[0] !== makeCb) {
|
||||
t0 = makeCb();
|
||||
$[0] = makeCb;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
let t1;
|
||||
if ($[2] !== t0) {
|
||||
t1 = <Stringify fn={t0} shouldInvokeFns={true} />;
|
||||
$[2] = t0;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
function _temp() {
|
||||
const logIntervalId = () => {
|
||||
log(intervalId);
|
||||
};
|
||||
let intervalId;
|
||||
if (!hidden) {
|
||||
intervalId = 2;
|
||||
}
|
||||
return () => {
|
||||
logIntervalId();
|
||||
};
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Stringify is not defined
|
||||
@@ -0,0 +1,25 @@
|
||||
import {CONST_TRUE, useIdentity} from 'shared-runtime';
|
||||
|
||||
const hidden = CONST_TRUE;
|
||||
function useFoo() {
|
||||
const makeCb = useIdentity(() => {
|
||||
const logIntervalId = () => {
|
||||
log(intervalId);
|
||||
};
|
||||
|
||||
let intervalId;
|
||||
if (!hidden) {
|
||||
intervalId = 2;
|
||||
}
|
||||
return () => {
|
||||
logIntervalId();
|
||||
};
|
||||
});
|
||||
|
||||
return <Stringify fn={makeCb()} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [],
|
||||
};
|
||||
@@ -30,8 +30,7 @@ function Foo() {
|
||||
getX = () => x;
|
||||
console.log(getX());
|
||||
|
||||
let x;
|
||||
x = 4;
|
||||
let x = 4;
|
||||
x = x + 5;
|
||||
$[0] = getX;
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {CONST_NUMBER1, Stringify} from 'shared-runtime';
|
||||
|
||||
function useHook({cond}) {
|
||||
'use memo';
|
||||
const getX = () => x;
|
||||
|
||||
let x;
|
||||
if (cond) {
|
||||
x = CONST_NUMBER1;
|
||||
}
|
||||
return <Stringify getX={getX} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: () => {},
|
||||
params: [{cond: true}],
|
||||
sequentialRenders: [{cond: true}, {cond: true}, {cond: false}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { CONST_NUMBER1, Stringify } from "shared-runtime";
|
||||
|
||||
function useHook(t0) {
|
||||
"use memo";
|
||||
const $ = _c(2);
|
||||
const { cond } = t0;
|
||||
let t1;
|
||||
if ($[0] !== cond) {
|
||||
const getX = () => x;
|
||||
|
||||
let x;
|
||||
if (cond) {
|
||||
x = CONST_NUMBER1;
|
||||
}
|
||||
|
||||
t1 = <Stringify getX={getX} shouldInvokeFns={true} />;
|
||||
$[0] = cond;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: () => {},
|
||||
params: [{ cond: true }],
|
||||
sequentialRenders: [{ cond: true }, { cond: true }, { cond: false }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import {CONST_NUMBER1, Stringify} from 'shared-runtime';
|
||||
|
||||
function useHook({cond}) {
|
||||
'use memo';
|
||||
const getX = () => x;
|
||||
|
||||
let x;
|
||||
if (cond) {
|
||||
x = CONST_NUMBER1;
|
||||
}
|
||||
return <Stringify getX={getX} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: () => {},
|
||||
params: [{cond: true}],
|
||||
sequentialRenders: [{cond: true}, {cond: true}, {cond: false}],
|
||||
};
|
||||
@@ -36,8 +36,7 @@ function hoisting(cond) {
|
||||
items.push(bar());
|
||||
};
|
||||
|
||||
let bar;
|
||||
bar = _temp;
|
||||
let bar = _temp;
|
||||
foo();
|
||||
}
|
||||
$[0] = cond;
|
||||
|
||||
@@ -41,11 +41,9 @@ function hoisting() {
|
||||
return result;
|
||||
};
|
||||
|
||||
let foo;
|
||||
foo = () => bar + baz;
|
||||
let foo = () => bar + baz;
|
||||
|
||||
let bar;
|
||||
bar = 3;
|
||||
let bar = 3;
|
||||
const baz = 2;
|
||||
t0 = qux();
|
||||
$[0] = t0;
|
||||
|
||||
@@ -37,8 +37,7 @@ function useHook(t0) {
|
||||
if ($[0] !== cond) {
|
||||
const getX = () => x;
|
||||
|
||||
let x;
|
||||
x = CONST_NUMBER0;
|
||||
let x = CONST_NUMBER0;
|
||||
if (cond) {
|
||||
x = x + CONST_NUMBER1;
|
||||
x;
|
||||
|
||||
@@ -38,8 +38,7 @@ function useHook(t0) {
|
||||
if ($[0] !== cond) {
|
||||
const getX = () => x;
|
||||
|
||||
let x;
|
||||
x = CONST_NUMBER0;
|
||||
let x = CONST_NUMBER0;
|
||||
if (cond) {
|
||||
x = x + CONST_NUMBER1;
|
||||
x;
|
||||
|
||||
@@ -29,10 +29,8 @@ function hoisting() {
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
foo = () => bar + baz;
|
||||
|
||||
let bar;
|
||||
bar = 3;
|
||||
let baz;
|
||||
baz = 2;
|
||||
let bar = 3;
|
||||
let baz = 2;
|
||||
$[0] = foo;
|
||||
} else {
|
||||
foo = $[0];
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import {useEffect} from 'react';
|
||||
import {print} from 'shared-runtime';
|
||||
|
||||
function Component({foo}) {
|
||||
const arr = [];
|
||||
// Taking either arr[0].value or arr as a dependency is reasonable
|
||||
// as long as developers know what to expect.
|
||||
useEffect(() => print(arr[0].value));
|
||||
arr.push({value: foo});
|
||||
return arr;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import { useEffect } from "react";
|
||||
import { print } from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
const { foo } = t0;
|
||||
const arr = [];
|
||||
|
||||
useEffect(() => print(arr[0].value), [arr[0].value]);
|
||||
arr.push({ value: foo });
|
||||
return arr;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -0,0 +1,12 @@
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import {useEffect} from 'react';
|
||||
import {print} from 'shared-runtime';
|
||||
|
||||
function Component({foo}) {
|
||||
const arr = [];
|
||||
// Taking either arr[0].value or arr as a dependency is reasonable
|
||||
// as long as developers know what to expect.
|
||||
useEffect(() => print(arr[0].value));
|
||||
arr.push({value: foo});
|
||||
return arr;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
|
||||
import {useEffect, useRef} from 'react';
|
||||
import {print} from 'shared-runtime';
|
||||
|
||||
function Component({arrRef}) {
|
||||
// Avoid taking arr.current as a dependency
|
||||
useEffect(() => print(arrRef.current));
|
||||
arrRef.current.val = 2;
|
||||
return arrRef;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { print } from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
const { arrRef } = t0;
|
||||
|
||||
useEffect(() => print(arrRef.current), [arrRef]);
|
||||
arrRef.current.val = 2;
|
||||
return arrRef;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -0,0 +1,11 @@
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
|
||||
import {useEffect, useRef} from 'react';
|
||||
import {print} from 'shared-runtime';
|
||||
|
||||
function Component({arrRef}) {
|
||||
// Avoid taking arr.current as a dependency
|
||||
useEffect(() => print(arrRef.current));
|
||||
arrRef.current.val = 2;
|
||||
return arrRef;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import {useEffect} from 'react';
|
||||
|
||||
function Component({foo}) {
|
||||
const arr = [];
|
||||
useEffect(() => arr.push(foo));
|
||||
arr.push(2);
|
||||
return arr;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import { useEffect } from "react";
|
||||
|
||||
function Component(t0) {
|
||||
const { foo } = t0;
|
||||
const arr = [];
|
||||
useEffect(() => arr.push(foo), [arr, foo]);
|
||||
arr.push(2);
|
||||
return arr;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -0,0 +1,9 @@
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import {useEffect} from 'react';
|
||||
|
||||
function Component({foo}) {
|
||||
const arr = [];
|
||||
useEffect(() => arr.push(foo));
|
||||
arr.push(2);
|
||||
return arr;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies
|
||||
import {useRef, useEffect} from 'react';
|
||||
import {print, mutate} from 'shared-runtime';
|
||||
|
||||
function Component({cond}) {
|
||||
const arr = useRef([]);
|
||||
const other = useRef([]);
|
||||
// Although arr and other are both stable, derived is not
|
||||
const derived = cond ? arr : other;
|
||||
useEffect(() => {
|
||||
mutate(derived.current);
|
||||
print(derived.current);
|
||||
});
|
||||
return arr;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
|
||||
import { useRef, useEffect } from "react";
|
||||
import { print, mutate } from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
const $ = _c(4);
|
||||
const { cond } = t0;
|
||||
let t1;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = [];
|
||||
$[0] = t1;
|
||||
} else {
|
||||
t1 = $[0];
|
||||
}
|
||||
const arr = useRef(t1);
|
||||
let t2;
|
||||
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t2 = [];
|
||||
$[1] = t2;
|
||||
} else {
|
||||
t2 = $[1];
|
||||
}
|
||||
const other = useRef(t2);
|
||||
|
||||
const derived = cond ? arr : other;
|
||||
let t3;
|
||||
if ($[2] !== derived) {
|
||||
t3 = () => {
|
||||
mutate(derived.current);
|
||||
print(derived.current);
|
||||
};
|
||||
$[2] = derived;
|
||||
$[3] = t3;
|
||||
} else {
|
||||
t3 = $[3];
|
||||
}
|
||||
useEffect(t3, [derived]);
|
||||
return arr;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -0,0 +1,15 @@
|
||||
// @inferEffectDependencies
|
||||
import {useRef, useEffect} from 'react';
|
||||
import {print, mutate} from 'shared-runtime';
|
||||
|
||||
function Component({cond}) {
|
||||
const arr = useRef([]);
|
||||
const other = useRef([]);
|
||||
// Although arr and other are both stable, derived is not
|
||||
const derived = cond ? arr : other;
|
||||
useEffect(() => {
|
||||
mutate(derived.current);
|
||||
print(derived.current);
|
||||
});
|
||||
return arr;
|
||||
}
|
||||
@@ -83,10 +83,10 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
import { c as _c2 } from "react/compiler-runtime"; // @inlineJsxTransform
|
||||
|
||||
function Parent(t0) {
|
||||
const $ = _c2(2);
|
||||
const $ = _c2(3);
|
||||
const { children, ref } = t0;
|
||||
let t1;
|
||||
if ($[0] !== children) {
|
||||
if ($[0] !== children || $[1] !== ref) {
|
||||
if (DEV) {
|
||||
t1 = <div ref={ref}>{children}</div>;
|
||||
} else {
|
||||
@@ -99,9 +99,10 @@ function Parent(t0) {
|
||||
};
|
||||
}
|
||||
$[0] = children;
|
||||
$[1] = t1;
|
||||
$[1] = ref;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
t1 = $[2];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
function Component({prop1, prop2}) {
|
||||
'use memo';
|
||||
|
||||
const data = useIdentity(
|
||||
new Map([
|
||||
[0, 'value0'],
|
||||
[1, 'value1'],
|
||||
])
|
||||
);
|
||||
let i = 0;
|
||||
const items = [];
|
||||
items.push(
|
||||
<Stringify
|
||||
key={i}
|
||||
onClick={() => data.get(i) + prop1}
|
||||
shouldInvokeFns={true}
|
||||
/>
|
||||
);
|
||||
i = i + 1;
|
||||
items.push(
|
||||
<Stringify
|
||||
key={i}
|
||||
onClick={() => data.get(i) + prop2}
|
||||
shouldInvokeFns={true}
|
||||
/>
|
||||
);
|
||||
return <>{items}</>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{prop1: 'prop1', prop2: 'prop2'}],
|
||||
sequentialRenders: [
|
||||
{prop1: 'prop1', prop2: 'prop2'},
|
||||
{prop1: 'prop1', prop2: 'prop2'},
|
||||
{prop1: 'changed', prop2: 'prop2'},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { Stringify, useIdentity } from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
"use memo";
|
||||
const $ = _c(12);
|
||||
const { prop1, prop2 } = t0;
|
||||
let t1;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = new Map([
|
||||
[0, "value0"],
|
||||
[1, "value1"],
|
||||
]);
|
||||
$[0] = t1;
|
||||
} else {
|
||||
t1 = $[0];
|
||||
}
|
||||
const data = useIdentity(t1);
|
||||
let t2;
|
||||
if ($[1] !== data || $[2] !== prop1 || $[3] !== prop2) {
|
||||
let i = 0;
|
||||
const items = [];
|
||||
items.push(
|
||||
<Stringify
|
||||
key={i}
|
||||
onClick={() => data.get(i) + prop1}
|
||||
shouldInvokeFns={true}
|
||||
/>,
|
||||
);
|
||||
i = i + 1;
|
||||
|
||||
const t3 = i;
|
||||
let t4;
|
||||
if ($[5] !== data || $[6] !== i || $[7] !== prop2) {
|
||||
t4 = () => data.get(i) + prop2;
|
||||
$[5] = data;
|
||||
$[6] = i;
|
||||
$[7] = prop2;
|
||||
$[8] = t4;
|
||||
} else {
|
||||
t4 = $[8];
|
||||
}
|
||||
let t5;
|
||||
if ($[9] !== t3 || $[10] !== t4) {
|
||||
t5 = <Stringify key={t3} onClick={t4} shouldInvokeFns={true} />;
|
||||
$[9] = t3;
|
||||
$[10] = t4;
|
||||
$[11] = t5;
|
||||
} else {
|
||||
t5 = $[11];
|
||||
}
|
||||
items.push(t5);
|
||||
t2 = <>{items}</>;
|
||||
$[1] = data;
|
||||
$[2] = prop1;
|
||||
$[3] = prop2;
|
||||
$[4] = t2;
|
||||
} else {
|
||||
t2 = $[4];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ prop1: "prop1", prop2: "prop2" }],
|
||||
sequentialRenders: [
|
||||
{ prop1: "prop1", prop2: "prop2" },
|
||||
{ prop1: "prop1", prop2: "prop2" },
|
||||
{ prop1: "changed", prop2: "prop2" },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"onClick":{"kind":"Function","result":"value1prop1"},"shouldInvokeFns":true}</div><div>{"onClick":{"kind":"Function","result":"value1prop2"},"shouldInvokeFns":true}</div>
|
||||
<div>{"onClick":{"kind":"Function","result":"value1prop1"},"shouldInvokeFns":true}</div><div>{"onClick":{"kind":"Function","result":"value1prop2"},"shouldInvokeFns":true}</div>
|
||||
<div>{"onClick":{"kind":"Function","result":"value1changed"},"shouldInvokeFns":true}</div><div>{"onClick":{"kind":"Function","result":"value1prop2"},"shouldInvokeFns":true}</div>
|
||||
@@ -0,0 +1,40 @@
|
||||
import {Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
function Component({prop1, prop2}) {
|
||||
'use memo';
|
||||
|
||||
const data = useIdentity(
|
||||
new Map([
|
||||
[0, 'value0'],
|
||||
[1, 'value1'],
|
||||
])
|
||||
);
|
||||
let i = 0;
|
||||
const items = [];
|
||||
items.push(
|
||||
<Stringify
|
||||
key={i}
|
||||
onClick={() => data.get(i) + prop1}
|
||||
shouldInvokeFns={true}
|
||||
/>
|
||||
);
|
||||
i = i + 1;
|
||||
items.push(
|
||||
<Stringify
|
||||
key={i}
|
||||
onClick={() => data.get(i) + prop2}
|
||||
shouldInvokeFns={true}
|
||||
/>
|
||||
);
|
||||
return <>{items}</>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{prop1: 'prop1', prop2: 'prop2'}],
|
||||
sequentialRenders: [
|
||||
{prop1: 'prop1', prop2: 'prop2'},
|
||||
{prop1: 'prop1', prop2: 'prop2'},
|
||||
{prop1: 'changed', prop2: 'prop2'},
|
||||
],
|
||||
};
|
||||
@@ -37,8 +37,7 @@ function Component() {
|
||||
}
|
||||
const x = t0;
|
||||
|
||||
let x_0;
|
||||
x_0 = 56;
|
||||
let x_0 = 56;
|
||||
const fn = function () {
|
||||
x_0 = 42;
|
||||
};
|
||||
|
||||
@@ -33,8 +33,7 @@ function component(a) {
|
||||
m(x);
|
||||
};
|
||||
|
||||
let x;
|
||||
x = { a };
|
||||
let x = { a };
|
||||
m(x);
|
||||
$[0] = a;
|
||||
$[1] = y;
|
||||
|
||||
@@ -65,8 +65,7 @@ function useBar(t0, cond) {
|
||||
} else {
|
||||
t1 = $[0];
|
||||
}
|
||||
let x;
|
||||
x = useIdentity(t1);
|
||||
let x = useIdentity(t1);
|
||||
if (cond) {
|
||||
x = b;
|
||||
}
|
||||
|
||||
@@ -47,8 +47,7 @@ function Foo(t0) {
|
||||
if ($[0] !== arr1 || $[1] !== arr2 || $[2] !== foo) {
|
||||
const x = [arr1];
|
||||
|
||||
let y;
|
||||
y = [];
|
||||
let y = [];
|
||||
|
||||
getVal1 = _temp;
|
||||
|
||||
|
||||
@@ -47,8 +47,7 @@ function Foo(t0) {
|
||||
if ($[0] !== arr1 || $[1] !== arr2 || $[2] !== foo) {
|
||||
const x = [arr1];
|
||||
|
||||
let y;
|
||||
y = [];
|
||||
let y = [];
|
||||
let t2;
|
||||
let t3;
|
||||
if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {useState, useEffect} from 'react';
|
||||
import {invoke, Stringify} from 'shared-runtime';
|
||||
|
||||
function Content() {
|
||||
const [announcement, setAnnouncement] = useState('');
|
||||
const [users, setUsers] = useState([{name: 'John Doe'}, {name: 'Jane Doe'}]);
|
||||
|
||||
// This was originally passed down as an onClick, but React Compiler's test
|
||||
// evaluator doesn't yet support events outside of React
|
||||
useEffect(() => {
|
||||
if (users.length === 2) {
|
||||
let removedUserName = '';
|
||||
setUsers(prevUsers => {
|
||||
const newUsers = [...prevUsers];
|
||||
removedUserName = newUsers.at(-1).name;
|
||||
newUsers.pop();
|
||||
return newUsers;
|
||||
});
|
||||
|
||||
setAnnouncement(`Removed user (${removedUserName})`);
|
||||
}
|
||||
}, [users]);
|
||||
|
||||
return <Stringify users={users} announcement={announcement} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Content,
|
||||
params: [{}],
|
||||
sequentialRenders: [{}, {}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { useState, useEffect } from "react";
|
||||
import { invoke, Stringify } from "shared-runtime";
|
||||
|
||||
function Content() {
|
||||
const $ = _c(8);
|
||||
const [announcement, setAnnouncement] = useState("");
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = [{ name: "John Doe" }, { name: "Jane Doe" }];
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
const [users, setUsers] = useState(t0);
|
||||
let t1;
|
||||
if ($[1] !== users.length) {
|
||||
t1 = () => {
|
||||
if (users.length === 2) {
|
||||
let removedUserName = "";
|
||||
setUsers((prevUsers) => {
|
||||
const newUsers = [...prevUsers];
|
||||
removedUserName = newUsers.at(-1).name;
|
||||
newUsers.pop();
|
||||
return newUsers;
|
||||
});
|
||||
|
||||
setAnnouncement(`Removed user (${removedUserName})`);
|
||||
}
|
||||
};
|
||||
$[1] = users.length;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
let t2;
|
||||
if ($[3] !== users) {
|
||||
t2 = [users];
|
||||
$[3] = users;
|
||||
$[4] = t2;
|
||||
} else {
|
||||
t2 = $[4];
|
||||
}
|
||||
useEffect(t1, t2);
|
||||
let t3;
|
||||
if ($[5] !== announcement || $[6] !== users) {
|
||||
t3 = <Stringify users={users} announcement={announcement} />;
|
||||
$[5] = announcement;
|
||||
$[6] = users;
|
||||
$[7] = t3;
|
||||
} else {
|
||||
t3 = $[7];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Content,
|
||||
params: [{}],
|
||||
sequentialRenders: [{}, {}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"users":[{"name":"John Doe"}],"announcement":"Removed user (Jane Doe)"}</div>
|
||||
<div>{"users":[{"name":"John Doe"}],"announcement":"Removed user (Jane Doe)"}</div>
|
||||
@@ -0,0 +1,31 @@
|
||||
import {useState, useEffect} from 'react';
|
||||
import {invoke, Stringify} from 'shared-runtime';
|
||||
|
||||
function Content() {
|
||||
const [announcement, setAnnouncement] = useState('');
|
||||
const [users, setUsers] = useState([{name: 'John Doe'}, {name: 'Jane Doe'}]);
|
||||
|
||||
// This was originally passed down as an onClick, but React Compiler's test
|
||||
// evaluator doesn't yet support events outside of React
|
||||
useEffect(() => {
|
||||
if (users.length === 2) {
|
||||
let removedUserName = '';
|
||||
setUsers(prevUsers => {
|
||||
const newUsers = [...prevUsers];
|
||||
removedUserName = newUsers.at(-1).name;
|
||||
newUsers.pop();
|
||||
return newUsers;
|
||||
});
|
||||
|
||||
setAnnouncement(`Removed user (${removedUserName})`);
|
||||
}
|
||||
}, [users]);
|
||||
|
||||
return <Stringify users={users} announcement={announcement} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Content,
|
||||
params: [{}],
|
||||
sequentialRenders: [{}, {}],
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Example showing that returned inner function expressions should not be
|
||||
* typed with `freeze` effects.
|
||||
*/
|
||||
function Foo({a, b}) {
|
||||
'use memo';
|
||||
const obj = {};
|
||||
const updaterFactory = () => {
|
||||
/**
|
||||
* This returned function expression *is* a local value. But it might (1)
|
||||
* capture and mutate its context environment and (2) be called during
|
||||
* render.
|
||||
* Typing it with `freeze` effects would be incorrect as it would mean
|
||||
* inferring that calls to updaterFactory()() do not mutate its captured
|
||||
* context.
|
||||
*/
|
||||
return newValue => {
|
||||
obj.value = newValue;
|
||||
obj.a = a;
|
||||
};
|
||||
};
|
||||
|
||||
const updater = updaterFactory();
|
||||
updater(b);
|
||||
return <Stringify cb={obj} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{a: 1, b: 2}],
|
||||
sequentialRenders: [
|
||||
{a: 1, b: 2},
|
||||
{a: 1, b: 3},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Example showing that returned inner function expressions should not be
|
||||
* typed with `freeze` effects.
|
||||
*/
|
||||
function Foo(t0) {
|
||||
"use memo";
|
||||
const $ = _c(3);
|
||||
const { a, b } = t0;
|
||||
let t1;
|
||||
if ($[0] !== a || $[1] !== b) {
|
||||
const obj = {};
|
||||
const updaterFactory = () => (newValue) => {
|
||||
obj.value = newValue;
|
||||
obj.a = a;
|
||||
};
|
||||
|
||||
const updater = updaterFactory();
|
||||
updater(b);
|
||||
t1 = <Stringify cb={obj} shouldInvokeFns={true} />;
|
||||
$[0] = a;
|
||||
$[1] = b;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ a: 1, b: 2 }],
|
||||
sequentialRenders: [
|
||||
{ a: 1, b: 2 },
|
||||
{ a: 1, b: 3 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"cb":{"value":2,"a":1},"shouldInvokeFns":true}</div>
|
||||
<div>{"cb":{"value":3,"a":1},"shouldInvokeFns":true}</div>
|
||||
@@ -0,0 +1,37 @@
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Example showing that returned inner function expressions should not be
|
||||
* typed with `freeze` effects.
|
||||
*/
|
||||
function Foo({a, b}) {
|
||||
'use memo';
|
||||
const obj = {};
|
||||
const updaterFactory = () => {
|
||||
/**
|
||||
* This returned function expression *is* a local value. But it might (1)
|
||||
* capture and mutate its context environment and (2) be called during
|
||||
* render.
|
||||
* Typing it with `freeze` effects would be incorrect as it would mean
|
||||
* inferring that calls to updaterFactory()() do not mutate its captured
|
||||
* context.
|
||||
*/
|
||||
return newValue => {
|
||||
obj.value = newValue;
|
||||
obj.a = a;
|
||||
};
|
||||
};
|
||||
|
||||
const updater = updaterFactory();
|
||||
updater(b);
|
||||
return <Stringify cb={obj} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{a: 1, b: 2}],
|
||||
sequentialRenders: [
|
||||
{a: 1, b: 2},
|
||||
{a: 1, b: 3},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,100 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {makeArray, Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Example showing that returned inner function expressions should not be
|
||||
* typed with `freeze` effects.
|
||||
* Also see repro-returned-inner-fn-mutates-context
|
||||
*/
|
||||
function Foo({b}) {
|
||||
'use memo';
|
||||
|
||||
const fnFactory = () => {
|
||||
/**
|
||||
* This returned function expression *is* a local value. But it might (1)
|
||||
* capture and mutate its context environment and (2) be called during
|
||||
* render.
|
||||
* Typing it with `freeze` effects would be incorrect as it would mean
|
||||
* inferring that calls to updaterFactory()() do not mutate its captured
|
||||
* context.
|
||||
*/
|
||||
return () => {
|
||||
myVar = () => console.log('a');
|
||||
};
|
||||
};
|
||||
let myVar = () => console.log('b');
|
||||
useIdentity();
|
||||
|
||||
const fn = fnFactory();
|
||||
const arr = makeArray(b);
|
||||
fn(arr);
|
||||
return <Stringify cb={myVar} value={arr} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{b: 1}],
|
||||
sequentialRenders: [{b: 1}, {b: 2}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { makeArray, Stringify, useIdentity } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Example showing that returned inner function expressions should not be
|
||||
* typed with `freeze` effects.
|
||||
* Also see repro-returned-inner-fn-mutates-context
|
||||
*/
|
||||
function Foo(t0) {
|
||||
"use memo";
|
||||
const $ = _c(3);
|
||||
const { b } = t0;
|
||||
|
||||
const fnFactory = () => () => {
|
||||
myVar = _temp;
|
||||
};
|
||||
|
||||
let myVar = _temp2;
|
||||
useIdentity();
|
||||
|
||||
const fn = fnFactory();
|
||||
const arr = makeArray(b);
|
||||
fn(arr);
|
||||
let t1;
|
||||
if ($[0] !== arr || $[1] !== myVar) {
|
||||
t1 = <Stringify cb={myVar} value={arr} shouldInvokeFns={true} />;
|
||||
$[0] = arr;
|
||||
$[1] = myVar;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
function _temp2() {
|
||||
return console.log("b");
|
||||
}
|
||||
function _temp() {
|
||||
return console.log("a");
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ b: 1 }],
|
||||
sequentialRenders: [{ b: 1 }, { b: 2 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"cb":{"kind":"Function"},"value":[1],"shouldInvokeFns":true}</div>
|
||||
<div>{"cb":{"kind":"Function"},"value":[2],"shouldInvokeFns":true}</div>
|
||||
logs: ['a','a']
|
||||
@@ -0,0 +1,37 @@
|
||||
import {makeArray, Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Example showing that returned inner function expressions should not be
|
||||
* typed with `freeze` effects.
|
||||
* Also see repro-returned-inner-fn-mutates-context
|
||||
*/
|
||||
function Foo({b}) {
|
||||
'use memo';
|
||||
|
||||
const fnFactory = () => {
|
||||
/**
|
||||
* This returned function expression *is* a local value. But it might (1)
|
||||
* capture and mutate its context environment and (2) be called during
|
||||
* render.
|
||||
* Typing it with `freeze` effects would be incorrect as it would mean
|
||||
* inferring that calls to updaterFactory()() do not mutate its captured
|
||||
* context.
|
||||
*/
|
||||
return () => {
|
||||
myVar = () => console.log('a');
|
||||
};
|
||||
};
|
||||
let myVar = () => console.log('b');
|
||||
useIdentity();
|
||||
|
||||
const fn = fnFactory();
|
||||
const arr = makeArray(b);
|
||||
fn(arr);
|
||||
return <Stringify cb={myVar} value={arr} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{b: 1}],
|
||||
sequentialRenders: [{b: 1}, {b: 2}],
|
||||
};
|
||||
@@ -1,42 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import {useRef} from 'react';
|
||||
import {useSpecialEffect} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* The retry pipeline disables memoization features, which means we need to
|
||||
* provide an alternate implementation of effect dependencies which does not
|
||||
* rely on memoization.
|
||||
*/
|
||||
function useFoo({cond}) {
|
||||
const ref = useRef();
|
||||
const derived = cond ? ref.current : makeObject();
|
||||
useSpecialEffect(() => {
|
||||
log(derived);
|
||||
}, [derived]);
|
||||
return ref;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
11 | const ref = useRef();
|
||||
12 | const derived = cond ? ref.current : makeObject();
|
||||
> 13 | useSpecialEffect(() => {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
> 14 | log(derived);
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
> 15 | }, [derived]);
|
||||
| ^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics.. (Bailout reason: Invariant: Expected function expression scope to exist (13:15)) (13:15)
|
||||
16 | return ref;
|
||||
17 | }
|
||||
18 |
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import {useRef} from 'react';
|
||||
import {useSpecialEffect} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* The retry pipeline disables memoization features, which means we need to
|
||||
* provide an alternate implementation of effect dependencies which does not
|
||||
* rely on memoization.
|
||||
*/
|
||||
function useFoo({cond}) {
|
||||
const ref = useRef();
|
||||
const derived = cond ? ref.current : makeObject();
|
||||
useSpecialEffect(() => {
|
||||
log(derived);
|
||||
}, [derived]);
|
||||
return ref;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import { useRef } from "react";
|
||||
import { useSpecialEffect } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* The retry pipeline disables memoization features, which means we need to
|
||||
* provide an alternate implementation of effect dependencies which does not
|
||||
* rely on memoization.
|
||||
*/
|
||||
function useFoo(t0) {
|
||||
const { cond } = t0;
|
||||
const ref = useRef();
|
||||
const derived = cond ? ref.current : makeObject();
|
||||
useSpecialEffect(
|
||||
() => {
|
||||
log(derived);
|
||||
},
|
||||
[derived],
|
||||
[derived],
|
||||
);
|
||||
return ref;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -49,7 +49,7 @@ function Component(props) {
|
||||
} else {
|
||||
t2 = $[4];
|
||||
}
|
||||
useEffect(t2, [t1, props]);
|
||||
useEffect(t2, [props]);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableUseTypeAnnotations
|
||||
function Component(props: {id: number}) {
|
||||
const x = makeArray(props.id) satisfies number[];
|
||||
const y = x.at(0);
|
||||
return y;
|
||||
}
|
||||
|
||||
function makeArray<T>(x: T): Array<T> {
|
||||
return [x];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{id: 42}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableUseTypeAnnotations
|
||||
function Component(props) {
|
||||
const $ = _c(4);
|
||||
let t0;
|
||||
if ($[0] !== props.id) {
|
||||
t0 = makeArray(props.id);
|
||||
$[0] = props.id;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
const x = t0 satisfies number[];
|
||||
let t1;
|
||||
if ($[2] !== x) {
|
||||
t1 = x.at(0);
|
||||
$[2] = x;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
const y = t1;
|
||||
return y;
|
||||
}
|
||||
|
||||
function makeArray(x) {
|
||||
const $ = _c(2);
|
||||
let t0;
|
||||
if ($[0] !== x) {
|
||||
t0 = [x];
|
||||
$[0] = x;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ id: 42 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) 42
|
||||
@@ -0,0 +1,15 @@
|
||||
// @enableUseTypeAnnotations
|
||||
function Component(props: {id: number}) {
|
||||
const x = makeArray(props.id) satisfies number[];
|
||||
const y = x.at(0);
|
||||
return y;
|
||||
}
|
||||
|
||||
function makeArray<T>(x: T): Array<T> {
|
||||
return [x];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{id: 42}],
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableUseTypeAnnotations
|
||||
import {identity} from 'shared-runtime';
|
||||
|
||||
function Component(props: {id: number}) {
|
||||
const x = identity(props.id);
|
||||
const y = x satisfies number;
|
||||
return y;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{id: 42}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @enableUseTypeAnnotations
|
||||
import { identity } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const x = identity(props.id);
|
||||
const y = x satisfies number;
|
||||
return y;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ id: 42 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) 42
|
||||
@@ -0,0 +1,13 @@
|
||||
// @enableUseTypeAnnotations
|
||||
import {identity} from 'shared-runtime';
|
||||
|
||||
function Component(props: {id: number}) {
|
||||
const x = identity(props.id);
|
||||
const y = x satisfies number;
|
||||
return y;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{id: 42}],
|
||||
};
|
||||
@@ -0,0 +1,122 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
/**
|
||||
* Example of a function expression whose return value shouldn't have
|
||||
* a "freeze" effect on all operands.
|
||||
*
|
||||
* This is because the function expression is passed to `useEffect` and
|
||||
* thus is not a render function. `cleanedUp` is also created within
|
||||
* the effect and is not a render variable.
|
||||
*/
|
||||
function Component({prop}) {
|
||||
const [cleanupCount, setCleanupCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
let cleanedUp = false;
|
||||
setTimeout(() => {
|
||||
if (!cleanedUp) {
|
||||
cleanedUp = true;
|
||||
setCleanupCount(c => c + 1);
|
||||
}
|
||||
}, 0);
|
||||
// This return value should not have freeze effects
|
||||
// on its operands
|
||||
return () => {
|
||||
if (!cleanedUp) {
|
||||
cleanedUp = true;
|
||||
setCleanupCount(c => c + 1);
|
||||
}
|
||||
};
|
||||
}, [prop]);
|
||||
return <div>{cleanupCount}</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{prop: 5}],
|
||||
sequentialRenders: [{prop: 5}, {prop: 5}, {prop: 6}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
/**
|
||||
* Example of a function expression whose return value shouldn't have
|
||||
* a "freeze" effect on all operands.
|
||||
*
|
||||
* This is because the function expression is passed to `useEffect` and
|
||||
* thus is not a render function. `cleanedUp` is also created within
|
||||
* the effect and is not a render variable.
|
||||
*/
|
||||
function Component(t0) {
|
||||
const $ = _c(5);
|
||||
const { prop } = t0;
|
||||
const [cleanupCount, setCleanupCount] = useState(0);
|
||||
let t1;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = () => {
|
||||
let cleanedUp = false;
|
||||
setTimeout(() => {
|
||||
if (!cleanedUp) {
|
||||
cleanedUp = true;
|
||||
setCleanupCount(_temp);
|
||||
}
|
||||
}, 0);
|
||||
return () => {
|
||||
if (!cleanedUp) {
|
||||
cleanedUp = true;
|
||||
setCleanupCount(_temp2);
|
||||
}
|
||||
};
|
||||
};
|
||||
$[0] = t1;
|
||||
} else {
|
||||
t1 = $[0];
|
||||
}
|
||||
let t2;
|
||||
if ($[1] !== prop) {
|
||||
t2 = [prop];
|
||||
$[1] = prop;
|
||||
$[2] = t2;
|
||||
} else {
|
||||
t2 = $[2];
|
||||
}
|
||||
useEffect(t1, t2);
|
||||
let t3;
|
||||
if ($[3] !== cleanupCount) {
|
||||
t3 = <div>{cleanupCount}</div>;
|
||||
$[3] = cleanupCount;
|
||||
$[4] = t3;
|
||||
} else {
|
||||
t3 = $[4];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
function _temp2(c_0) {
|
||||
return c_0 + 1;
|
||||
}
|
||||
function _temp(c) {
|
||||
return c + 1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ prop: 5 }],
|
||||
sequentialRenders: [{ prop: 5 }, { prop: 5 }, { prop: 6 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>0</div>
|
||||
<div>0</div>
|
||||
<div>1</div>
|
||||
@@ -0,0 +1,38 @@
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
/**
|
||||
* Example of a function expression whose return value shouldn't have
|
||||
* a "freeze" effect on all operands.
|
||||
*
|
||||
* This is because the function expression is passed to `useEffect` and
|
||||
* thus is not a render function. `cleanedUp` is also created within
|
||||
* the effect and is not a render variable.
|
||||
*/
|
||||
function Component({prop}) {
|
||||
const [cleanupCount, setCleanupCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
let cleanedUp = false;
|
||||
setTimeout(() => {
|
||||
if (!cleanedUp) {
|
||||
cleanedUp = true;
|
||||
setCleanupCount(c => c + 1);
|
||||
}
|
||||
}, 0);
|
||||
// This return value should not have freeze effects
|
||||
// on its operands
|
||||
return () => {
|
||||
if (!cleanedUp) {
|
||||
cleanedUp = true;
|
||||
setCleanupCount(c => c + 1);
|
||||
}
|
||||
};
|
||||
}, [prop]);
|
||||
return <div>{cleanupCount}</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{prop: 5}],
|
||||
sequentialRenders: [{prop: 5}, {prop: 5}, {prop: 6}],
|
||||
};
|
||||
@@ -79,8 +79,7 @@ function Component(props) {
|
||||
|
||||
function Inner(props) {
|
||||
const $ = _c(7);
|
||||
let input;
|
||||
input = null;
|
||||
let input = null;
|
||||
if (props.cond) {
|
||||
input = use(FooContext);
|
||||
}
|
||||
|
||||
@@ -1,3 +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.
|
||||
*/
|
||||
|
||||
import {defineConfig} from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user