Compare commits

..

25 Commits

Author SHA1 Message Date
Mofei Zhang
daab9e33b2 [compiler][rfc] enableTreatFunctionDepsAsConditional by default
See https://github.com/facebook/react/issues/31551 for context

We've rolled out internally with `enableTreatFunctionDepsAsConditional: false` and encountered no issues, but this is technically an unsound optimization as both flow and typescript have sources of unsoundness:
- typing array accesses / other unknown keys as `TValue | undefined`  (typescript's noUncheckedIndexedAccess)
- explicit and inferred `any`
- unsound typecasts

Note that removing this optimization results in less granular dependencies for ~3% of files on a large Meta project ([link](https://www.internalfb.com/phabricator/paste/view/P1681742214)).
2024-11-19 10:10:39 -05:00
mofeiZ
e33b13795d [compiler] repro for type inference + control flow bug (#31570)
Repro for bug in our type inference system.

We currently propagate inferred types through control flow / potential
type guards. Note that this is inconsistent with both
[Flow](https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkO4AX34kA0SRWxgABAAxYjsgC87OAAB0oOzReythU2Mh2YKQNyILLeMKxeymrgZNLhCIbsL6QBuYVs7DsgBCVD5AuVYolUClMpAZsoiqtorVGvZWpuSqg9OFMAeyjg0HZdTmW3lAAp5NKAPJoABWcwkAEppWZGLg4O12fJ2bSuTyhSKxSwJEJKCKAOQ2tiVvMi3MAMkbOasNb5vP5svlsoNPuFfoD8JFGQqUel8vZAB9TVReCHoHa0MRnlBUwWIJbi6K4DB2RHbGxk1uVSrd-uAIShsDh4hR5PHoun5-siS1SgQADuHuw34AotQECUBGsqysmfYvuyvrbqepblg2EFitBKpwRWOZ9vSuQgA0JgkEGUBJBk9gmCA9JAA)
and
[Typescript](https://www.typescriptlang.org/play/?#code/C4TwDgpgBAYg9nKBeKBvAUFLUDWBLAOwBMAuKAInjnIBpNsA3AQwBsBXCMgtgWwCMIAJ3QBfANzpQkKACEmg5GnpZ8xMuTmDayqM3aco3fkLoj0AMzYEAxsDxwCUawAsI1nFQAUADzJw+AFZuwACUZEwAzhFCwBFQ3lB4cVRK2InmUJ4AhJ4A5KpEuYmOCQBkpfEAdAXISCiUCOQhIalp2MDOgnAA7oYQvQCigl2CnuRWEN6QthBETTpmZhZWtvaOPEyEPmQpAD6y8jRODqRQfAgsEEwEYbAIrVh4GZ7WJy0Ybdgubh4IPiEST5YQQQYBsQQlQHYMxpEFgiHxCQiIA)
2024-11-18 11:54:11 -05:00
Jack Pope
4beb1fd8ba [compiler] Support enableRefAsProp in jsx transform (#31558)
Since `enableRefAsProp` shipped everywhere, the ReactElement
implementation on prod puts refs on both `element.ref` and
`element.props.ref`. Here we let the `ref` case fall through so its now
available on props, matching the JSX runtime.
2024-11-18 10:51:16 -05:00
lauren
047d95e85f [crud] Basic implementation (#31523)
This PR introduces a new experimental hook `useResourceEffect`, which is
something that we're doing some very early initial tests on.

This may likely not pan out and will be removed or modified if so.
Please do not rely on it as it will break.
2024-11-18 10:16:37 -05:00
Sebastian Markbåge
92c0f5f85f Track separate SuspendedOnAction flag by rethrowing a separate SuspenseActionException sentinel (#31554)
This lets us track separately if something was suspended on an Action
using useActionState rather than suspended on Data.

This approach feels quite bloated and it seems like we'd eventually
might want to read more information about the Promise that suspended and
the context it suspended in. As a more general reason for suspending.

The way useActionState works in combination with the prewarming is quite
unfortunate because 1) it renders blocking to update the isPending flag
whether you use it or not 2) it prewarms and suspends the useActionState
3) then it does another third render to get back into the useActionState
position again.
2024-11-15 17:52:24 -05:00
lauren
053b3cb050 [crud] Rename Effect type (#31557)
Adds a new `Effect` type which for now just points to the `SimpleEffect`
type, in prepartion for later in the stack where we add more.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31557).
* #31523
* __->__ #31557
* #31556
* #31555
2024-11-15 17:50:50 -05:00
lauren
7dd6b9e68a [crud] Add enableUseResourceEffectHook flag (#31556)
Adds a new feature flag for `enableUseResourceEffectHook`.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31556).
* #31523
* #31557
* __->__ #31556
* #31555
2024-11-15 17:49:42 -05:00
lauren
d8afd1c82e [crud] Scaffold initial types (#31555)
Scaffolds the initial `useResourceEffect` dispatcher type. This will
eventually be folded into `useEffect` et al as an overload.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31555).
* #31523
* #31557
* #31556
* __->__ #31555
2024-11-15 17:49:31 -05:00
mofeiZ
0480cdb58c [compiler][be] Clean up nested function context in DCE (#31202)
Now that we rely on function context exclusively, let's clean up
`HIRFunction.context` after DCE. This PR is in preparation of #31204,
which would otherwise have unnecessary declarations (of context values
that become entirely DCE'd)

'
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31202).
* __->__ #31202
* #31203
* #31201
* #31200
* #31521
2024-11-15 13:06:39 -05:00
mofeiZ
0f3c62b466 [compiler][be] Patch test fixtures for evaluator (#31203)
Add more `FIXTURE_ENTRYPOINT`s

'
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31203).
* #31202
* __->__ #31203
* #31201
* #31200
* #31521
2024-11-15 13:06:29 -05:00
mofeiZ
858633f900 [compiler] Lower JSXMemberExpression with LoadLocal (#31201)
`JSXMemberExpression` is currently the only instruction (that I know of)
that directly references identifier lvalues without a corresponding
`LoadLocal`.

This has some side effects:
- deadcode elimination and constant propagation now reach
JSXMemberExpressions
- we can delete `LoweredFunction.dependencies` without dangling
references (previously, the only reference to JSXMemberExpression
objects in HIR was in function dependencies)
- JSXMemberExpression now is consistent with all other instructions
(e.g. has a rvalue-producing LoadLocal)

'
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31201).
* #31202
* #31203
* __->__ #31201
* #31200
* #31521
2024-11-15 13:06:19 -05:00
mofeiZ
c09402aa2f [compiler] Stop using function dependencies in propagateScopeDeps (#31200)
Recursively visit inner function instructions to extract dependencies
instead of using `LoweredFunction.dependencies` directly.

This is currently gated by enableFunctionDependencyRewrite, which needs
to be removed before we delete `LoweredFunction.dependencies` altogether
(#31204).

Some nice side effects
- optional-chaining deps for inner functions
- full DCE and outlining for inner functions (see #31202)
- fewer extraneous instructions (see #31204)

-
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31200).
* #31202
* #31203
* #31201
* __->__ #31200
* #31521
2024-11-15 13:06:05 -05:00
mofeiZ
4972718c26 [compiler] Fix: ref.current now correctly reactive (#31521)
We were previously filtering out `ref.current` dependencies in
propagateScopeDependencies:checkValidDependency`. This is incorrect.

Instead, we now always take a dependency on ref values (the outer box)
as they may be reactive. Pruning is done in
pruneNonReactiveDependencies.

This PR includes a small patch to `collectReactiveIdentifier`. Prior to
this, we conservatively assumed that pruned scopes always produced
reactive declarations. This assumption fixed a bug with non-reactivity,
but some of these declarations are `useRef` calls. Now we have special
handling for this case
```js
// This often produces a pruned scope
React.useRef(1);
```
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31521).
* #31202
* #31203
* #31201
* #31200
* __->__ #31521
2024-11-15 13:05:55 -05:00
Sebastian Markbåge
3720870a97 Log Render Phases that Never Committed (#31548)
This includes:

- `Interrupted Render`: Interrupted Renders (setState or ping at higher
priority)
- `Prewarm`: Suspended Renders outside a Suspense boundary
(RootSuspendedWithDelay/RootSuspendedAtTheShell)
- `Errored Render`: Render that errored somewhere in the tree (Fatal or
Not) (which may or may not be retried and then complete)
- `Teared Render`: Due to useSyncExternalStore not matching (which will
do another sync attempt)

Suspended Commit:

<img width="893" alt="Screenshot 2024-11-14 at 11 47 40 PM"
src="https://github.com/user-attachments/assets/b25a6a8b-a5e9-4d66-b325-57aef4bf9dad">

Errored with a second recovery attempt that also errors:

<img width="976" alt="Screenshot 2024-11-15 at 12 09 06 AM"
src="https://github.com/user-attachments/assets/9ce52cbb-b587-4f1e-8b67-e51d9073ae5b">
2024-11-15 12:13:24 -05:00
Andrew Clark
e1ef8c9515 Don't dist-tag canaries with rc (#31546) 2024-11-15 10:05:36 +01:00
Sebastian Markbåge
8a41d6ceab Unify RootDidNotComplete and RootSuspendedWithDelay exit path (#31547)
Also rename RootDidNotComplete to RootSuspendedAtTheShell since it
specifically means something suspended in the shell during hydration.
2024-11-14 23:51:51 -05:00
Zack Tanner
63cde684f5 (chore): copy fix in <style> precedence error (#31524)
## Summary

This fixes a typo in the error that gets reported when Float errors
while hoisting a style tag that does not contain both `precedence` and
`href`. There was a typo in _conflict_ and the last part of the sentence
doesn't make sense. I assume it wasn't needed since the message already
suggests moving the style tag to the head manually.
2024-11-14 14:03:59 -08:00
Sebastian Markbåge
b01722d585 Format event with "warning" yellow and prefix with "Event: " (#31536)
It's useful to quickly see where new events are kicking off new
rendering. This uses the new "warning" color (yellow) to do that. This
is to help distinguish it from the purple (secondary color) which is
used for the commit phase which is more of a follow up and it's often
that you have several rerenders within one event which makes it hard to
tell a part where it starts and event otherwise.

For the span marking between previous render within the same event and
the next setState, I use secondary-light (light purple) since it's kind
of still part of the same sequence at that point. It's usually a spawned
render (e.g. setState in useEffect or microtask) but it can also be
sequential flushSync.

I was bothered by that the event name is the only thing that's lower
case so I prefixed it with `Event: ` like the JS traces are.

<img width="1499" alt="Screenshot 2024-11-13 at 7 15 45 PM"
src="https://github.com/user-attachments/assets/0c81c810-6b5d-4fc7-9bc0-d15b53844ade">

It might be a little confusing why our track starts earlier than the JS
one below in the "Main Thread" flamegraph which looks the same. That's
because ours is the start of the event time which is when the click
happens where as the Main Thread one is when the JS event loop gets
around to processing the event.
2024-11-14 16:44:29 -05:00
Sebastian Markbåge
c13986da78 Fix Overlapping "message" Bug in Performance Track (#31528)
When you schedule a microtask from render or effect and then call
setState (or ping) from there, the "event" is the event that React
scheduled (which will be a postMessage). The event time of this new
render will be before the last render finished.

We usually clamp these but in this scenario the update doesn't happen
while a render is happening. Causing overlapping events.

Before:

<img width="1229" alt="Screenshot 2024-11-12 at 11 01 30 PM"
src="https://github.com/user-attachments/assets/9652cf3b-b358-453c-b295-1239cbb15952">

Therefore when we finalize a render we need to store the end of the last
render so when we a new update comes in later with an event time earlier
than that, we know to clamp it.

There's also a special case here where when we enter the
`RootDidNotComplete` or `RootSuspendedWithDelay` case we neither leave
the root as in progress nor commit it. Those needs to finalize too.
Really this should be modeled as a suspended track that we haven't added
yet. That's the gap between "Blocked" and "message" below.

After:

<img width="1471" alt="Screenshot 2024-11-13 at 12 31 34 AM"
src="https://github.com/user-attachments/assets/b24f994e-9055-4b10-ad29-ad9b36302ffc">

I also fixed an issue where we may log the same event name multiple
times if we're rendering more than once in the same event. In this case
I just leave a blank trace between the last commit and the next update.

I also adding ignoring of the "message" event at all in these cases when
the event is from React's scheduling itself.
2024-11-14 16:35:08 -05:00
Sebastian Markbåge
4686872159 Log passive commit phase when it wasn't delayed (#31526)
Fixes a bug.

We're supposed to not log "Waiting for Paint" if the passive effect
phase was forced since we weren't really waiting until the paint.
Instead we just log an empty string when we force it to still ensure
continuity.

We should always log the passive phase. This check was in the wrong
place.
2024-11-14 16:30:05 -05:00
lauren
5d89471ca6 Export __COMPILER_RUNTIME in stable (#31540)
In order to make use of the compiler in stable releases (eg React 19 RC,
canary), we need to export the compiler runtime in the stable channel as
well.
2024-11-14 15:46:35 -05:00
Niklas Mollenhauer
3c15d219aa [compiler] Disable emit of .tsbuildinfo (#31459)
## Summary
`@rollup/plugin-typescript` emits a warning while building, hinting that
`outputToFilesystem` defaults to true.

Although "noEmit" is set to `true` for the tsconfig, rollup writes a
`dist/.tsbuildinfo`. That file is then also shipped inside the npm
module and doesn't offer any benefit for library consumers. Setting this
option to false results in the file not being written and thus omitted
from the npm module.

## How did you test this change?
`dist/.tsbuildinfo` is not emitted any more.
2024-11-14 15:43:36 -05:00
Sebastian Markbåge
3644f0bd21 Use completedRenderEndTime as the start of the commit phase if it's an immediate commit (#31527)
We don't log a phase like "Throttled" or "Suspended" for this case so it
can leave a tiny gap otherwise.

This ensures it connects without a seam.

<img width="1059" alt="Screenshot 2024-11-12 at 9 34 17 PM"
src="https://github.com/user-attachments/assets/7ed4a3c7-b508-4fc1-b956-44c4e31faa4d">
2024-11-14 13:05:38 -05:00
Sebastian Markbåge
8657869999 Separate Tracks for Components and Phases (#31525)
Previously we were showing Components inside each lane track but that
meant that as soon as you expanded a lane you couldn't see the other
line so you couldn't get an overview over how well things were
scheduled.

This instead moves all the Components into a single top-level track and
renames the previous one to a "Scheduler" track group.

<img width="1352" alt="Screenshot 2024-11-12 at 8 26 05 PM"
src="https://github.com/user-attachments/assets/590bc6d3-3540-4ee4-b474-5d733b8d8d8d">

That way you can get an overview over what React is working on first and
then right below see which Component is being worked on.

Ideally the "Scheduler" track would be always expanded since each Track
is always just a single row. Now you have to expand each lane to see the
labels but then you're wasting a lot of vertical real estate. There's
currently no option to create this with the Chrome performance.measure
extensions.

<img width="1277" alt="Screenshot 2024-11-12 at 8 26 16 PM"
src="https://github.com/user-attachments/assets/4fc39e35-10ec-4452-ad32-c1c2e6b5e1a8">
2024-11-14 13:05:20 -05:00
lauren
b15135b9f5 [ez] Update useMemoCache return type (#31539)
Use `mixed` instead of `any`
2024-11-14 12:10:51 -05:00
166 changed files with 3495 additions and 2182 deletions

View File

@@ -3,10 +3,6 @@ name: (Runtime) Build and Test
on:
push:
branches: [main]
tags:
# To get CI for backport releases.
# This will duplicate CI for releases from main which is acceptable
- "v*"
pull_request:
paths-ignore:
- compiler/**

View File

@@ -27,7 +27,7 @@ jobs:
# because this used to be called the "next" channel and some
# downstream consumers might still expect that tag. We can remove this
# after some time has elapsed and the change has been communicated.
dist_tag: canary,next,rc
dist_tag: canary,next
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -15,7 +15,7 @@ jobs:
with:
commit_sha: ${{ github.sha }}
release_channel: stable
dist_tag: canary,next,rc
dist_tag: canary,next
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -7,18 +7,18 @@
//
// The @latest channel uses the version as-is, e.g.:
//
// 19.0.5
// 19.0.0
//
// The @canary channel appends additional information, with the scheme
// <version>-<label>-<commit_sha>, e.g.:
//
// 19.0.5-canary-a1c2d3e4
// 19.0.0-canary-a1c2d3e4
//
// The @experimental channel doesn't include a version, only a date and a sha, e.g.:
//
// 0.0.0-experimental-241c4467e-20200129
const ReactVersion = '19.0.5';
const ReactVersion = '19.0.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

View File

@@ -5,7 +5,8 @@
"main": "dist/index.js",
"license": "MIT",
"files": [
"dist"
"dist",
"!*.tsbuildinfo"
],
"scripts": {
"build": "rimraf dist && rollup --config --bundleConfigAsCjs",

View File

@@ -28,6 +28,7 @@ const DEV_ROLLUP_CONFIG = {
plugins: [
typescript({
tsconfig: './tsconfig.json',
outputToFilesystem: true,
compilerOptions: {
noEmit: true,
},

View File

@@ -3186,7 +3186,13 @@ function lowerJsxMemberExpression(
loc: object.node.loc ?? null,
suggestions: null,
});
objectPlace = lowerIdentifier(builder, object);
const kind = getLoadKind(builder, object);
objectPlace = lowerValueToTemporary(builder, {
kind: kind,
place: lowerIdentifier(builder, object),
loc: exprPath.node.loc ?? GeneratedSource,
});
}
const property = exprPath.get('property').node.name;
return lowerValueToTemporary(builder, {

View File

@@ -231,6 +231,8 @@ const EnvironmentConfigSchema = z.object({
*/
enableUseTypeAnnotations: z.boolean().default(false),
enableFunctionDependencyRewrite: z.boolean().default(true),
/**
* Enables inlining ReactElement object literals in place of JSX
* An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime
@@ -455,9 +457,10 @@ const EnvironmentConfigSchema = z.object({
throwUnknownException__testonly: z.boolean().default(false),
/**
* Enables deps of a function epxression to be treated as conditional. This
* makes sure we don't load a dep when it's a property (to check if it has
* changed) and instead check the receiver.
* Enables deps of a function expression to be treated as conditional. This
* makes sure we don't hoist property loads from function expressions when we
* don't know that the property load source object is safe to access in the
* outer context
*
* This makes sure we don't end up throwing when the reciver is null. Consider
* this code:
@@ -473,8 +476,14 @@ const EnvironmentConfigSchema = z.object({
*
* This does cause the memoization to now be coarse grained, which is
* non-ideal.
*
* This is safe to toggle off for a codebase that has no sources of
* unsoundness. This includes:
* - typing array accesses / other unknown keys as TValue | undefined
* (typescript's noUncheckedIndexedAccess)
* - enabling and monitoring warnings for explicit and inferred `any`
*/
enableTreatFunctionDepsAsConditional: z.boolean().default(false),
enableTreatFunctionDepsAsConditional: z.boolean().default(true),
/**
* When true, always act as though the dependencies of a memoized value

View File

@@ -440,14 +440,6 @@ class Context {
// Checks if identifier is a valid dependency in the current scope
#checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean {
// ref.current access is not a valid dep
if (
isUseRefType(maybeDependency.identifier) &&
maybeDependency.path.at(0)?.property === 'current'
) {
return false;
}
// ref value is not a valid dep
if (isRefValueType(maybeDependency.identifier)) {
return false;
@@ -549,6 +541,16 @@ class Context {
});
}
// ref.current access is not a valid dep
if (
isUseRefType(maybeDependency.identifier) &&
maybeDependency.path.at(0)?.property === 'current'
) {
maybeDependency = {
identifier: maybeDependency.identifier,
path: [],
};
}
if (this.#checkValidDependency(maybeDependency)) {
this.#dependencies.value!.push(maybeDependency);
}
@@ -661,35 +663,54 @@ function collectDependencies(
const scopeTraversal = new ScopeBlockTraversal();
for (const [blockId, block] of fn.body.blocks) {
scopeTraversal.recordScopes(block);
const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId);
if (scopeBlockInfo?.kind === 'begin') {
context.enterScope(scopeBlockInfo.scope);
} else if (scopeBlockInfo?.kind === 'end') {
context.exitScope(scopeBlockInfo.scope, scopeBlockInfo?.pruned);
}
const handleFunction = (fn: HIRFunction): void => {
for (const [blockId, block] of fn.body.blocks) {
scopeTraversal.recordScopes(block);
const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId);
if (scopeBlockInfo?.kind === 'begin') {
context.enterScope(scopeBlockInfo.scope);
} else if (scopeBlockInfo?.kind === 'end') {
context.exitScope(scopeBlockInfo.scope, scopeBlockInfo.pruned);
}
// 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 (
fn.env.config.enableFunctionDependencyRewrite &&
(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 there
*/
const wasInInnerFn = context.inInnerFn;
context.inInnerFn = true;
handleFunction(instr.value.loweredFunc.func);
context.inInnerFn = wasInInnerFn;
} else if (!processedInstrsInOptional.has(instr)) {
handleInstruction(instr, context);
}
}
// 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);
if (!processedInstrsInOptional.has(block.terminal)) {
for (const place of eachTerminalOperand(block.terminal)) {
context.visitOperand(place);
}
}
}
for (const instr of block.instructions) {
if (!processedInstrsInOptional.has(instr)) {
handleInstruction(instr, context);
}
}
};
if (!processedInstrsInOptional.has(block.terminal)) {
for (const place of eachTerminalOperand(block.terminal)) {
context.visitOperand(place);
}
}
}
handleFunction(fn);
return context.deps;
}

View File

@@ -58,6 +58,14 @@ export function deadCodeElimination(fn: HIRFunction): void {
}
}
}
/**
* Constant propagation and DCE may have deleted or rewritten instructions
* that reference context variables.
*/
retainWhere(fn.context, contextVar =>
state.isIdOrNameUsed(contextVar.identifier),
);
}
class State {

View File

@@ -559,28 +559,45 @@ function createPropsProperties(
propAttributes.forEach(prop => {
switch (prop.kind) {
case 'JsxAttribute': {
if (prop.name === 'ref') {
refProperty = {
kind: 'ObjectProperty',
key: {name: 'ref', kind: 'string'},
type: 'property',
place: {...prop.place},
};
} else if (prop.name === 'key') {
keyProperty = {
kind: 'ObjectProperty',
key: {name: 'key', kind: 'string'},
type: 'property',
place: {...prop.place},
};
} else {
const attributeProperty: ObjectProperty = {
kind: 'ObjectProperty',
key: {name: prop.name, kind: 'string'},
type: 'property',
place: {...prop.place},
};
props.push(attributeProperty);
switch (prop.name) {
case 'key': {
keyProperty = {
kind: 'ObjectProperty',
key: {name: 'key', kind: 'string'},
type: 'property',
place: {...prop.place},
};
break;
}
case 'ref': {
/**
* In the current JSX implementation, ref is both
* a property on the element and a property on props.
*/
refProperty = {
kind: 'ObjectProperty',
key: {name: 'ref', kind: 'string'},
type: 'property',
place: {...prop.place},
};
const refPropProperty: ObjectProperty = {
kind: 'ObjectProperty',
key: {name: 'ref', kind: 'string'},
type: 'property',
place: {...prop.place},
};
props.push(refPropProperty);
break;
}
default: {
const attributeProperty: ObjectProperty = {
kind: 'ObjectProperty',
key: {name: prop.name, kind: 'string'},
type: 'property',
place: {...prop.place},
};
props.push(attributeProperty);
}
}
break;
}

View File

@@ -12,6 +12,8 @@ import {
PrunedReactiveScopeBlock,
ReactiveFunction,
isPrimitiveType,
isUseRefType,
Identifier,
} from '../HIR/HIR';
import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
@@ -50,13 +52,21 @@ class Visitor extends ReactiveFunctionVisitor<Set<IdentifierId>> {
this.traversePrunedScope(scopeBlock, state);
for (const [id, decl] of scopeBlock.scope.declarations) {
if (!isPrimitiveType(decl.identifier)) {
if (
!isPrimitiveType(decl.identifier) &&
!isStableRefType(decl.identifier, state)
) {
state.add(id);
}
}
}
}
function isStableRefType(
identifier: Identifier,
reactiveIdentifiers: Set<IdentifierId>,
): boolean {
return isUseRefType(identifier) && !reactiveIdentifiers.has(identifier.id);
}
/*
* Computes a set of identifiers which are reactive, using the analysis previously performed
* in `InferReactivePlaces`.

View File

@@ -28,7 +28,7 @@ function Component() {
t0 = () => {
"worklet";
setCount((count_0) => count_0 + 1);
setCount(_temp);
};
$[0] = t0;
} else {
@@ -45,6 +45,9 @@ function Component() {
}
return t1;
}
function _temp(count_0) {
return count_0 + 1;
}
```

View File

@@ -75,9 +75,9 @@ function useFoo(t0) {
arrayPush(x, y);
const y_alias = y;
let t2;
if ($[3] !== y_alias.value) {
if ($[3] !== y_alias) {
t2 = () => y_alias.value;
$[3] = y_alias.value;
$[3] = y_alias;
$[4] = t2;
} else {
t2 = $[4];

View File

@@ -56,9 +56,9 @@ function useFoo(t0) {
setPropertyByKey(obj, "arr", arr);
const obj_alias = obj;
let t2;
if ($[2] !== obj_alias.arr.length) {
if ($[2] !== obj_alias) {
t2 = () => obj_alias.arr.length;
$[2] = obj_alias.arr.length;
$[2] = obj_alias;
$[3] = t2;
} else {
t2 = $[3];

View File

@@ -1,89 +0,0 @@
## Input
```javascript
import {useRef} from 'react';
import {Stringify} from 'shared-runtime';
/**
* Bug: we're currently filtering out `ref.current` dependencies in
* `propagateScopeDependencies:checkValidDependency`. This is incorrect.
* Instead, we should always take a dependency on ref values (the outer box) as
* they may be reactive. Pruning should be done in
* `pruneNonReactiveDependencies`
*
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok)
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
* <div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>
* Forget:
* (kind: ok)
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
*/
function Component({cond}) {
const ref1 = useRef(1);
const ref2 = useRef(2);
const ref = cond ? ref1 : ref2;
const cb = () => ref.current;
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{cond: true}],
sequentialRenders: [{cond: true}, {cond: false}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useRef } from "react";
import { Stringify } from "shared-runtime";
/**
* Bug: we're currently filtering out `ref.current` dependencies in
* `propagateScopeDependencies:checkValidDependency`. This is incorrect.
* Instead, we should always take a dependency on ref values (the outer box) as
* they may be reactive. Pruning should be done in
* `pruneNonReactiveDependencies`
*
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok)
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
* <div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>
* Forget:
* (kind: ok)
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
*/
function Component(t0) {
const $ = _c(1);
const { cond } = t0;
const ref1 = useRef(1);
const ref2 = useRef(2);
const ref = cond ? ref1 : ref2;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const cb = () => ref.current;
t1 = <Stringify cb={cb} shouldInvokeFns={true} />;
$[0] = t1;
} else {
t1 = $[0];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ cond: true }],
sequentialRenders: [{ cond: true }, { cond: false }],
};
```

View File

@@ -1,33 +0,0 @@
import {useRef} from 'react';
import {Stringify} from 'shared-runtime';
/**
* Bug: we're currently filtering out `ref.current` dependencies in
* `propagateScopeDependencies:checkValidDependency`. This is incorrect.
* Instead, we should always take a dependency on ref values (the outer box) as
* they may be reactive. Pruning should be done in
* `pruneNonReactiveDependencies`
*
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok)
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
* <div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>
* Forget:
* (kind: ok)
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
*/
function Component({cond}) {
const ref1 = useRef(1);
const ref2 = useRef(2);
const ref = cond ? ref1 : ref2;
const cb = () => ref.current;
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{cond: true}],
sequentialRenders: [{cond: true}, {cond: false}],
};

View File

@@ -0,0 +1,114 @@
## Input
```javascript
import {arrayPush, CONST_NUMBER0, mutate} from 'shared-runtime';
/**
* Repro for bug in our type inference system. We currently propagate inferred
* types through control flow / potential type guards. Note that this is
* inconsistent with both Flow and Typescript.
* https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkO4AX34kA0SRWxgABAAxYjsgC87OAAB0oOzReythU2Mh2YKQNyILLeMKxeymrgZNLhCIbsL6QBuYVs7DsgBCVD5AuVYolUClMpAZsoiqtorVGvZWpuSqg9OFMAeyjg0HZdTmW3lAAp5NKAPJoABWcwkAEppWZGLg4O12fJ2bSuTyhSKxSwJEJKCKAOQ2tiVvMi3MAMkbOasNb5vP5svlsoNPuFfoD8JFGQqUel8vZAB9TVReCHoHa0MRnlBUwWIJbi6K4DB2RHbGxk1uVSrd-uAIShsDh4hR5PHoun5-siS1SgQADuHuw34AotQECUBGsqysmfYvuyvrbqepblg2EFitBKpwRWOZ9vSuQgA0JgkEGUBJBk9gmCA9JAA
* https://www.typescriptlang.org/play/?#code/C4TwDgpgBAYg9nKBeKBvAUFLUDWBLAOwBMAuKAInjnIBpNsA3AQwBsBXCMgtgWwCMIAJ3QBfANzpQkKACEmg5GnpZ8xMuTmDayqM3aco3fkLoj0AMzYEAxsDxwCUawAsI1nFQAUADzJw+AFZuwACUZEwAzhFCwBFQ3lB4cVRK2InmUJ4AhJ4A5KpEuYmOCQBkpfEAdAXISCiUCOQhIalp2MDOgnAA7oYQvQCigl2CnuRWEN6QthBETTpmZhZWtvaOPEyEPmQpAD6y8jRODqRQfAgsEEwEYbAIrVh4GZ7WJy0Ybdgubh4IPiEST5YQQQYBsQQlQHYMxpEFgiHxCQiIA
*
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok)
* [2]
* [3]
* Forget:
* (kind: ok)
* [2]
* [2,3]
*/
function useFoo({cond, value}: {cond: boolean; value: number}) {
const x = {value: cond ? CONST_NUMBER0 : []};
mutate(x);
const xValue = x.value;
let result;
if (typeof xValue === 'number') {
result = xValue + 1; // (1) here we infer xValue is a primitive
} else {
result = arrayPush(xValue, value); // (2) and propagate it to all other xValue references
}
return result;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{cond: true}],
sequentialRenders: [
{cond: false, value: 2},
{cond: false, value: 3},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { arrayPush, CONST_NUMBER0, mutate } from "shared-runtime";
/**
* Repro for bug in our type inference system. We currently propagate inferred
* types through control flow / potential type guards. Note that this is
* inconsistent with both Flow and Typescript.
* https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkO4AX34kA0SRWxgABAAxYjsgC87OAAB0oOzReythU2Mh2YKQNyILLeMKxeymrgZNLhCIbsL6QBuYVs7DsgBCVD5AuVYolUClMpAZsoiqtorVGvZWpuSqg9OFMAeyjg0HZdTmW3lAAp5NKAPJoABWcwkAEppWZGLg4O12fJ2bSuTyhSKxSwJEJKCKAOQ2tiVvMi3MAMkbOasNb5vP5svlsoNPuFfoD8JFGQqUel8vZAB9TVReCHoHa0MRnlBUwWIJbi6K4DB2RHbGxk1uVSrd-uAIShsDh4hR5PHoun5-siS1SgQADuHuw34AotQECUBGsqysmfYvuyvrbqepblg2EFitBKpwRWOZ9vSuQgA0JgkEGUBJBk9gmCA9JAA
* https://www.typescriptlang.org/play/?#code/C4TwDgpgBAYg9nKBeKBvAUFLUDWBLAOwBMAuKAInjnIBpNsA3AQwBsBXCMgtgWwCMIAJ3QBfANzpQkKACEmg5GnpZ8xMuTmDayqM3aco3fkLoj0AMzYEAxsDxwCUawAsI1nFQAUADzJw+AFZuwACUZEwAzhFCwBFQ3lB4cVRK2InmUJ4AhJ4A5KpEuYmOCQBkpfEAdAXISCiUCOQhIalp2MDOgnAA7oYQvQCigl2CnuRWEN6QthBETTpmZhZWtvaOPEyEPmQpAD6y8jRODqRQfAgsEEwEYbAIrVh4GZ7WJy0Ybdgubh4IPiEST5YQQQYBsQQlQHYMxpEFgiHxCQiIA
*
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok)
* [2]
* [3]
* Forget:
* (kind: ok)
* [2]
* [2,3]
*/
function useFoo(t0) {
const $ = _c(5);
const { cond, value } = t0;
let x;
if ($[0] !== cond) {
x = { value: cond ? CONST_NUMBER0 : [] };
mutate(x);
$[0] = cond;
$[1] = x;
} else {
x = $[1];
}
const xValue = x.value;
let result;
if (typeof xValue === "number") {
result = xValue + 1;
} else {
let t1;
if ($[2] !== value || $[3] !== xValue) {
t1 = arrayPush(xValue, value);
$[2] = value;
$[3] = xValue;
$[4] = t1;
} else {
t1 = $[4];
}
result = t1;
}
return result;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ cond: true }],
sequentialRenders: [
{ cond: false, value: 2 },
{ cond: false, value: 3 },
],
};
```

View File

@@ -0,0 +1,41 @@
import {arrayPush, CONST_NUMBER0, mutate} from 'shared-runtime';
/**
* Repro for bug in our type inference system. We currently propagate inferred
* types through control flow / potential type guards. Note that this is
* inconsistent with both Flow and Typescript.
* https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkO4AX34kA0SRWxgABAAxYjsgC87OAAB0oOzReythU2Mh2YKQNyILLeMKxeymrgZNLhCIbsL6QBuYVs7DsgBCVD5AuVYolUClMpAZsoiqtorVGvZWpuSqg9OFMAeyjg0HZdTmW3lAAp5NKAPJoABWcwkAEppWZGLg4O12fJ2bSuTyhSKxSwJEJKCKAOQ2tiVvMi3MAMkbOasNb5vP5svlsoNPuFfoD8JFGQqUel8vZAB9TVReCHoHa0MRnlBUwWIJbi6K4DB2RHbGxk1uVSrd-uAIShsDh4hR5PHoun5-siS1SgQADuHuw34AotQECUBGsqysmfYvuyvrbqepblg2EFitBKpwRWOZ9vSuQgA0JgkEGUBJBk9gmCA9JAA
* https://www.typescriptlang.org/play/?#code/C4TwDgpgBAYg9nKBeKBvAUFLUDWBLAOwBMAuKAInjnIBpNsA3AQwBsBXCMgtgWwCMIAJ3QBfANzpQkKACEmg5GnpZ8xMuTmDayqM3aco3fkLoj0AMzYEAxsDxwCUawAsI1nFQAUADzJw+AFZuwACUZEwAzhFCwBFQ3lB4cVRK2InmUJ4AhJ4A5KpEuYmOCQBkpfEAdAXISCiUCOQhIalp2MDOgnAA7oYQvQCigl2CnuRWEN6QthBETTpmZhZWtvaOPEyEPmQpAD6y8jRODqRQfAgsEEwEYbAIrVh4GZ7WJy0Ybdgubh4IPiEST5YQQQYBsQQlQHYMxpEFgiHxCQiIA
*
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok)
* [2]
* [3]
* Forget:
* (kind: ok)
* [2]
* [2,3]
*/
function useFoo({cond, value}: {cond: boolean; value: number}) {
const x = {value: cond ? CONST_NUMBER0 : []};
mutate(x);
const xValue = x.value;
let result;
if (typeof xValue === 'number') {
result = xValue + 1; // (1) here we infer xValue is a primitive
} else {
result = arrayPush(xValue, value); // (2) and propagate it to all other xValue references
}
return result;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{cond: true}],
sequentialRenders: [
{cond: false, value: 2},
{cond: false, value: 3},
],
};

View File

@@ -55,11 +55,7 @@ function getNativeLogFunction(level) {
if (arguments.length === 1 && typeof arguments[0] === "string") {
str = arguments[0];
} else {
str = Array.prototype.map
.call(arguments, function (arg) {
return inspect(arg, { depth: 10 });
})
.join(", ");
str = Array.prototype.map.call(arguments, _temp).join(", ");
}
const firstArg = arguments[0];
@@ -92,6 +88,9 @@ function getNativeLogFunction(level) {
}
return t0;
}
function _temp(arg) {
return inspect(arg, { depth: 10 });
}
```

View File

@@ -2,39 +2,55 @@
## Input
```javascript
function component(foo, bar) {
import {mutate} from 'shared-runtime';
function Component({foo, bar}) {
let x = {foo};
let y = {bar};
const f0 = function () {
let a = {y};
let a = [y];
let b = x;
a.x = b;
// this writes y.x = x
a[0].x = b;
};
f0();
mutate(y);
mutate(y.x);
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{foo: 3, bar: 4}],
sequentialRenders: [
{foo: 3, bar: 4},
{foo: 3, bar: 5},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function component(foo, bar) {
import { mutate } from "shared-runtime";
function Component(t0) {
const $ = _c(3);
const { foo, bar } = t0;
let y;
if ($[0] !== bar || $[1] !== foo) {
const x = { foo };
y = { bar };
const f0 = function () {
const a = { y };
const a = [y];
const b = x;
a.x = b;
a[0].x = b;
};
f0();
mutate(y);
mutate(y.x);
$[0] = bar;
$[1] = foo;
$[2] = y;
@@ -44,5 +60,17 @@ function component(foo, bar) {
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ foo: 3, bar: 4 }],
sequentialRenders: [
{ foo: 3, bar: 4 },
{ foo: 3, bar: 5 },
],
};
```
### Eval output
(kind: ok) {"bar":4,"x":{"foo":3,"wat0":"joe"}}
{"bar":5,"x":{"foo":3,"wat0":"joe"}}

View File

@@ -1,12 +1,24 @@
function component(foo, bar) {
import {mutate} from 'shared-runtime';
function Component({foo, bar}) {
let x = {foo};
let y = {bar};
const f0 = function () {
let a = {y};
let a = [y];
let b = x;
a.x = b;
// this writes y.x = x
a[0].x = b;
};
f0();
mutate(y);
mutate(y.x);
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{foo: 3, bar: 4}],
sequentialRenders: [
{foo: 3, bar: 4},
{foo: 3, bar: 5},
],
};

View File

@@ -26,29 +26,20 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function component(a, b) {
const $ = _c(5);
let t0;
if ($[0] !== b) {
t0 = { b };
$[0] = b;
$[1] = t0;
} else {
t0 = $[1];
}
const y = t0;
const $ = _c(2);
const y = { b };
let z;
if ($[2] !== a || $[3] !== y) {
if ($[0] !== a) {
z = { a };
const x = function () {
z.a = 2;
};
x();
$[2] = a;
$[3] = y;
$[4] = z;
$[0] = a;
$[1] = z;
} else {
z = $[4];
z = $[1];
}
return z;
}

View File

@@ -2,21 +2,28 @@
## Input
```javascript
function component(a, b) {
import {mutate} from 'shared-runtime';
function Component({a, b}) {
let z = {a};
let y = {b};
let y = {b: {b}};
let x = function () {
z.a = 2;
console.log(y.b);
mutate(y.b);
};
x();
return z;
return [y, z];
}
export const FIXTURE_ENTRYPOINT = {
fn: component,
params: ['TodoAdd'],
isComponent: 'TodoAdd',
fn: Component,
params: [{a: 2, b: 3}],
sequentialRenders: [
{a: 2, b: 3},
{a: 2, b: 3},
{a: 4, b: 3},
{a: 4, b: 5},
],
};
```
@@ -25,32 +32,46 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function component(a, b) {
import { mutate } from "shared-runtime";
function Component(t0) {
const $ = _c(3);
let z;
const { a, b } = t0;
let t1;
if ($[0] !== a || $[1] !== b) {
z = { a };
const y = { b };
const z = { a };
const y = { b: { b } };
const x = function () {
z.a = 2;
console.log(y.b);
mutate(y.b);
};
x();
t1 = [y, z];
$[0] = a;
$[1] = b;
$[2] = z;
$[2] = t1;
} else {
z = $[2];
t1 = $[2];
}
return z;
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: component,
params: ["TodoAdd"],
isComponent: "TodoAdd",
fn: Component,
params: [{ a: 2, b: 3 }],
sequentialRenders: [
{ a: 2, b: 3 },
{ a: 2, b: 3 },
{ a: 4, b: 3 },
{ a: 4, b: 5 },
],
};
```
### Eval output
(kind: ok) [{"b":{"b":3,"wat0":"joe"}},{"a":2}]
[{"b":{"b":3,"wat0":"joe"}},{"a":2}]
[{"b":{"b":3,"wat0":"joe"}},{"a":2}]
[{"b":{"b":5,"wat0":"joe"}},{"a":2}]

View File

@@ -1,16 +1,23 @@
function component(a, b) {
import {mutate} from 'shared-runtime';
function Component({a, b}) {
let z = {a};
let y = {b};
let y = {b: {b}};
let x = function () {
z.a = 2;
console.log(y.b);
mutate(y.b);
};
x();
return z;
return [y, z];
}
export const FIXTURE_ENTRYPOINT = {
fn: component,
params: ['TodoAdd'],
isComponent: 'TodoAdd',
fn: Component,
params: [{a: 2, b: 3}],
sequentialRenders: [
{a: 2, b: 3},
{a: 2, b: 3},
{a: 4, b: 3},
{a: 4, b: 5},
],
};

View File

@@ -0,0 +1,72 @@
## Input
```javascript
function Component({a, b}) {
let z = {a};
let y = {b};
let x = function () {
z.a = 2;
return Math.max(y.b, 0);
};
x();
return z;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: 2, b: 3}],
sequentialRenders: [
{a: 2, b: 3},
{a: 2, b: 3},
{a: 4, b: 3},
{a: 4, b: 5},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(t0) {
const $ = _c(3);
const { a, b } = t0;
let z;
if ($[0] !== a || $[1] !== b) {
z = { a };
const y = { b };
const x = function () {
z.a = 2;
return Math.max(y.b, 0);
};
x();
$[0] = a;
$[1] = b;
$[2] = z;
} else {
z = $[2];
}
return z;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ a: 2, b: 3 }],
sequentialRenders: [
{ a: 2, b: 3 },
{ a: 2, b: 3 },
{ a: 4, b: 3 },
{ a: 4, b: 5 },
],
};
```
### Eval output
(kind: ok) {"a":2}
{"a":2}
{"a":2}
{"a":2}

View File

@@ -0,0 +1,21 @@
function Component({a, b}) {
let z = {a};
let y = {b};
let x = function () {
z.a = 2;
return Math.max(y.b, 0);
};
x();
return z;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: 2, b: 3}],
sequentialRenders: [
{a: 2, b: 3},
{a: 2, b: 3},
{a: 4, b: 3},
{a: 4, b: 5},
],
};

View File

@@ -22,11 +22,11 @@ import { c as _c } from "react/compiler-runtime";
function Foo(props) {
const $ = _c(2);
let t0;
if ($[0] !== props.router.location) {
if ($[0] !== props.router) {
t0 = (reason) => {
log(props.router.location);
};
$[0] = props.router.location;
$[0] = props.router;
$[1] = t0;
} else {
t0 = $[1];

View File

@@ -35,11 +35,11 @@ function component(t0) {
}
const poke = t1;
let t2;
if ($[2] !== mutator.user) {
if ($[2] !== mutator) {
t2 = () => {
mutator.user.hide();
};
$[2] = mutator.user;
$[2] = mutator;
$[3] = t2;
} else {
t2 = $[3];

View File

@@ -34,11 +34,11 @@ function component(a) {
}
const z = t0;
let t1;
if ($[2] !== z.a) {
if ($[2] !== z) {
t1 = function () {
console.log(z.a);
};
$[2] = z.a;
$[2] = z;
$[3] = t1;
} else {
t1 = $[3];

View File

@@ -36,13 +36,13 @@ function component(a) {
}
const z = t0;
let t1;
if ($[2] !== z.a.a) {
if ($[2] !== z) {
t1 = function () {
(function () {
console.log(z.a.a);
})();
};
$[2] = z.a.a;
$[2] = z;
$[3] = t1;
} else {
t1 = $[3];

View File

@@ -34,11 +34,11 @@ function component(a) {
}
const z = t0;
let t1;
if ($[2] !== z.a.a) {
if ($[2] !== z) {
t1 = function () {
console.log(z.a.a);
};
$[2] = z.a.a;
$[2] = z;
$[3] = t1;
} else {
t1 = $[3];

View File

@@ -97,7 +97,7 @@ function Component(props) {
}
const urls = t5;
let t6;
if ($[6] !== comments.length) {
if ($[6] !== comments) {
t6 = (e) => {
if (!comments.length) {
return;
@@ -105,7 +105,7 @@ function Component(props) {
console.log(comments.length);
};
$[6] = comments.length;
$[6] = comments;
$[7] = t6;
} else {
t6 = $[7];

View File

@@ -56,7 +56,7 @@ function Component(props) {
const { media, comments, urls } = post;
let t1;
if ($[2] !== comments.length) {
if ($[2] !== comments) {
t1 = (e) => {
if (!comments.length) {
return;
@@ -64,7 +64,7 @@ function Component(props) {
console.log(comments.length);
};
$[2] = comments.length;
$[2] = comments;
$[3] = t1;
} else {
t1 = $[3];

View File

@@ -34,7 +34,7 @@ function Component() {
t0 = function update() {
"worklet";
setCount((count_0) => count_0 + 1);
setCount(_temp);
};
$[0] = t0;
} else {
@@ -51,6 +51,9 @@ function Component() {
}
return t1;
}
function _temp(count_0) {
return count_0 + 1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,

View File

@@ -23,11 +23,11 @@ import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(4);
let t0;
if ($[0] !== props.name) {
if ($[0] !== props) {
t0 = function () {
return <div>{props.name}</div>;
};
$[0] = props.name;
$[0] = props;
$[1] = t0;
} else {
t0 = $[1];

View File

@@ -91,7 +91,7 @@ function Parent(t0) {
type: "div",
ref: ref,
key: null,
props: { children: children },
props: { ref: ref, children: children },
};
}
$[0] = children;
@@ -180,7 +180,7 @@ function ParentAndRefAndKey(props) {
type: Parent,
ref: testRef,
key: "testKey",
props: { a: "a", b: { b: "b" }, c: C },
props: { a: "a", b: { b: "b" }, c: C, ref: testRef },
};
}
$[0] = t0;

View File

@@ -0,0 +1,75 @@
## Input
```javascript
import {Throw} from 'shared-runtime';
/**
* Note: this is disabled in the evaluator due to different devmode errors.
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) <invalidtag val="[object Object]"></invalidtag>
* logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag']
*
* Forget:
* (kind: ok) <invalidtag val="[object Object]"></invalidtag>
* logs: [
* 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag',
* 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag',
* ]
*/
function useFoo() {
const invalidTag = Throw;
/**
* Need to be careful to not parse `invalidTag` as a localVar (i.e. render
* Throw). Note that the jsx transform turns this into a string tag:
* `jsx("invalidTag"...
*/
return <invalidTag val={{val: 2}} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { Throw } from "shared-runtime";
/**
* Note: this is disabled in the evaluator due to different devmode errors.
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) <invalidtag val="[object Object]"></invalidtag>
* logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag']
*
* Forget:
* (kind: ok) <invalidtag val="[object Object]"></invalidtag>
* logs: [
* 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag',
* 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag',
* ]
*/
function useFoo() {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <invalidTag val={{ val: 2 }} />;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```

View File

@@ -0,0 +1,29 @@
import {Throw} from 'shared-runtime';
/**
* Note: this is disabled in the evaluator due to different devmode errors.
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) <invalidtag val="[object Object]"></invalidtag>
* logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag']
*
* Forget:
* (kind: ok) <invalidtag val="[object Object]"></invalidtag>
* logs: [
* 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag',
* 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag',
* ]
*/
function useFoo() {
const invalidTag = Throw;
/**
* Need to be careful to not parse `invalidTag` as a localVar (i.e. render
* Throw). Note that the jsx transform turns this into a string tag:
* `jsx("invalidTag"...
*/
return <invalidTag val={{val: 2}} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};

View File

@@ -27,11 +27,10 @@ import * as SharedRuntime from "shared-runtime";
function useFoo(t0) {
const $ = _c(1);
const { cond } = t0;
const MyLocal = SharedRuntime;
if (cond) {
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <MyLocal.Text value={4} />;
t1 = <SharedRuntime.Text value={4} />;
$[0] = t1;
} else {
t1 = $[0];

View File

@@ -22,10 +22,9 @@ import { c as _c } from "react/compiler-runtime";
import * as SharedRuntime from "shared-runtime";
function useFoo() {
const $ = _c(1);
const MyLocal = SharedRuntime;
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <MyLocal.Text value={4} />;
t0 = <SharedRuntime.Text value={4} />;
$[0] = t0;
} else {
t0 = $[0];

View File

@@ -0,0 +1,59 @@
## Input
```javascript
import * as SharedRuntime from 'shared-runtime';
import {invoke} from 'shared-runtime';
function useComponentFactory({name}) {
const localVar = SharedRuntime;
const cb = () => <localVar.Stringify>hello world {name}</localVar.Stringify>;
return invoke(cb);
}
export const FIXTURE_ENTRYPOINT = {
fn: useComponentFactory,
params: [{name: 'sathya'}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import * as SharedRuntime from "shared-runtime";
import { invoke } from "shared-runtime";
function useComponentFactory(t0) {
const $ = _c(4);
const { name } = t0;
let t1;
if ($[0] !== name) {
t1 = () => (
<SharedRuntime.Stringify>hello world {name}</SharedRuntime.Stringify>
);
$[0] = name;
$[1] = t1;
} else {
t1 = $[1];
}
const cb = t1;
let t2;
if ($[2] !== cb) {
t2 = invoke(cb);
$[2] = cb;
$[3] = t2;
} else {
t2 = $[3];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: useComponentFactory,
params: [{ name: "sathya" }],
};
```
### Eval output
(kind: ok) <div>{"children":["hello world ","sathya"]}</div>

View File

@@ -0,0 +1,12 @@
import * as SharedRuntime from 'shared-runtime';
import {invoke} from 'shared-runtime';
function useComponentFactory({name}) {
const localVar = SharedRuntime;
const cb = () => <localVar.Stringify>hello world {name}</localVar.Stringify>;
return invoke(cb);
}
export const FIXTURE_ENTRYPOINT = {
fn: useComponentFactory,
params: [{name: 'sathya'}],
};

View File

@@ -0,0 +1,45 @@
## Input
```javascript
import * as SharedRuntime from 'shared-runtime';
function Component({name}) {
const localVar = SharedRuntime;
return <localVar.Stringify>hello world {name}</localVar.Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{name: 'sathya'}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import * as SharedRuntime from "shared-runtime";
function Component(t0) {
const $ = _c(2);
const { name } = t0;
let t1;
if ($[0] !== name) {
t1 = <SharedRuntime.Stringify>hello world {name}</SharedRuntime.Stringify>;
$[0] = name;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ name: "sathya" }],
};
```
### Eval output
(kind: ok) <div>{"children":["hello world ","sathya"]}</div>

View File

@@ -0,0 +1,10 @@
import * as SharedRuntime from 'shared-runtime';
function Component({name}) {
const localVar = SharedRuntime;
return <localVar.Stringify>hello world {name}</localVar.Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{name: 'sathya'}],
};

View File

@@ -0,0 +1,44 @@
## Input
```javascript
import * as SharedRuntime from 'shared-runtime';
function Component({name}) {
return <SharedRuntime.Stringify>hello world {name}</SharedRuntime.Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{name: 'sathya'}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import * as SharedRuntime from "shared-runtime";
function Component(t0) {
const $ = _c(2);
const { name } = t0;
let t1;
if ($[0] !== name) {
t1 = <SharedRuntime.Stringify>hello world {name}</SharedRuntime.Stringify>;
$[0] = name;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ name: "sathya" }],
};
```
### Eval output
(kind: ok) <div>{"children":["hello world ","sathya"]}</div>

View File

@@ -0,0 +1,9 @@
import * as SharedRuntime from 'shared-runtime';
function Component({name}) {
return <SharedRuntime.Stringify>hello world {name}</SharedRuntime.Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{name: 'sathya'}],
};

View File

@@ -25,10 +25,9 @@ import { c as _c } from "react/compiler-runtime";
import * as SharedRuntime from "shared-runtime";
function useFoo() {
const $ = _c(1);
const MyLocal = SharedRuntime;
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const callback = () => <MyLocal.Text value={4} />;
const callback = () => <SharedRuntime.Text value={4} />;
t0 = callback();
$[0] = t0;

View File

@@ -32,7 +32,7 @@ function Component() {
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
setState((s) => s + 1);
setState(_temp);
};
$[0] = t0;
} else {
@@ -61,6 +61,9 @@ function Component() {
}
return t2;
}
function _temp(s) {
return s + 1;
}
```

View File

@@ -1,32 +0,0 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees
import {useCallback} from 'react';
function useHook(maybeRef) {
return useCallback(() => {
return [maybeRef.current];
}, [maybeRef]);
}
```
## Error
```
3 |
4 | function useHook(maybeRef) {
> 5 | return useCallback(() => {
| ^^^^^^^
> 6 | return [maybeRef.current];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 7 | }, [maybeRef]);
| ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (5:7)
8 | }
9 |
```

View File

@@ -1,32 +0,0 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees
import {useMemo} from 'react';
function useHook(maybeRef, shouldRead) {
return useMemo(() => {
return () => [maybeRef.current];
}, [shouldRead, maybeRef]);
}
```
## Error
```
3 |
4 | function useHook(maybeRef, shouldRead) {
> 5 | return useMemo(() => {
| ^^^^^^^
> 6 | return () => [maybeRef.current];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 7 | }, [shouldRead, maybeRef]);
| ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (5:7)
8 | }
9 |
```

View File

@@ -0,0 +1,53 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees
import {useCallback} from 'react';
import {Stringify} from 'shared-runtime';
/**
* TODO: we're currently bailing out because `contextVar` is a context variable
* and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad
* sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted
* `LoadContext` and `PropertyLoad` instructions into the outer function, which
* we took as eligible dependencies.
*
* One solution is to simply record `LoadContext` identifiers into the
* temporaries sidemap when the instruction occurs *after* the context
* variable's mutable range.
*/
function Foo(props) {
let contextVar;
if (props.cond) {
contextVar = {val: 2};
} else {
contextVar = {};
}
const cb = useCallback(() => [contextVar.val], [contextVar.val]);
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{cond: true}],
};
```
## Error
```
22 | }
23 |
> 24 | const cb = useCallback(() => [contextVar.val], [contextVar.val]);
| ^^^^^^^^^^^^^^^^^^^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (24:24)
25 |
26 | return <Stringify cb={cb} shouldInvokeFns={true} />;
27 | }
```

View File

@@ -0,0 +1,32 @@
// @validatePreserveExistingMemoizationGuarantees
import {useCallback} from 'react';
import {Stringify} from 'shared-runtime';
/**
* TODO: we're currently bailing out because `contextVar` is a context variable
* and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad
* sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted
* `LoadContext` and `PropertyLoad` instructions into the outer function, which
* we took as eligible dependencies.
*
* One solution is to simply record `LoadContext` identifiers into the
* temporaries sidemap when the instruction occurs *after* the context
* variable's mutable range.
*/
function Foo(props) {
let contextVar;
if (props.cond) {
contextVar = {val: 2};
} else {
contextVar = {};
}
const cb = useCallback(() => [contextVar.val], [contextVar.val]);
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{cond: true}],
};

View File

@@ -44,8 +44,6 @@ function Component({propA, propB}) {
| ^^^^^^^^^^^^^^^^^
> 14 | }, [propA?.a, propB.x.y]);
| ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14)
CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14)
15 | }
16 |
```

View File

@@ -0,0 +1,38 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees
import {useCallback} from 'react';
function useHook(maybeRef) {
return useCallback(() => {
return [maybeRef.current];
}, [maybeRef]);
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
import { useCallback } from "react";
function useHook(maybeRef) {
const $ = _c(2);
let t0;
if ($[0] !== maybeRef) {
t0 = () => [maybeRef.current];
$[0] = maybeRef;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,40 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees
import {useMemo} from 'react';
function useHook(maybeRef) {
return useMemo(() => {
return () => [maybeRef.current];
}, [maybeRef]);
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
import { useMemo } from "react";
function useHook(maybeRef) {
const $ = _c(2);
let t0;
let t1;
if ($[0] !== maybeRef) {
t1 = () => [maybeRef.current];
$[0] = maybeRef;
$[1] = t1;
} else {
t1 = $[1];
}
t0 = t1;
return t0;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,8 +1,8 @@
// @validatePreserveExistingMemoizationGuarantees
import {useMemo} from 'react';
function useHook(maybeRef, shouldRead) {
function useHook(maybeRef) {
return useMemo(() => {
return () => [maybeRef.current];
}, [shouldRead, maybeRef]);
}, [maybeRef]);
}

View File

@@ -9,13 +9,13 @@ import {sum} from 'shared-runtime';
function Component({propA, propB}) {
const x = propB.x.y;
return useCallback(() => {
return sum(propA.x, x);
}, [propA.x, x]);
return sum(propA, x);
}, [propA, x]);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{propA: {x: 2}, propB: {x: {y: 3}}}],
params: [{propA: 2, propB: {x: {y: 3}}}],
};
```
@@ -32,9 +32,9 @@ function Component(t0) {
const { propA, propB } = t0;
const x = propB.x.y;
let t1;
if ($[0] !== propA.x || $[1] !== x) {
t1 = () => sum(propA.x, x);
$[0] = propA.x;
if ($[0] !== propA || $[1] !== x) {
t1 = () => sum(propA, x);
$[0] = propA;
$[1] = x;
$[2] = t1;
} else {
@@ -45,7 +45,7 @@ function Component(t0) {
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ propA: { x: 2 }, propB: { x: { y: 3 } } }],
params: [{ propA: 2, propB: { x: { y: 3 } } }],
};
```

View File

@@ -5,11 +5,11 @@ import {sum} from 'shared-runtime';
function Component({propA, propB}) {
const x = propB.x.y;
return useCallback(() => {
return sum(propA.x, x);
}, [propA.x, x]);
return sum(propA, x);
}, [propA, x]);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{propA: {x: 2}, propB: {x: {y: 3}}}],
params: [{propA: 2, propB: {x: {y: 3}}}],
};

View File

@@ -1,81 +0,0 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees
import {useCallback} from 'react';
import {Stringify} from 'shared-runtime';
function Foo(props) {
let contextVar;
if (props.cond) {
contextVar = {val: 2};
} else {
contextVar = {};
}
const cb = useCallback(() => [contextVar.val], [contextVar.val]);
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{cond: true}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
import { useCallback } from "react";
import { Stringify } from "shared-runtime";
function Foo(props) {
const $ = _c(6);
let contextVar;
if ($[0] !== props.cond) {
if (props.cond) {
contextVar = { val: 2 };
} else {
contextVar = {};
}
$[0] = props.cond;
$[1] = contextVar;
} else {
contextVar = $[1];
}
const t0 = contextVar;
let t1;
if ($[2] !== t0.val) {
t1 = () => [contextVar.val];
$[2] = t0.val;
$[3] = t1;
} else {
t1 = $[3];
}
contextVar;
const cb = t1;
let t2;
if ($[4] !== cb) {
t2 = <Stringify cb={cb} shouldInvokeFns={true} />;
$[4] = cb;
$[5] = t2;
} else {
t2 = $[5];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{ cond: true }],
};
```
### Eval output
(kind: ok) <div>{"cb":{"kind":"Function","result":[2]},"shouldInvokeFns":true}</div>

View File

@@ -1,21 +0,0 @@
// @validatePreserveExistingMemoizationGuarantees
import {useCallback} from 'react';
import {Stringify} from 'shared-runtime';
function Foo(props) {
let contextVar;
if (props.cond) {
contextVar = {val: 2};
} else {
contextVar = {};
}
const cb = useCallback(() => [contextVar.val], [contextVar.val]);
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{cond: true}],
};

View File

@@ -45,18 +45,16 @@ function Foo(props) {
} else {
x = $[1];
}
const t0 = x;
let t1;
if ($[2] !== t0) {
t1 = () => [x];
$[2] = t0;
$[3] = t1;
let t0;
if ($[2] !== x) {
t0 = () => [x];
$[2] = x;
$[3] = t0;
} else {
t1 = $[3];
t0 = $[3];
}
x;
const cb = t1;
const cb = t0;
return cb;
}

View File

@@ -70,28 +70,26 @@ function useBar(t0, cond) {
if (cond) {
x = b;
}
const t2 = x;
let t3;
if ($[1] !== a || $[2] !== t2) {
t3 = () => [a, x];
let t2;
if ($[1] !== a || $[2] !== x) {
t2 = () => [a, x];
$[1] = a;
$[2] = t2;
$[3] = t3;
$[2] = x;
$[3] = t2;
} else {
t3 = $[3];
t2 = $[3];
}
x;
const cb = t3;
let t4;
const cb = t2;
let t3;
if ($[4] !== cb) {
t4 = <Stringify cb={cb} shouldInvoke={true} />;
t3 = <Stringify cb={cb} shouldInvoke={true} />;
$[4] = cb;
$[5] = t4;
$[5] = t3;
} else {
t4 = $[5];
t3 = $[5];
}
return t4;
return t3;
}
export const FIXTURE_ENTRYPOINT = {

View File

@@ -37,9 +37,9 @@ import { useCallback } from "react";
function useHook(x) {
const $ = _c(2);
let t0;
if ($[0] !== x.y.z) {
if ($[0] !== x) {
t0 = () => [x.y.z];
$[0] = x.y.z;
$[0] = x;
$[1] = t0;
} else {
t0 = $[1];

View File

@@ -29,9 +29,9 @@ function useFoo(t0) {
const $ = _c(2);
const { a } = t0;
let t1;
if ($[0] !== a.b.c) {
if ($[0] !== a) {
t1 = <Stringify fn={() => a.b.c} shouldInvokeFns={true} />;
$[0] = a.b.c;
$[0] = a;
$[1] = t1;
} else {
t1 = $[1];

View File

@@ -51,12 +51,12 @@ function Foo(t0) {
const fn = t1;
useIdentity(null);
let x;
if ($[2] !== a.b.c || $[3] !== cond) {
if ($[2] !== a || $[3] !== cond) {
x = makeArray();
if (cond) {
x.push(identity(a.b.c));
}
$[2] = a.b.c;
$[2] = a;
$[3] = cond;
$[4] = x;
} else {

View File

@@ -41,9 +41,9 @@ function useFoo(t0) {
local = $[1];
}
let t1;
if ($[2] !== local.b.c) {
if ($[2] !== local) {
t1 = () => local.b.c;
$[2] = local.b.c;
$[2] = local;
$[3] = t1;
} else {
t1 = $[3];

View File

@@ -50,12 +50,12 @@ function Foo(t0) {
const fn = t1;
useIdentity(null);
let arr;
if ($[2] !== a.b?.c.e || $[3] !== cond) {
if ($[2] !== a || $[3] !== cond) {
arr = makeArray();
if (cond) {
arr.push(identity(a.b?.c.e));
}
$[2] = a.b?.c.e;
$[2] = a;
$[3] = cond;
$[4] = arr;
} else {

View File

@@ -41,9 +41,9 @@ function useFoo(t0) {
local = $[1];
}
let t1;
if ($[2] !== local.b.c) {
if ($[2] !== local) {
t1 = () => [() => local.b.c];
$[2] = local.b.c;
$[2] = local;
$[3] = t1;
} else {
t1 = $[3];

View File

@@ -34,9 +34,9 @@ function useFoo(t0) {
const $ = _c(4);
const { a } = t0;
let t1;
if ($[0] !== a.b.c) {
if ($[0] !== a) {
t1 = () => () => ({ value: a.b.c });
$[0] = a.b.c;
$[0] = a;
$[1] = t1;
} else {
t1 = $[1];

View File

@@ -34,13 +34,13 @@ function useFoo(t0) {
const $ = _c(4);
const { a } = t0;
let t1;
if ($[0] !== a.b.c) {
if ($[0] !== a) {
t1 = {
fn() {
return identity(a.b.c);
},
};
$[0] = a.b.c;
$[0] = a;
$[1] = t1;
} else {
t1 = $[1];

View File

@@ -34,9 +34,9 @@ function useFoo(t0) {
const $ = _c(2);
const { a } = t0;
let t1;
if ($[0] !== a.b) {
if ($[0] !== a) {
t1 = <Stringify fn={() => a.b?.c.d?.e} shouldInvokeFns={true} />;
$[0] = a.b;
$[0] = a;
$[1] = t1;
} else {
t1 = $[1];

View File

@@ -29,9 +29,9 @@ function Foo(t0) {
const $ = _c(5);
const { data } = t0;
let t1;
if ($[0] !== data.a.d) {
if ($[0] !== data.a) {
t1 = () => data.a.d;
$[0] = data.a.d;
$[0] = data.a;
$[1] = t1;
} else {
t1 = $[1];

View File

@@ -51,7 +51,7 @@ import { identity } from "shared-runtime";
function Component(props) {
const $ = _c(4);
let x;
if ($[0] !== props.cond) {
if ($[0] !== props) {
const f = () => {
if (props.cond) {
x = 1;
@@ -62,7 +62,7 @@ function Component(props) {
const f2 = identity(f);
f2();
$[0] = props.cond;
$[0] = props;
$[1] = x;
} else {
x = $[1];

View File

@@ -0,0 +1,102 @@
## Input
```javascript
import {useRef, forwardRef} from 'react';
import {Stringify} from 'shared-runtime';
/**
* Fixture showing that Ref types may be reactive.
* We should always take a dependency on ref values (the outer box) as
* they may be reactive. Pruning should be done in
* `pruneNonReactiveDependencies`
*/
function Parent({cond}) {
const ref1 = useRef(1);
const ref2 = useRef(2);
const ref = cond ? ref1 : ref2;
return <Child ref={ref} />;
}
function ChildImpl(_props, ref) {
const cb = () => ref.current;
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
const Child = forwardRef(ChildImpl);
export const FIXTURE_ENTRYPOINT = {
fn: Parent,
params: [{cond: true}],
sequentialRenders: [{cond: true}, {cond: false}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useRef, forwardRef } from "react";
import { Stringify } from "shared-runtime";
/**
* Fixture showing that Ref types may be reactive.
* We should always take a dependency on ref values (the outer box) as
* they may be reactive. Pruning should be done in
* `pruneNonReactiveDependencies`
*/
function Parent(t0) {
const $ = _c(2);
const { cond } = t0;
const ref1 = useRef(1);
const ref2 = useRef(2);
const ref = cond ? ref1 : ref2;
let t1;
if ($[0] !== ref) {
t1 = <Child ref={ref} />;
$[0] = ref;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
function ChildImpl(_props, ref) {
const $ = _c(4);
let t0;
if ($[0] !== ref) {
t0 = () => ref.current;
$[0] = ref;
$[1] = t0;
} else {
t0 = $[1];
}
const cb = t0;
let t1;
if ($[2] !== cb) {
t1 = <Stringify cb={cb} shouldInvokeFns={true} />;
$[2] = cb;
$[3] = t1;
} else {
t1 = $[3];
}
return t1;
}
const Child = forwardRef(ChildImpl);
export const FIXTURE_ENTRYPOINT = {
fn: Parent,
params: [{ cond: true }],
sequentialRenders: [{ cond: true }, { cond: false }],
};
```
### Eval output
(kind: ok) <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
<div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>

View File

@@ -0,0 +1,29 @@
import {useRef, forwardRef} from 'react';
import {Stringify} from 'shared-runtime';
/**
* Fixture showing that Ref types may be reactive.
* We should always take a dependency on ref values (the outer box) as
* they may be reactive. Pruning should be done in
* `pruneNonReactiveDependencies`
*/
function Parent({cond}) {
const ref1 = useRef(1);
const ref2 = useRef(2);
const ref = cond ? ref1 : ref2;
return <Child ref={ref} />;
}
function ChildImpl(_props, ref) {
const cb = () => ref.current;
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
const Child = forwardRef(ChildImpl);
export const FIXTURE_ENTRYPOINT = {
fn: Parent,
params: [{cond: true}],
sequentialRenders: [{cond: true}, {cond: false}],
};

View File

@@ -0,0 +1,79 @@
## Input
```javascript
import {useRef} from 'react';
import {Stringify} from 'shared-runtime';
/**
* Fixture showing that Ref types may be reactive.
* We should always take a dependency on ref values (the outer box) as
* they may be reactive. Pruning should be done in
* `pruneNonReactiveDependencies`
*/
function Component({cond}) {
const ref1 = useRef(1);
const ref2 = useRef(2);
const ref = cond ? ref1 : ref2;
const cb = () => ref.current;
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{cond: true}],
sequentialRenders: [{cond: true}, {cond: false}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useRef } from "react";
import { Stringify } from "shared-runtime";
/**
* Fixture showing that Ref types may be reactive.
* We should always take a dependency on ref values (the outer box) as
* they may be reactive. Pruning should be done in
* `pruneNonReactiveDependencies`
*/
function Component(t0) {
const $ = _c(4);
const { cond } = t0;
const ref1 = useRef(1);
const ref2 = useRef(2);
const ref = cond ? ref1 : ref2;
let t1;
if ($[0] !== ref) {
t1 = () => ref.current;
$[0] = ref;
$[1] = t1;
} else {
t1 = $[1];
}
const cb = t1;
let t2;
if ($[2] !== cb) {
t2 = <Stringify cb={cb} shouldInvokeFns={true} />;
$[2] = cb;
$[3] = t2;
} else {
t2 = $[3];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ cond: true }],
sequentialRenders: [{ cond: true }, { cond: false }],
};
```
### Eval output
(kind: ok) <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
<div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>

View File

@@ -0,0 +1,22 @@
import {useRef} from 'react';
import {Stringify} from 'shared-runtime';
/**
* Fixture showing that Ref types may be reactive.
* We should always take a dependency on ref values (the outer box) as
* they may be reactive. Pruning should be done in
* `pruneNonReactiveDependencies`
*/
function Component({cond}) {
const ref1 = useRef(1);
const ref2 = useRef(2);
const ref = cond ? ref1 : ref2;
const cb = () => ref.current;
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{cond: true}],
sequentialRenders: [{cond: true}, {cond: false}],
};

View File

@@ -31,9 +31,9 @@ function useFoo(t0) {
const $ = _c(2);
const { a } = t0;
let t1;
if ($[0] !== a.b) {
if ($[0] !== a) {
t1 = <Stringify fn={() => a.b?.c.d?.e} shouldInvokeFns={true} />;
$[0] = a.b;
$[0] = a;
$[1] = t1;
} else {
t1 = $[1];

View File

@@ -13,7 +13,7 @@ function Foo(props, ref) {
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{bar: 'foo'}, {ref: {cuurrent: 1}}],
params: [{bar: 'foo'}, {ref: {current: 1}}],
isComponent: true,
};
@@ -26,35 +26,39 @@ import { c as _c } from "react/compiler-runtime";
import { useEffect } from "react";
function Foo(props, ref) {
const $ = _c(4);
const $ = _c(5);
let t0;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
if ($[0] !== ref) {
t0 = () => {
ref.current = 2;
};
t1 = [];
$[0] = t0;
$[1] = t1;
$[0] = ref;
$[1] = t0;
} else {
t0 = $[0];
t1 = $[1];
t0 = $[1];
}
let t1;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t1 = [];
$[2] = t1;
} else {
t1 = $[2];
}
useEffect(t0, t1);
let t2;
if ($[2] !== props.bar) {
if ($[3] !== props.bar) {
t2 = <div>{props.bar}</div>;
$[2] = props.bar;
$[3] = t2;
$[3] = props.bar;
$[4] = t2;
} else {
t2 = $[3];
t2 = $[4];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{ bar: "foo" }, { ref: { cuurrent: 1 } }],
params: [{ bar: "foo" }, { ref: { current: 1 } }],
isComponent: true,
};

View File

@@ -9,6 +9,6 @@ function Foo(props, ref) {
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{bar: 'foo'}, {ref: {cuurrent: 1}}],
params: [{bar: 'foo'}, {ref: {current: 1}}],
isComponent: true,
};

View File

@@ -42,7 +42,7 @@ const t0 = "module_t0";
const c_0 = "module_c_0";
function useFoo(props) {
const $0 = _c(4);
const c_00 = $0[0] !== props.value;
const c_00 = $0[0] !== props;
let t1;
if (c_00) {
t1 = () => {
@@ -57,7 +57,7 @@ function useFoo(props) {
};
return b;
};
$0[0] = props.value;
$0[0] = props;
$0[1] = t1;
} else {
t1 = $0[1];

View File

@@ -43,7 +43,7 @@ const t0 = "module_t0";
const c_0 = "module_c_0";
function useFoo(props) {
const $0 = _c(2);
const c_00 = $0[0] !== props.value;
const c_00 = $0[0] !== props;
let t1;
if (c_00) {
const a = {
@@ -61,7 +61,7 @@ function useFoo(props) {
};
t1 = a.foo().bar();
$0[0] = props.value;
$0[0] = props;
$0[1] = t1;
} else {
t1 = $0[1];

View File

@@ -39,7 +39,7 @@ function Component() {
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions
function Component() {
const $ = _c(8);
const $ = _c(7);
const items = useItems();
let t0;
let t1;
@@ -47,35 +47,25 @@ function Component() {
if ($[0] !== items) {
t2 = Symbol.for("react.early_return_sentinel");
bb0: {
let t3;
if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
t3 = (t4) => {
const [item] = t4;
return item.name != null;
};
$[4] = t3;
} else {
t3 = $[4];
}
t0 = items.filter(t3);
t0 = items.filter(_temp);
const filteredItems = t0;
if (filteredItems.length === 0) {
let t4;
if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
t4 = (
let t3;
if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
t3 = (
<div>
<span />
</div>
);
$[5] = t4;
$[4] = t3;
} else {
t4 = $[5];
t3 = $[4];
}
t2 = t4;
t2 = t3;
break bb0;
}
t1 = filteredItems.map(_temp);
t1 = filteredItems.map(_temp2);
}
$[0] = items;
$[1] = t1;
@@ -90,19 +80,23 @@ function Component() {
return t2;
}
let t3;
if ($[6] !== t1) {
if ($[5] !== t1) {
t3 = <>{t1}</>;
$[6] = t1;
$[7] = t3;
$[5] = t1;
$[6] = t3;
} else {
t3 = $[7];
t3 = $[6];
}
return t3;
}
function _temp(t0) {
function _temp2(t0) {
const [item_0] = t0;
return <Stringify item={item_0} />;
}
function _temp(t0) {
const [item] = t0;
return item.name != null;
}
```

View File

@@ -38,7 +38,7 @@ function Component() {
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
setState((s) => s + 1);
setState(_temp);
};
$[0] = t0;
} else {
@@ -67,6 +67,9 @@ function Component() {
}
return t2;
}
function _temp(s) {
return s + 1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,

View File

@@ -41,11 +41,11 @@ function Component(props) {
const $ = _c(5);
const Foo = useContext(FooContext);
let t0;
if ($[0] !== Foo.current) {
if ($[0] !== Foo) {
t0 = () => {
mutate(Foo.current);
};
$[0] = Foo.current;
$[0] = Foo;
$[1] = t0;
} else {
t0 = $[1];

View File

@@ -42,7 +42,7 @@ function Component(props) {
const $ = _c(6);
const foo = useContext(FooContext);
let t0;
if ($[0] !== foo.current) {
if ($[0] !== foo) {
t0 = () => {
if (foo.current) {
return {};
@@ -50,7 +50,7 @@ function Component(props) {
return null;
}
};
$[0] = foo.current;
$[0] = foo;
$[1] = t0;
} else {
t0 = $[1];

View File

@@ -34,11 +34,11 @@ function Component(props) {
const $ = _c(5);
const foo = useContext(FooContext);
let t0;
if ($[0] !== foo.current) {
if ($[0] !== foo) {
t0 = () => {
console.log(foo.current);
};
$[0] = foo.current;
$[0] = foo;
$[1] = t0;
} else {
t0 = $[1];

View File

@@ -35,7 +35,7 @@ function Component(props) {
import { c as _c } from "react/compiler-runtime"; // @enableTransitivelyFreezeFunctionExpressions:false
function Component(props) {
const $ = _c(9);
const $ = _c(7);
const item = useMutable(props.itemId);
const dispatch = useDispatch();
useFreeze(dispatch);
@@ -51,7 +51,8 @@ function Component(props) {
}
const exit = t0;
let t1;
if ($[2] !== exit || $[3] !== item.value) {
let t2;
if ($[2] !== exit || $[3] !== item) {
t1 = () => {
const cleanup = GlobalEventEmitter.addListener("onInput", () => {
if (item.value) {
@@ -60,30 +61,24 @@ function Component(props) {
});
return () => cleanup.remove();
};
t2 = [exit, item];
$[2] = exit;
$[3] = item.value;
$[3] = item;
$[4] = t1;
$[5] = t2;
} else {
t1 = $[4];
}
let t2;
if ($[5] !== exit || $[6] !== item) {
t2 = [exit, item];
$[5] = exit;
$[6] = item;
$[7] = t2;
} else {
t2 = $[7];
t2 = $[5];
}
useEffect(t1, t2);
maybeMutate(item);
let t3;
if ($[8] === Symbol.for("react.memo_cache_sentinel")) {
if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
t3 = <div />;
$[8] = t3;
$[6] = t3;
} else {
t3 = $[8];
t3 = $[6];
}
return t3;
}

View File

@@ -34,7 +34,6 @@ const skipFilter = new Set([
'capturing-arrow-function-1',
'capturing-func-mutate-3',
'capturing-func-mutate-nested',
'capturing-func-mutate',
'capturing-function-1',
'capturing-function-alias-computed-load',
'capturing-function-decl',
@@ -236,7 +235,6 @@ const skipFilter = new Set([
'capturing-fun-alias-captured-mutate-2',
'capturing-fun-alias-captured-mutate-arr-2',
'capturing-func-alias-captured-mutate-arr',
'capturing-func-alias-captured-mutate',
'capturing-func-alias-computed-mutate',
'capturing-func-alias-mutate',
'capturing-func-alias-receiver-computed-mutate',
@@ -475,6 +473,9 @@ const skipFilter = new Set([
'rules-of-hooks/rules-of-hooks-93dc5d5e538a',
'rules-of-hooks/rules-of-hooks-69521d94fa03',
// false positives
'invalid-jsx-lowercase-localvar',
// bugs
'fbt/bug-fbt-plural-multiple-function-calls',
'fbt/bug-fbt-plural-multiple-mixed-call-tag',
@@ -484,7 +485,7 @@ const skipFilter = new Set([
'bug-aliased-capture-mutate',
'bug-functiondecl-hoisting',
'bug-try-catch-maybe-null-dependency',
'bug-nonreactive-ref',
'bug-type-inference-control-flow',
'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted',
'bug-invalid-phi-as-dependency',
'reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond',

View File

@@ -107,8 +107,9 @@ export function setPropertyByKey<
return arg;
}
export function arrayPush<T>(arr: Array<T>, ...values: Array<T>): void {
export function arrayPush<T>(arr: Array<T>, ...values: Array<T>): Array<T> {
arr.push(...values);
return arr;
}
export function graphql(value: string): string {
@@ -252,6 +253,9 @@ export function Stringify(props: any): React.ReactElement {
toJSON(props, props?.shouldInvokeFns),
);
}
export function Throw() {
throw new Error();
}
export function ValidateMemoization({
inputs,

View File

@@ -111,7 +111,7 @@
},
"scripts": {
"build": "node ./scripts/rollup/build-all-release-channels.js",
"build-for-devtools": "cross-env yarn build react/index,react/jsx,react/compiler-runtime,react-dom/index,react-dom/client,react-dom/unstable_testing,react-dom/test-utils,react-is,react-debug-tools,scheduler,react-test-renderer,react-refresh,react-art --type=NODE --release-channel=experimental",
"build-for-devtools": "cross-env RELEASE_CHANNEL=experimental yarn build react/index,react/jsx,react/compiler-runtime,react-dom/index,react-dom/client,react-dom/unstable_testing,react-dom/test-utils,react-is,react-debug-tools,scheduler,react-test-renderer,react-refresh,react-art --type=NODE",
"build-for-devtools-dev": "yarn build-for-devtools --type=NODE_DEV",
"build-for-devtools-prod": "yarn build-for-devtools --type=NODE_PROD",
"build-for-flight-dev": "cross-env RELEASE_CHANNEL=experimental node ./scripts/rollup/build.js react/index,react/jsx,react.react-server,react-dom/index,react-dom/client,react-dom/server,react-dom.react-server,react-dom-server.node,react-dom-server-legacy.node,scheduler,react-server-dom-webpack/ --type=NODE_DEV,ESM_PROD,NODE_ES2015 && mv ./build/node_modules ./build/oss-experimental",

View File

@@ -20,8 +20,8 @@
"homepage": "https://react.dev/",
"peerDependencies": {
"jest": "^23.0.1 || ^24.0.0 || ^25.1.0 || ^26.0.0 || ^27.0.0 || ^28.0.0 || ^29.0.0",
"react": "^19.0.5",
"react-test-renderer": "^19.0.5"
"react": "^19.0.0",
"react-test-renderer": "^19.0.0"
},
"files": [
"LICENSE",

View File

@@ -1,7 +1,7 @@
{
"name": "react-art",
"description": "React ART is a JavaScript library for drawing vector graphics using React. It provides declarative and reactive bindings to the ART library. Using the same declarative API you can render the output to either Canvas, SVG or VML (IE8).",
"version": "19.0.5",
"version": "19.0.0",
"main": "index.js",
"repository": {
"type": "git",
@@ -27,7 +27,7 @@
"scheduler": "^0.23.0"
},
"peerDependencies": {
"react": "^19.0.5"
"react": "^19.0.0"
},
"files": [
"LICENSE",

View File

@@ -363,6 +363,8 @@ export function resolveUpdatePriority(): EventPriority {
return currentUpdatePriority || DefaultEventPriority;
}
export function trackSchedulerEvent(): void {}
export function resolveEventType(): null | string {
return null;
}

View File

@@ -79,8 +79,6 @@ import getComponentNameFromType from 'shared/getComponentNameFromType';
import {getOwnerStackByComponentInfoInDev} from 'shared/ReactComponentInfoStack';
import hasOwnProperty from 'shared/hasOwnProperty';
import {injectInternals} from './ReactFlightClientDevToolsHook';
import ReactVersion from 'shared/ReactVersion';
@@ -137,8 +135,6 @@ const RESOLVED_MODULE = 'resolved_module';
const INITIALIZED = 'fulfilled';
const ERRORED = 'rejected';
const __PROTO__ = '__proto__';
type PendingChunk<T> = {
status: 'pending',
value: null | Array<(T) => mixed>,
@@ -928,21 +924,10 @@ function waitForReference<T>(
return;
}
}
const name = path[i];
if (
typeof value === 'object' &&
value !== null &&
hasOwnProperty.call(value, name)
) {
value = value[name];
} else {
throw new Error('Invalid reference.');
}
value = value[path[i]];
}
const mappedValue = map(response, value, parentObject, key);
if (key !== __PROTO__) {
parentObject[key] = mappedValue;
}
parentObject[key] = mappedValue;
// If this is the root object for a model reference, where `handler.value`
// is a stale `null`, the resolved value can be used directly.
@@ -1108,9 +1093,7 @@ function loadServerReference<A: Iterable<any>, T>(
resolvedValue = resolvedValue.bind.apply(resolvedValue, boundArgs);
}
if (key !== __PROTO__) {
parentObject[key] = resolvedValue;
}
parentObject[key] = resolvedValue;
// If this is the root object for a model reference, where `handler.value`
// is a stale `null`, the resolved value can be used directly.
@@ -1389,7 +1372,7 @@ function parseModelString(
// Symbol
return Symbol.for(value.slice(2));
}
case 'h': {
case 'F': {
// Server Reference
const ref = value.slice(2);
return getOutlinedModel(
@@ -1516,20 +1499,18 @@ function parseModelString(
// In DEV mode we encode omitted objects in logs as a getter that throws
// so that when you try to access it on the client, you know why that
// happened.
if (key !== __PROTO__) {
Object.defineProperty(parentObject, key, {
get: function () {
// TODO: We should ideally throw here to indicate a difference.
return (
'This object has been omitted by React in the console log ' +
'to avoid sending too much data from the server. Try logging smaller ' +
'or more specific objects.'
);
},
enumerable: true,
configurable: false,
});
}
Object.defineProperty(parentObject, key, {
get: function () {
// TODO: We should ideally throw here to indicate a difference.
return (
'This object has been omitted by React in the console log ' +
'to avoid sending too much data from the server. Try logging smaller ' +
'or more specific objects.'
);
},
enumerable: true,
configurable: false,
});
return null;
}
// Fallthrough
@@ -3163,9 +3144,6 @@ function parseModel<T>(response: Response, json: UninitializedModel): T {
function createFromJSONCallback(response: Response) {
// $FlowFixMe[missing-this-annot]
return function (key: string, value: JSONValue) {
if (key === __PROTO__) {
return undefined;
}
if (typeof value === 'string') {
// We can't use .bind here because we need the "this" value.
return parseModelString(response, this, key, value);

View File

@@ -98,8 +98,6 @@ export type ReactServerValue =
type ReactServerObject = {+[key: string]: ReactServerValue};
const __PROTO__ = '__proto__';
function serializeByValueID(id: number): string {
return '$' + id.toString(16);
}
@@ -109,7 +107,7 @@ function serializePromiseID(id: number): string {
}
function serializeServerReferenceID(id: number): string {
return '$h' + id.toString(16);
return '$F' + id.toString(16);
}
function serializeTemporaryReferenceMarker(): string {
@@ -117,6 +115,7 @@ function serializeTemporaryReferenceMarker(): string {
}
function serializeFormDataReference(id: number): string {
// Why K? F is "Function". D is "Date". What else?
return '$K' + id.toString(16);
}
@@ -366,15 +365,6 @@ export function processReply(
): ReactJSONValue {
const parent = this;
if (__DEV__) {
if (key === __PROTO__) {
console.error(
'Expected not to serialize an object with own property `__proto__`. When parsed this property will be omitted.%s',
describeObjectForErrorMessage(parent, key),
);
}
}
// Make sure that `parent[key]` wasn't JSONified before `value` was passed to us
if (__DEV__) {
// $FlowFixMe[incompatible-use]
@@ -487,22 +477,8 @@ export function processReply(
}
}
const existingReference = writtenObjects.get(value);
// $FlowFixMe[method-unbinding]
if (typeof value.then === 'function') {
if (existingReference !== undefined) {
if (modelRoot === value) {
// This is the ID we're currently emitting so we need to write it
// once but if we discover it again, we refer to it by id.
modelRoot = null;
} else {
// We've already emitted this as an outlined object, so we can
// just refer to that by its existing ID.
return existingReference;
}
}
// We assume that any object with a .then property is a "Thenable" type,
// or a Promise type. Either of which can be represented by a Promise.
if (formData === null) {
@@ -511,19 +487,11 @@ export function processReply(
}
pendingParts++;
const promiseId = nextPartId++;
const promiseReference = serializePromiseID(promiseId);
writtenObjects.set(value, promiseReference);
const thenable: Thenable<any> = (value: any);
thenable.then(
partValue => {
try {
const previousReference = writtenObjects.get(partValue);
let partJSON;
if (previousReference !== undefined) {
partJSON = JSON.stringify(previousReference);
} else {
partJSON = serializeModel(partValue, promiseId);
}
const partJSON = serializeModel(partValue, promiseId);
// $FlowFixMe[incompatible-type] We know it's not null because we assigned it above.
const data: FormData = formData;
data.append(formFieldPrefix + promiseId, partJSON);
@@ -539,9 +507,10 @@ export function processReply(
// that throws on the server instead.
reject,
);
return promiseReference;
return serializePromiseID(promiseId);
}
const existingReference = writtenObjects.get(value);
if (existingReference !== undefined) {
if (modelRoot === value) {
// This is the ID we're currently emitting so we need to write it
@@ -801,10 +770,6 @@ export function processReply(
if (typeof value === 'function') {
const metaData = knownServerReferences.get(value);
if (metaData !== undefined) {
const existingReference = writtenObjects.get(value);
if (existingReference !== undefined) {
return existingReference;
}
const metaDataJSON = JSON.stringify(metaData, resolveToJSON);
if (formData === null) {
// Upgrade to use FormData to allow us to stream this value.
@@ -813,10 +778,7 @@ export function processReply(
// The reference to this function came from the same client so we can pass it back.
const refId = nextPartId++;
formData.set(formFieldPrefix + refId, metaDataJSON);
const serverReferenceId = serializeServerReferenceID(refId);
// Store the server reference ID for deduplication.
writtenObjects.set(value, serverReferenceId);
return serverReferenceId;
return serializeServerReferenceID(refId);
}
if (temporaryReferences !== undefined && key.indexOf(':') === -1) {
// TODO: If the property name contains a colon, we don't dedupe. Escape instead.

View File

@@ -214,7 +214,7 @@ const SuspenseException: mixed = new Error(
'`try/catch` block. Capturing without rethrowing will lead to ' +
'unexpected behavior.\n\n' +
'To handle async errors, wrap your component in an error boundary, or ' +
"call the promise's `.catch` method and pass the result to `use`",
"call the promise's `.catch` method and pass the result to `use`.",
);
function use<T>(usable: Usable<T>): T {
@@ -560,7 +560,7 @@ function useId(): string {
// useMemoCache is an implementation detail of Forget's memoization
// it should not be called directly in user-generated code
function useMemoCache(size: number): Array<any> {
function useMemoCache(size: number): Array<mixed> {
const fiber = currentFiber;
// Don't throw, in case this is called from getPrimitiveStackCache
if (fiber == null) {

View File

@@ -1,7 +1,7 @@
{
"name": "react-dom-bindings",
"description": "React implementation details for react-dom.",
"version": "19.0.5",
"version": "19.0.0",
"private": true,
"main": "index.js",
"repository": {
@@ -18,6 +18,6 @@
},
"homepage": "https://react.dev/",
"peerDependencies": {
"react": "^19.0.5"
"react": "^19.0.0"
}
}

Some files were not shown because too many files have changed in this diff Show More